1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2003-2007 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11 12package mx.rpc.xml 13{ 14 15import flash.utils.ByteArray; 16import flash.utils.Dictionary; 17import mx.collections.ArrayCollection; 18import mx.collections.IList; 19import mx.messaging.config.LoaderConfig; 20import mx.utils.DescribeTypeCache; 21import mx.utils.object_proxy; 22import mx.utils.ObjectProxy; 23import mx.utils.ObjectUtil; 24 25[ExcludeClass] 26 27/** 28 * Encodes an ActionScript Object graph to XML based on an XML Schema. 29 * 30 * @private 31 */ 32public class XMLEncoder extends SchemaProcessor implements IXMLEncoder 33{ 34 public function XMLEncoder() 35 { 36 super(); 37 38 // Depending on the target player version, xml characters in strings 39 // need or need not be escaped. Prior to version 10, Flex needs to 40 // do it. The version test to determine this behavior is done here, so 41 // that it's not repeated every time xml content is encoded. This is a 42 // fix for bug SDK-18326. 43 if (LoaderConfig.swfVersion >= 10) 44 { 45 _escapeXMLChars = false; 46 } 47 } 48 49 //-------------------------------------------------------------------------- 50 // 51 // Methods 52 // 53 //-------------------------------------------------------------------------- 54 55 /** 56 * Encodes an ActionScript value as XML. 57 * 58 * @param value The ActionScript value to encode as XML. 59 * @param name The QName of an XML Schema <code>element</code> that 60 * describes how to encode the value, or the name to be used for the 61 * encoded XML node when a type parameter is also specified. 62 * @param type The QName of an XML Schema <code>simpleType</code> or 63 * <code>complexType</code> definition that describes how to encode the 64 * @param definition If neither a top level element nor type exists in the 65 * schema to describe how to encode this value, a custom element definition 66 * can be provided. 67 */ 68 public function encode(value:*, name:QName = null, type:QName = null, definition:XML = null):XMLList 69 { 70 var result:XMLList = new XMLList(); 71 var content:XML; 72 73 // FIXME: Handle generic name == null case with some default encoding? 74 if (name == null) 75 name = new QName("", "root"); 76 77 if (type != null) 78 { 79 content = encodeXSINil(null, name, value); 80 if (content == null) 81 { 82 // If encodeXSINil didn't create content, we do now. 83 content = createElement(name); 84 85 // However, value can still be null if the element wasn't 86 // allowed to be nillable. 87 // FIXME: should we skip null or always create content with xsi:nil? 88 if (value == null) 89 setValue(content, null); 90 else 91 encodeType(type, content, name, value); 92 } 93 } 94 else 95 { 96 var elementDefinition:XML = definition; 97 var mustReleaseScope:Boolean = false; 98 if (elementDefinition == null) 99 { 100 elementDefinition = schemaManager.getNamedDefinition(name, constants.elementTypeQName); 101 // If we found a definition through the schemaManager, the relevant 102 // schema was pushed in scope, so we must remember to release it. 103 if (elementDefinition != null) 104 mustReleaseScope = true; 105 } 106 107 // Encoding is based on an element definition, either a custom one 108 // was provided or we looked one up for the given name. If no definition 109 // was found, encodeElementTopLevel will encode with anyType. 110 content = encodeElementTopLevel(elementDefinition, name, value); 111 112 if (mustReleaseScope) 113 schemaManager.releaseScope(); 114 } 115 116 if (content != null) 117 result += content; 118 119 return result; 120 } 121 122 123 124 /** 125 * All content: 126 * (annotation?, (element | any)*) 127 * 128 * FIXME: This needs work, right now it treats all as a sequence. 129 * @private 130 */ 131 public function encodeAll(definition:XML, parent:XMLList, name:QName, value:*, isRequired:Boolean = true):Boolean 132 { 133 return encodeSequence(definition, parent, name, value, isRequired); 134 } 135 136 137 138 /** 139 * Encodes any complex object values as attributes using the XML schema 140 * rules for attribute wildcards. 141 * 142 * FIXME: This needs further investigation of the XML schema spec for 143 * wildcard rules and constraints. 144 * 145 * @private 146 */ 147 public function encodeAnyAttribute(definition:XML, parent:XML, name:QName, value:* = undefined, restriction:XML = null):void 148 { 149 // FIXME: honor restrictions for attributes 150 151 if (value !== undefined) 152 { 153 if (!isSimpleValue(value) && !(value is Array)) 154 { 155 // FIXME: De we consider namespace filter attributes for 156 // encoding? Also consider skipping reserved attribute names 157 // like xmlns etc? What about non-public namespace properties? 158 for (var propertyName:Object in getProperties(value)) 159 { 160 // Only add wildcard attributes if property has not already been added 161 if (!hasAttribute(value, propertyName) && !hasValue(value, propertyName)) 162 { 163 var attributeValue:* = getAttribute(value, propertyName); 164 165 if (attributeValue != null) 166 setAttribute(parent, propertyName, attributeValue); 167 } 168 } 169 } 170 } 171 } 172 173 174 175 /** 176 * Encodes elements based on wildcard rules. 177 * 178 * Any content: 179 * (annotation?) 180 * @private 181 * 182 */ 183 public function encodeAnyElement(definition:XML, siblings:XMLList, name:QName, value:*, isRequired:Boolean=true, encodedVals:Dictionary=null):Boolean 184 { 185 // encodeAnyElement is never called with null value 186// if (value == null) 187// return false; 188 189 var maxOccurs:uint = getMaxOccurs(definition); 190 var minOccurs:uint = getMinOccurs(definition); 191 192 if (isSimpleValue(value)) 193 { 194 var item:XML = createElement(name); 195 setValue(item, value); 196 appendValue(siblings, item); 197 } 198 else if (value is XML || value is XMLList) 199 { 200 // If we have XML or XMLList, just append it to the siblings list. 201 appendValue(siblings, value); 202 } 203 else 204 { 205 // We keep a dictionary of values we have encountered on this stack 206 // in order to detect cyclic references. 207 if (encodedVals == null) 208 encodedVals = new Dictionary(true); 209 210 // Work around AS problem with QName values in Dictionary. Since 211 // QNames cannot possibly create cyclic references, it's OK not to 212 // keep track of them. 213 if (!(value is QName)) 214 { 215 if (encodedVals[value] != null) 216 throw new Error("Cannot encode complex structure. Cyclic references detected."); 217 encodedVals[value] = true; 218 } 219 220 if (value is Array || value is IList) 221 { 222 // FIXME: Check for maxOccurs and minOccurs. 223 if (value is IList) 224 value = IList(value).toArray(); 225 226 for each (var arrValue:* in value as Array) 227 { 228 // Since this is an array, we don't need check for nillable 229 // or isRequired. We must always create a node to preserve 230 // array indexes. 231 var arrayItem:* = createElement(name); 232 233 if (arrValue == null) 234 { 235 // Directly set xsi:nil 236 setValue(arrayItem, null); 237 } 238 else if (arrValue != null) 239 { 240 var arrayChildren:XMLList = new XMLList(); 241 encodeAnyElement(definition, arrayChildren, name, arrValue, isRequired, encodedVals); 242 if (isSimpleValue(arrValue)) 243 { 244 // Don't double wrap simple values. 245 arrayItem = arrayChildren[0]; 246 } 247 else 248 { 249 setValue(arrayItem, arrayChildren); 250 } 251 } 252 appendValue(siblings, arrayItem); 253 } 254 } 255 else 256 { 257 for each (var objProperty:Object in getProperties(value)) 258 { 259 var propValue:* = getValue(value, objProperty); 260 var propQName:QName = new QName(name.uri, objProperty); 261 262 // Only encode if property hasn't been encoded yet. 263 // FIXME: When coming from a group context, if the any element 264 // is not last in the definition, we will end up encoding subsequent 265 // named elements twice. We need all sibling names from the definition 266 // of the group to properly do this. 267 if (!containsNodeByName(siblings, propQName)) 268 { 269 var propItem:XML = encodeXSINil(definition, propQName, propValue); 270 271 if (propItem != null) 272 { 273 appendValue(siblings, propItem); 274 } 275 else if (propValue != null) 276 { 277 var propChildren:XMLList = new XMLList(); 278 encodeAnyElement(definition, propChildren, propQName, propValue, isRequired, encodedVals); 279 appendValue(siblings, propChildren); 280 } 281 } 282 } 283 284 } 285 delete encodedVals[value]; 286 } 287 288 // FIXME: figure out isRequired 289 return true; 290 } 291 292 293 /** 294 * An attribute must be based on a simple type and thus will have simple 295 * content encoded as a String. 296 * 297 * This function is used to encode an <code>attribute</code> that may be 298 * named and registered as a top-level <code>schema</code> definition or 299 * in-line from a <code>complexType</code>, <code>extension</code> or 300 * <restriction> of either a <code>complexType</code> or 301 * <code>simpleType</code>, or <code>attributeGroup</code> 302 * definition in any aforementioned parent component. 303 * 304 * If the <code>attribute</code> points to a named definition using a 305 * <code>ref</code> attribute, the reference is resolved to provide the 306 * real definition of the attribute. If the reference cannot be resolved, 307 * an error is thrown. 308 * 309 * If the attribute defines a <code>fixed</code> constraint then any value 310 * provided is ignored and the fixed value is used instead. If a value is 311 * not provided and the attribute defines a <code>default</code>, the 312 * default is used for the encoded attribute. Otherwise if an attribute is 313 * marked as <code>optional</code> and a value is not provided it will be 314 * skipped. 315 * 316 * @param parent The parent instance to which these attributes will be added. 317 * @param definition The XML schema definition of the attribute. 318 * @param value An object with a property name that matches the resolved 319 * attribute name. The property value will be used as the encoded attribute 320 * value. 321 * 322 * FIXME: Attributes are expected to be simple values and must be ultimately 323 * representable as a String. If a complex value is passed to this method 324 * should we assume that we're always looking for a property with the same 325 * name as the attribute? We may need to because if we have a ref then the 326 * name is not known immediately... 327 * 328 * @private 329 */ 330 public function encodeAttribute(definition:XML, parent:XML, name:QName, value:* = undefined, restriction:XML = null):void 331 { 332 // <attribute ref="..."> may be used to point to a top-level attribute definition 333 var ref:QName; 334 if (definition.attribute("ref").length() == 1) 335 { 336 ref = schemaManager.getQNameForPrefixedName(definition.@ref, definition, true); 337 definition = schemaManager.getNamedDefinition(ref, constants.attributeQName); 338 339 if (definition == null) 340 throw new Error("Cannot resolve attribute definition for '" + ref + "'"); 341 } 342 343 // FIXME: Check restriction for prohibited attribute definitions too 344 var attributeNameString:String = definition.@name.toString(); 345 var attributeUse:String = definition.attribute("use").toString(); 346 if (attributeUse != "prohibited") 347 { 348 var attributeName:QName = schemaManager.getQNameForAttribute(attributeNameString, getAttributeFromNode("form", definition)); 349 350 var attributeFixed:String = getAttributeFromNode("fixed", definition); 351 if (attributeFixed != null) 352 { 353 value = attributeFixed; 354 } 355 else 356 { 357 value = getValue(value, attributeName); 358 359 if (value === undefined) 360 { 361 var attributeDefault:String = getAttributeFromNode("default", definition); 362 if (attributeDefault != null) 363 value = attributeDefault; 364 } 365 } 366 367 var tempElement:*; 368 var attributeFound:Boolean; 369 if (value !== undefined) 370 { 371 var typeDefinition:XML; 372 // we just need a temporary wrapper to pass down as 373 // the parent XML for the encodeSimpleType calls 374 tempElement = <temp/>; 375 376 377 378 // An <attribute> may declare a type="QName" attribute which 379 // refers to either a built-in schema type or a previously 380 // declared <simpleType> 381 var typeName:String = getAttributeFromNode("type", definition); 382 var attributeType:QName; 383 if (typeName != null) 384 attributeType = schemaManager.getQNameForPrefixedName(definition.@type, definition); 385 else 386 attributeType = schemaManager.schemaDatatypes.anySimpleTypeQName; 387 388 if (attributeType != null) 389 { 390 if (isBuiltInType(attributeType)) 391 { 392 tempElement.appendChild(schemaManager.marshall(value, attributeType, restriction)); 393 } 394 else 395 { 396 // <simpleType> 397 typeDefinition = schemaManager.getNamedDefinition(attributeType, constants.simpleTypeQName); 398 if (typeDefinition != null) 399 encodeSimpleType(typeDefinition, tempElement, attributeName, value, restriction); 400 else 401 throw new Error("Cannot find simpleType " + attributeType + " for attribute " + attributeName); 402 403 // Then release the scope after we've found the attribute type 404 schemaManager.releaseScope(); 405 } 406 } 407 else 408 { 409 // Otherwise, an <attribute> may define a single anonymous 410 // <simpleType> child in-line 411 typeDefinition = getSingleElementFromNode(definition, constants.simpleTypeQName); 412 if (typeDefinition != null) 413 { 414 encodeSimpleType(typeDefinition, tempElement, attributeName, value, restriction); 415 } 416 else if (value != null) 417 { 418 // Finally, in the absence of type information we 419 // just get the attribute value as a String without 420 // restriction 421 tempElement.appendChild(value.toString()); 422 } 423 } 424 } 425 426 // FIXME: Should we enforce use="required"? 427 if (tempElement !== undefined) 428 { 429 setAttribute(parent, attributeName, tempElement); 430 } 431 } 432 433 // If we found our attribute by reference, we now release the schema scope 434 if (ref != null) 435 schemaManager.releaseScope(); 436 } 437 438 /** 439 * An <code>attributeGroup</code> definition may include a number of 440 * <code>attribute</code> or <code>attributeGroup</code> children, all of 441 * which ultimately combine to form a flat group of attributes for some 442 * type. It may also specify <code>anyAttribute</code> which expands 443 * the definition to accept attributes based on more general criteria 444 * (such excluding or including attributes on namespace). 445 * 446 * This function is used to encode an <code>attributeGroup</code> that may 447 * be named and registered as a top-level <code>schema</code> definition or 448 * in-line from a <code>complexType</code>, <code>extension</code> or 449 * <restriction> of either a <code>complexType</code> or 450 * <code>simpleType</code>, or even another <code>attributeGroup</code> 451 * definition in any aforementioned parent component. 452 * 453 * If the <code>attributeGroup</code> points to a named definition using a 454 * ref attribute, the reference is resolved to provide the real definition 455 * of the attributeGroup. If the reference cannot be resolved, an error is 456 * thrown. 457 * 458 * @param parent The parent instance to which these attributes will be added. 459 * @param definition The XML schema definition of the attributeGroup. 460 * @param value An object with property names that match the resolved 461 * attribute names in the group. The property values will be used as the 462 * encoded attribute values. This argument may be omitted if each attribute 463 * in the group has a fixed or default value. 464 * 465 * @private 466 */ 467 public function encodeAttributeGroup(definition:XML, parent:XML, name:QName, value:* = undefined, restriction:XML = null):void 468 { 469 // <attributeGroup ref="..."> may be used to point to a top-level 470 // attributeGroup definition which must first be resolved. 471 var ref:QName; 472 if (definition.attribute("ref").length() == 1) 473 { 474 ref = schemaManager.getQNameForPrefixedName(definition.@ref, definition, true); 475 definition = schemaManager.getNamedDefinition(ref, constants.attributeGroupQName); 476 477 if (definition == null) 478 throw new Error("Cannot resolve attributeGroup definition for '" + ref + "'"); 479 } 480 481 // <attribute> 482 var attributes:XMLList = definition.elements(constants.attributeQName); 483 for each (var attributeDefinition:XML in attributes) 484 { 485 encodeAttribute(attributeDefinition, parent, name, value, restriction); 486 } 487 488 // <attributeGroup> 489 var attributeGroups:XMLList = definition.elements(constants.attributeGroupQName); 490 for each (var attributeGroup:XML in attributeGroups) 491 { 492 encodeAttributeGroup(attributeGroup, parent, name, value, restriction); 493 } 494 495 // <anyAttribute> 496 var anyAttribute:XML = getSingleElementFromNode(definition, constants.anyAttributeQName); 497 if (anyAttribute != null) 498 { 499 encodeAnyAttribute(anyAttribute, parent, name, value, restriction); 500 } 501 502 // If we found our attributeGroup by reference, we now release the schema scope 503 if (ref != null) 504 schemaManager.releaseScope(); 505 } 506 507 /** 508 * choice: 509 * (annotation?, (element | group | choice | sequence | any)*) 510 * 511 * @private 512 */ 513 public function encodeChoice(definition:XML, parent:XMLList, name:QName, value:*, isRequired:Boolean = true):Boolean 514 { 515 var maxOccurs:uint = getMaxOccurs(definition); 516 var minOccurs:uint = getMinOccurs(definition); 517 518 // If maxOccurs is 0 this choice must not be present. 519 // If minOccurs == 0 the choice is optional so it can be omitted if 520 // a value was not provided. 521 if (maxOccurs == 0) 522 return false; 523 if (value == null && minOccurs == 0) 524 return true; 525 526 var choiceElements:XMLList = definition.elements(); 527 var choiceSatisfied:Boolean = true; 528 var lastIndex:uint; 529 var choiceOccurs:uint; 530 531 // We don't enforce occurs bounds on the choice element itself. Since all 532 // child elements of the choice definition would be properties on the 533 // value object, simply looping through the choice children once would 534 // encode the values that apply to each of the child elements. 535 536 // An empty choice is satisfied by default, but if there are choiceElements 537 // we need to start out with choiceSatisfied = false 538 if (choiceElements.length() > 0) 539 choiceSatisfied = false; 540 541 for each (var childDefinition:XML in choiceElements) 542 { 543 if (childDefinition.name() == constants.elementTypeQName) 544 { 545 // <element> 546 choiceSatisfied = encodeGroupElement(childDefinition, parent, 547 name, value, false) || choiceSatisfied; 548 } 549 else if (childDefinition.name() == constants.sequenceQName) 550 { 551 // <sequence> 552 choiceSatisfied = encodeSequence(childDefinition, parent, 553 name, value, false) || choiceSatisfied; 554 } 555 else if (childDefinition.name() == constants.groupQName) 556 { 557 // <group> 558 choiceSatisfied = encodeGroupReference(childDefinition, parent, 559 name, value, false) || choiceSatisfied; 560 } 561 else if (childDefinition.name() == constants.choiceQName) 562 { 563 // <choice> 564 choiceSatisfied = encodeChoice(childDefinition, parent, 565 name, value, false) || choiceSatisfied; 566 } 567 else if (childDefinition.name() == constants.anyQName) 568 { 569 // <any> 570 choiceSatisfied = encodeAnyElement(childDefinition, parent, 571 name, value, false) || choiceSatisfied; 572 } 573 } 574 575 return choiceSatisfied; 576 } 577 578 579 /** 580 * Derivation by restriction takes an existing type as the base and creates 581 * a new type by limiting its allowed content to a subset of that allowed 582 * by the base type. Derivation by extension takes an existing type as the 583 * base and creates a new type by adding to its allowed content. 584 * 585 * complexContent: 586 * (annotation?, (restriction | extension)) 587 * 588 * @private 589 */ 590 public function encodeComplexContent(definition:XML, parent:XML, name:QName, value:*):void 591 { 592 var childDefinition:XML = getSingleElementFromNode(definition, constants.extensionQName, constants.restrictionQName); 593 594 if (childDefinition.name() == constants.extensionQName) 595 { 596 encodeComplexExtension(childDefinition, parent, name, value); 597 } 598 else if (childDefinition.name() == constants.restrictionQName) 599 { 600 encodeComplexRestriction(childDefinition, parent, name, value); 601 } 602 } 603 604 605 /** 606 * complexContent: 607 * extension: 608 * (annotation?, ((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?), (assert | report)*)) 609 * 610 * @private 611 */ 612 public function encodeComplexExtension(definition:XML, parent:XML, name:QName, value:*):void 613 { 614 var baseName:String = getAttributeFromNode("base", definition); 615 if (baseName == null) 616 throw new Error ("A complexContent extension must declare a base type."); 617 618 var baseType:QName = schemaManager.getQNameForPrefixedName(baseName, definition); 619 620 // complexContent base type must be a complexType 621 var baseDefinition:XML = schemaManager.getNamedDefinition(baseType, constants.complexTypeQName); 622 if (baseDefinition == null) 623 throw new Error("Cannot find base type definition '" + baseType + "'"); 624 625 // FIXME: Should we care if base type is marked final? 626 627 // First encode all of the properties of the base type 628 encodeComplexType(baseDefinition, parent, name, value); 629 630 // Then release the scope of the base type definition 631 schemaManager.releaseScope(); 632 633 var childDefinitions:XMLList = definition.elements(); 634 635 // Start a separate XMLList for the child elements defined in this extension. 636 // Extension attributes are still encoded directly on the parent. 637 var extChildren:XMLList = new XMLList(); 638 for each (var childDefinition:XML in childDefinitions) 639 { 640 if (childDefinition.name() == constants.sequenceQName) 641 { 642 // <sequence> 643 encodeSequence(childDefinition, extChildren, name, value); 644 } 645 else if (childDefinition.name() == constants.groupQName) 646 { 647 // <group> 648 encodeGroupReference(childDefinition, extChildren, name, value); 649 } 650 else if (childDefinition.name() == constants.allQName) 651 { 652 // <all> 653 encodeAll(childDefinition, extChildren, name, value); 654 } 655 else if (childDefinition.name() == constants.choiceQName) 656 { 657 // <choice> 658 encodeChoice(childDefinition, extChildren, name, value); 659 } 660 else if (childDefinition.name() == constants.attributeQName) 661 { 662 // <attribute> 663 encodeAttribute(childDefinition, parent, name, value); 664 } 665 else if (childDefinition.name() == constants.attributeGroupQName) 666 { 667 // <attributeGroup> 668 encodeAttributeGroup(childDefinition, parent, name, value); 669 } 670 else if (childDefinition.name() == constants.anyAttributeQName) 671 { 672 // <anyAttribute> 673 encodeAnyAttribute(childDefinition, parent, name, value); 674 } 675 } 676 677 // We need to add the extension elements to the parent node. However, 678 // we need to handle the case where a value fits both the base and the 679 // extension definitions (strictly speaking that's illegal schema, but 680 // it's used in some cases). We need to keep the values encoded with the 681 // extension definition, so we delete any values with the same names that 682 // we got from encoding the base definition. 683 for each (var extension:XML in extChildren) 684 { 685 // Delete anything already encoded during base type processing, which 686 // matches the full qualified name of this extension element. 687 delete parent[extension.name()]; 688 // Also delete unqualified elements with the same local name, since 689 // <any> in the base definition would encode with local names only. 690 delete parent[new QName("", extension.name().localName)]; 691 delete parent[new QName(null, extension.name().localName)]; 692 } 693 setValue(parent, extChildren); 694 } 695 696 /** 697 * complexContent: 698 * restriction: 699 * (annotation?, (group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?), (assert | report)*) 700 * 701 * @private 702 */ 703 public function encodeComplexRestriction(restriction:XML, parent:XML, name:QName, value:*):void 704 { 705 var baseName:String = getAttributeFromNode("base", restriction); 706 if (baseName == null) 707 throw new Error ("A complexContent restriction must declare a base type."); 708 709 var baseType:QName = schemaManager.getQNameForPrefixedName(baseName, restriction); 710 711 // FIXME: Validate complex restriction based on the base type definition 712 // complexContent base type must be a complexType 713 // var baseDefinition:XML = schemaManager.getNamedDefinition(baseType, constants.complexTypeQName); 714 // if (baseDefinition == null) 715 // throw new Error("Cannot find base type definition '" + baseType + "'"); 716 717 // FIXME: Should we care if base type is marked final? 718 719 var childDefinitions:XMLList = restriction.elements(); 720 var children:XMLList = parent.elements(); 721 for each (var childDefinition:XML in childDefinitions) 722 { 723 if (childDefinition.name() == constants.sequenceQName) 724 { 725 // <sequence> 726 encodeSequence(childDefinition, children, name, value); 727 } 728 else if (childDefinition.name() == constants.groupQName) 729 { 730 // <group> 731 encodeGroupReference(childDefinition, children, name, value); 732 } 733 else if (childDefinition.name() == constants.allQName) 734 { 735 // <all> 736 encodeAll(childDefinition, children, name, value); 737 } 738 else if (childDefinition.name() == constants.choiceQName) 739 { 740 // <choice> 741 encodeChoice(childDefinition, children, name, value); 742 } 743 else if (childDefinition.name() == constants.attributeQName) 744 { 745 // <attribute> 746 encodeAttribute(childDefinition, parent, name, value, restriction); 747 } 748 else if (childDefinition.name() == constants.attributeGroupQName) 749 { 750 // <attributeGroup> 751 encodeAttributeGroup(childDefinition, parent, name, value, restriction); 752 } 753 else if (childDefinition.name() == constants.anyAttributeQName) 754 { 755 // <anyAttribute> 756 encodeAnyAttribute(childDefinition, parent, name, value, restriction); 757 } 758 } 759 parent.setChildren(children); 760 } 761 762 public function encodeComplexType(definition:XML, parent:XML, name:QName, value:*, restriction:XML = null):void 763 { 764 var childElements:XMLList = definition.elements(); 765 766 var children:XMLList = new XMLList(); 767 // FIXME: Investigate if we need to support "base" attribute on 768 // complexType as short-cut as seen in some examples... 769 770 for each (var childDefinition:XML in childElements) 771 { 772 if (childDefinition.name() == constants.sequenceQName) 773 { 774 // <sequence> 775 encodeSequence(childDefinition, children, name, value); 776 } 777 else if (childDefinition.name() == constants.simpleContentQName) 778 { 779 // <simpleContent> 780 encodeSimpleContent(childDefinition, parent, name, value, restriction); 781 } 782 else if (childDefinition.name() == constants.complexContentQName) 783 { 784 // <complexContent> 785 encodeComplexContent(childDefinition, parent, name, value); 786 } 787 else if (childDefinition.name() == constants.groupQName) 788 { 789 // <group> 790 encodeGroupReference(childDefinition, children, name, value); 791 } 792 else if (childDefinition.name() == constants.allQName) 793 { 794 // <all> 795 encodeAll(childDefinition, children, name, value); 796 } 797 else if (childDefinition.name() == constants.choiceQName) 798 { 799 // <choice> 800 encodeChoice(childDefinition, children, name, value); 801 } 802 else if (childDefinition.name() == constants.attributeQName) 803 { 804 // <attribute> 805 encodeAttribute(childDefinition, parent, name, value, restriction); 806 } 807 else if (childDefinition.name() == constants.attributeGroupQName) 808 { 809 // <attributeGroup> 810 encodeAttributeGroup(childDefinition, parent, name, value, restriction); 811 } 812 else if (childDefinition.name() == constants.anyAttributeQName) 813 { 814 // <anyAttribute> 815 encodeAnyAttribute(childDefinition, parent, name, value, restriction); 816 } 817 } 818 setValue(parent, children); 819 } 820 821 822 /** 823 * Used to encode a local element definition (inside a model group). 824 * Handles restrictions on omittability and occurence counts in the 825 * context of the parent model group. 826 * Delegates actual encoding to encodeElementTopLevel once all the 827 * context around the element is known. 828 * 829 * @param definition The XML Schema definition of the local element. 830 * @param parent The XMLList of values encoded in the current level. The 831 * new encoded node should be appended to this XMLList. 832 * @param name The QName to be used for the encoded XML node. 833 * @param value The ActionScript value to encode as XML. 834 * @param isRequired A flag indicating wether the element should meet 835 * its local occurence bounds. For example, the local element may have 836 * minOccurs=1, but be only one of many elements in a choice group, in 837 * which case it is valid not to satisfy the minOccurs requirement. 838 * 839 * @return Wether or not the value provided 840 * 841 * FIXME: Support substitutionGroup, block and redefine? 842 * FIXME: Do we care about abstract or final? 843 * @private 844 */ 845 public function encodeGroupElement(definition:XML, siblings:XMLList, name:QName, value:*, isRequired:Boolean = true):Boolean 846 { 847 // <element minOccurs="..." maxOccurs="..."> occur on the local element, 848 // not on a referent, so we capture this information first. 849 var maxOccurs:uint = getMaxOccurs(definition); 850 var minOccurs:uint = getMinOccurs(definition); 851 852 // If the maximum occurence is 0 this element must not be present. 853 if (maxOccurs == 0) 854 return true; 855 856 isRequired = isRequired && minOccurs > 0; 857 858 // <element ref="..."> may be used to point to a top-level element definition 859 var ref:QName; 860 if (definition.attribute("ref").length() == 1) 861 { 862 ref = schemaManager.getQNameForPrefixedName(definition.@ref, definition, true); 863 definition = schemaManager.getNamedDefinition(ref, constants.elementTypeQName); 864 if (definition == null) 865 throw new Error("Cannot resolve element definition for ref '" + ref + "'"); 866 } 867 868 var elementName:String = definition.@name.toString(); 869 var elementQName:QName = schemaManager.getQNameForElement(elementName, getAttributeFromNode("form", definition)); 870 871 // Now that we've resolved the real element name, look for the element's 872 // value on the provided value. 873 var elementValue:* = getValue(value, elementQName); 874 var encodedElement:XML; 875 876 // If minOccurs == 0 the element is optional so we can omit it if 877 // a value was not provided. 878 if (elementValue == null) 879 { 880 encodedElement = encodeElementTopLevel(definition, elementQName, elementValue); 881 if (encodedElement != null) 882 appendValue(siblings, encodedElement); 883 884 // If we found our element by reference, we now release the schema scope 885 if (ref != null) 886 schemaManager.releaseScope(); 887 888 // if required, but no value was encoded, the definition is not 889 // satisfied 890 if (isRequired && encodedElement == null) 891 return false; 892 893 // Otherwise we can return true 894 return true; 895 } 896 897 // We treat maxOccurs="1" as a special case and not check the 898 // occurence because we need to pass through values to SOAP 899 // encoded Arrays which do not rely on minOccurs/maxOccurs 900 if (maxOccurs == 1) 901 { 902 encodedElement = encodeElementTopLevel(definition, elementQName, elementValue); 903 if (encodedElement != null) 904 { 905 appendValue(siblings, encodedElement); 906 } 907 // ...else we just skip the element as a value wasn't provided. 908 } 909 else if (maxOccurs > 1) 910 { 911 var valueOccurence:uint = getValueOccurence(elementValue); 912 913 // If maxOccurs is greater than 1 then we would expect an 914 // Array of values 915 if (valueOccurence < minOccurs) 916 { 917 throw new Error("Value supplied for element '" + elementQName + 918 "' occurs " + valueOccurence + " times which falls short of minOccurs " + 919 minOccurs + "."); 920 } 921 922 if (valueOccurence > maxOccurs) 923 { 924 throw new Error("Value supplied for element of type '" + elementQName + 925 "' occurs " + valueOccurence + " times which exceeds maxOccurs " + 926 maxOccurs + "."); 927 } 928 929 // Promote non-iterable values to an Array to handle the MXML 930 // single-child property case where the compiler doesn't promote 931 // a property to an Array until two items are present. 932 if (!TypeIterator.isIterable(elementValue)) 933 elementValue = [elementValue]; 934 935 // Encode element based on occurence within the bounds of 936 // minOccurs and maxOccurs 937 var iter:TypeIterator = new TypeIterator(elementValue); 938 939 for (var i:uint = 0; i < maxOccurs && i < valueOccurence; i++) 940 { 941 var item:*; 942 if (iter.hasNext()) 943 { 944 item = iter.next(); 945 } 946 else if (i > minOccurs) 947 { 948 break; 949 } 950 951 encodedElement = encodeElementTopLevel(definition, elementQName, item); 952 // encodedElement is null if encodeXSINil inside encodeElementTopLevel 953 // was not allowed to create element with xsi:nil for a null or undefined 954 // value. We must still force xsi:nil, because we are encoding an array 955 // and we need to preserve the index. 956 if (encodedElement == null) 957 { 958 encodedElement = createElement(elementQName); 959 setValue(encodedElement, null); 960 } 961 appendValue(siblings, encodedElement); 962 } 963 } 964 965 // If we found our element by reference, we now release the schema scope 966 if (ref != null) 967 schemaManager.releaseScope(); 968 969 return true; 970 } 971 972 /** 973 * Element content: 974 * (annotation?, ((simpleType | complexType)?, (unique | key | keyref)*)) 975 * 976 * @private 977 */ 978 public function encodeElementTopLevel(definition:XML, elementQName:QName, value:*):XML 979 { 980 // Let encodeXSINil create an element if null, fixed, or default value 981 // must be used. 982 var element:XML = encodeXSINil(definition, elementQName, value); 983 984 // If ecnodeXSINil created an element, we are done. 985 if (element != null) 986 return element; 987 // If value was null, but element wasn't created, it must be omitted. 988 else if (value == null) 989 return null; 990 991 // Otherwise, just create the element with the given QName. This starts off 992 // a new tree of encoded values with this top level element as the root. 993 element = createElement(elementQName); 994 995 // Check for a simple def first, falling back to complex type handling 996 // and then default handling. 997 var typeAttribute:String = getAttributeFromNode("type", definition); 998 if (typeAttribute != null) 999 { 1000 var typeQName:QName = schemaManager.getQNameForPrefixedName(typeAttribute, definition); 1001 encodeType(typeQName, element, elementQName, value); 1002 } 1003 // Next, check if the element has an in-line <complexType> or 1004 // <simpleType> definition. 1005 else if (definition != null && definition.hasComplexContent()) 1006 { 1007 var typeDefinition:XML = getSingleElementFromNode(definition, 1008 constants.complexTypeQName, 1009 constants.simpleTypeQName); 1010 1011 if (typeDefinition.name() == constants.complexTypeQName) 1012 { 1013 // <complexType> 1014 encodeComplexType(typeDefinition, element, elementQName, value); 1015 } 1016 else if (typeDefinition.name() == constants.simpleTypeQName) 1017 { 1018 // <simpleType> 1019 encodeSimpleType(typeDefinition, element, elementQName, value); 1020 } 1021 1022 // FIXME: Support unique, key, keyref, field, selector 1023 } 1024 else 1025 { 1026 // FIXME: Support <element substitutionGroup="..."> 1027 encodeType(constants.anyTypeQName, element, elementQName, value); 1028 } 1029 return element; 1030 } 1031 1032 /** 1033 * The <code>group</code> element allows partial (or complete) content 1034 * models to be reused in complex types. When used inside a choice, 1035 * sequence, complexType, extension or restriction element, it must 1036 * have a ref attribute, specifying the name of a global definition 1037 * of a named model group. 1038 * 1039 * group: 1040 * (annotation?, (all | choice | sequence)?) 1041 * 1042 * @private 1043 */ 1044 public function encodeGroupReference(definition:XML, parent:XMLList, name:QName, value:*, isRequired:Boolean = true):Boolean 1045 { 1046 // <group ref="..."> must be used to point to a top-level group definition 1047 var ref:QName; 1048 if (definition.attribute("ref").length() == 1) 1049 { 1050 ref = schemaManager.getQNameForPrefixedName(definition.@ref, definition, true); 1051 definition = schemaManager.getNamedDefinition(ref, constants.groupQName); 1052 1053 if (definition == null) 1054 throw new Error("Cannot resolve group definition for '" + ref + "'"); 1055 } 1056 else 1057 { 1058 throw new Error("A group reference element must have the ref attribute"); 1059 } 1060 1061 var groupElements:XMLList = definition.elements(); 1062 var groupSatisfied:Boolean = false; 1063 for each (var childDefinition:XML in groupElements) 1064 { 1065 if (childDefinition.name() == constants.sequenceQName) 1066 { 1067 // <sequence> 1068 groupSatisfied = encodeSequence(childDefinition, parent, name, value, isRequired); 1069 } 1070 else if (childDefinition.name() == constants.allQName) 1071 { 1072 // <all> 1073 groupSatisfied = encodeAll(childDefinition, parent, name, value, isRequired); 1074 } 1075 else if (childDefinition.name() == constants.choiceQName) 1076 { 1077 // <choice> 1078 groupSatisfied = encodeChoice(childDefinition, parent, name, value, isRequired); 1079 } 1080 } 1081 // We found our group by reference, we now release the schema scope 1082 schemaManager.releaseScope(); 1083 return groupSatisfied; 1084 } 1085 1086 /** 1087 * sequence: 1088 * (annotation?, (element | group | choice | sequence | any)*) 1089 * 1090 * @private 1091 */ 1092 public function encodeSequence(definition:XML, siblings:XMLList, name:QName, value:*, isRequired:Boolean=true):Boolean 1093 { 1094 var maxOccurs:uint = getMaxOccurs(definition); 1095 var minOccurs:uint = getMinOccurs(definition); 1096 1097 // If maxOccurs is 0 this sequence must not be present. 1098 // If minOccurs == 0 the sequence is optional so it can be omitted if 1099 // a value was not provided. 1100 if (maxOccurs == 0) 1101 return true; 1102 if (value == null && minOccurs == 0) 1103 return true; 1104 1105 // Note that we can't enforce occurence count on the sequence element 1106 // itself. Since the value is an ActionScript object, any named element 1107 // in the sequence should correspond to a named property on the object. 1108 1109 var sequenceElements:XMLList = definition.elements(); 1110 // We loop through the children of the sequence definition. We require 1111 // all child definitions to be satisfied, unless the sequence itself 1112 // doesn't need to be satisfied. 1113 var requireChild:Boolean = isRequired && minOccurs > 0; 1114 var sequenceSatisfied:Boolean = true; 1115 1116 for each (var childDefinition:XML in sequenceElements) 1117 { 1118 sequenceSatisfied = false; 1119 if (childDefinition.name() == constants.elementTypeQName) 1120 { 1121 // <element> 1122 if (!encodeGroupElement(childDefinition, siblings, name, value, isRequired)) 1123 break; 1124 } 1125 else if (childDefinition.name() == constants.groupQName) 1126 { 1127 // <group> 1128 if (!encodeGroupReference(childDefinition, siblings, name, value, isRequired)) 1129 break; 1130 } 1131 else if (childDefinition.name() == constants.choiceQName) 1132 { 1133 // <choice> 1134 if (!encodeChoice(childDefinition, siblings, name, value, isRequired)) 1135 break; 1136 } 1137 else if (childDefinition.name() == constants.sequenceQName) 1138 { 1139 // <sequence> 1140 if (!encodeSequence(childDefinition, siblings, name, value, isRequired)) 1141 break; 1142 } 1143 else if (childDefinition.name() == constants.anyQName) 1144 { 1145 // <any> 1146 if (!encodeAnyElement(childDefinition, siblings, name, value, isRequired)) 1147 break; 1148 } 1149 sequenceSatisfied = true; 1150 } 1151 1152 return sequenceSatisfied || !isRequired; 1153 } 1154 1155 1156 1157 /** 1158 * <code>simpleContent</code> specifies that the content will be simple text 1159 * only, that is it conforms to a simple type and will not contain elements, 1160 * although it may also define attributes. 1161 * 1162 * A simpleContent must be defined with an extension or a restriction. An 1163 * extension specifies the attribute definitions that are to be added to the 1164 * type and the base attribute specifies from which simple data type this 1165 * custom type is defined. A restriction for simpleContent is less common, 1166 * although it may be used to prohibit attributes in derived types also 1167 * with simpleContent. 1168 * 1169 * simpleContent 1170 * (annotation?, (restriction | extension)) 1171 * 1172 * @private 1173 */ 1174 public function encodeSimpleContent(definition:XML, parent:XML, name:QName, value:*, restriction:XML = null):void 1175 { 1176 var childDefinition:XML = getSingleElementFromNode(definition, constants.extensionQName, constants.restrictionQName); 1177 1178 if (childDefinition != null) 1179 { 1180 var baseName:String = getAttributeFromNode("base", childDefinition); 1181 if (baseName == null) 1182 throw new Error ("A simpleContent extension or restriction must declare a base type."); 1183 1184 var baseType:QName = schemaManager.getQNameForPrefixedName(baseName, childDefinition); 1185 1186 if (!isBuiltInType(baseType)) 1187 { 1188 var baseDefinition:XML = schemaManager.getNamedDefinition(baseType, 1189 constants.complexTypeQName, constants.simpleTypeQName); 1190 if (baseDefinition == null) 1191 throw new Error("Cannot find base type definition '" + baseType + "'"); 1192 1193 // We found our baseType by name so we now release the schema scope 1194 schemaManager.releaseScope(); 1195 } 1196 1197 // FIXME: Should we care if base type is marked final? 1198 var simpleValue:*; 1199 1200 // <extension> 1201 if (childDefinition.name() == constants.extensionQName) 1202 { 1203 // simpleContent base type must be a simpleType or a complexType 1204 // that ultimately has simpleContent (FIXME: we currently don't verify the latter) 1205 if (isBuiltInType(baseType)) 1206 { 1207 simpleValue = getSimpleValue(value, name); 1208 setValue(parent, schemaManager.marshall(simpleValue, baseType, restriction)); 1209 } 1210 else 1211 { 1212 encodeType(baseType, parent, value, restriction); 1213 } 1214 1215 var extensions:XMLList = childDefinition.elements(); 1216 for each (var extensionChild:XML in extensions) 1217 { 1218 if (extensionChild.name() == constants.attributeQName) 1219 { 1220 // <attribute> 1221 encodeAttribute(extensionChild, parent, name, value, restriction); 1222 } 1223 else if (extensionChild.name() == constants.attributeGroupQName) 1224 { 1225 // <attributeGroup> 1226 encodeAttributeGroup(extensionChild, parent, name, value, restriction); 1227 } 1228 else if (extensionChild.name() == constants.anyAttributeQName) 1229 { 1230 // <anyAttribute> 1231 encodeAnyAttribute(extensionChild, parent, name, value, restriction); 1232 } 1233 } 1234 } 1235 // <restriction> 1236 else if (childDefinition.name() == constants.restrictionQName) 1237 { 1238 simpleValue = getSimpleValue(value, name); 1239 encodeSimpleRestriction(childDefinition, parent, name, simpleValue); 1240 } 1241 } 1242 } 1243 1244 /** 1245 * A <code>simpleType</code> may declare a list of space separated 1246 * simple content for a single value. 1247 * 1248 * <list 1249 * id = ID 1250 * itemType = QName > 1251 * Content: (annotation?, simpleType?) 1252 * </list> 1253 * 1254 * @private 1255 */ 1256 public function encodeSimpleList(definition:XML, parent:XML, name:QName, value:*, restriction:XML = null):void 1257 { 1258 var itemTypeAttribute:String = definition.@itemType; 1259 1260 var itemTypeQName:QName; 1261 var itemDefinition:XML; 1262 1263 // The simple type of each item in the list can be specified by 1264 // either an itemType attribute, or a simpleType inline definition. 1265 if (itemTypeAttribute != "") 1266 itemTypeQName = schemaManager.getQNameForPrefixedName(itemTypeAttribute, definition); 1267 else 1268 itemDefinition = getSingleElementFromNode(definition, constants.simpleTypeQName); 1269 1270 var listValue:String = ""; 1271 1272 if (!TypeIterator.isIterable(value)) 1273 value = [value]; 1274 1275 var iter:TypeIterator = new TypeIterator(value); 1276 while (iter.hasNext()) 1277 { 1278 var item:* = iter.next(); 1279 var tempElement:* = <temp/>; 1280 1281 // Lists cannot encode null values, since separators are collapsed. 1282 if (item == null) 1283 continue; 1284 1285 if (itemTypeQName != null) 1286 encodeType(itemTypeQName, tempElement, name, item, restriction); 1287 else 1288 encodeSimpleType(itemDefinition, tempElement, name, item, restriction); 1289 1290 listValue = listValue.concat(tempElement.toString()); 1291 if (iter.hasNext()) 1292 listValue = listValue.concat(" "); 1293 } 1294 1295 setValue(parent, listValue); 1296 } 1297 1298 /** 1299 * simpleType: 1300 * restriction: (annotation?, (simpleType?, 1301 * (minExclusive | minInclusive | maxExclusive | maxInclusive | 1302 * totalDigits | fractionDigits | maxScale | minScale | length | 1303 * minLength | maxLength | enumeration | whiteSpace | pattern)*)) 1304 * 1305 * @private 1306 */ 1307 public function encodeSimpleRestriction(restriction:XML, parent:XML, name:QName, value:*):void 1308 { 1309 var simpleTypeDefinition:XML = getSingleElementFromNode(restriction, constants.simpleTypeQName); 1310 if (simpleTypeDefinition != null) 1311 { 1312 encodeSimpleType(simpleTypeDefinition, parent, name, value, restriction); 1313 } 1314 else 1315 { 1316 var baseName:String = getAttributeFromNode("base", restriction); 1317 var baseType:QName = schemaManager.getQNameForPrefixedName(baseName, restriction); 1318 1319 // FIXME: handle anyType 1320 encodeType(baseType, parent, name, value, restriction); 1321 } 1322 } 1323 1324 /** 1325 * <simpleType 1326 * final = (#all | List of (list | union | restriction | extension)) 1327 * id = ID 1328 * name = NCName> 1329 * Content: (annotation?, (restriction | list | union)) 1330 * </simpleType> 1331 * 1332 * @private 1333 */ 1334 public function encodeSimpleType(definition:XML, parent:XML, name:QName, value:*, restriction:XML = null):void 1335 { 1336 var definitionChild:XML = getSingleElementFromNode(definition, 1337 constants.restrictionQName, 1338 constants.listQName, 1339 constants.unionQName); 1340 1341 if (definitionChild.name() == constants.restrictionQName) 1342 { 1343 // <restriction> 1344 encodeSimpleRestriction(definitionChild, parent, name, value); 1345 } 1346 else if (definitionChild.name() == constants.listQName) 1347 { 1348 // <list> 1349 encodeSimpleList(definitionChild, parent, name, value, restriction); 1350 } 1351 else if (definitionChild.name() == constants.listQName) 1352 { 1353 // <union> 1354 encodeSimpleUnion(definitionChild, parent, name, value, restriction); 1355 } 1356 } 1357 1358 /** 1359 * <union 1360 * id = ID 1361 * memberTypes = List of QName > 1362 * Content: (annotation?, simpleType*) 1363 * </union> 1364 * 1365 * FIXME: This needs a lot of work. 1366 * 1367 * @private 1368 */ 1369 public function encodeSimpleUnion(definition:XML, parent:XML, name:QName, value:*, restriction:XML = null):void 1370 { 1371 var memberList:String = getAttributeFromNode("memberTypes", definition); 1372 var memberArray:Array = memberList.split(" "); 1373 var type:QName; 1374 var args:*; 1375 1376 //as the memeber types can contain simple data types like xsd:string or tns:address 1377 for (var i:int = 0; i < memberArray.length; i++) 1378 { 1379 var prefixedName:String = memberArray[i]; 1380 var simpleType:QName = schemaManager.getQNameForPrefixedName(prefixedName, definition); 1381 1382 if (!isBuiltInType(simpleType)) 1383 { 1384 args = getValue(value, simpleType); 1385 if (args !== undefined) 1386 { 1387 type = simpleType; 1388 break; 1389 } 1390 } 1391 } 1392 1393 if (!type) 1394 { 1395 type = schemaManager.schemaDatatypes.stringQName; 1396 } 1397 1398 setValue(parent, schemaManager.marshall(value, type, restriction)); 1399 } 1400 1401 /** 1402 * Allow instance specific overrides for concrete type information as 1403 * abstract complexTypes may require a concrete xsi:type definition. 1404 * 1405 * @param parent A reference to the parent XML. Must not be null. 1406 * 1407 * @private 1408 */ 1409 public function encodeType(type:QName, parent:XML, name:QName, value:*, restriction:XML = null):void 1410 { 1411 var xsiType:QName = getXSIType(value); 1412 if (xsiType != null) 1413 type = xsiType; 1414 1415 var definition:XML = schemaManager.getNamedDefinition(type, 1416 constants.complexTypeQName, constants.simpleTypeQName); 1417 1418 1419 if (isBuiltInType(type)) 1420 { 1421 if (type == constants.anyTypeQName && !isSimpleValue(value)) 1422 { 1423 var children:XMLList = new XMLList(); 1424 encodeAnyElement(definition, children, name, value); 1425 setValue(parent, children); 1426 } 1427 else 1428 { 1429 setValue(parent, schemaManager.marshall(value, type, restriction)); 1430 } 1431 1432 deriveXSIType(parent, type, value); 1433 } 1434 else 1435 { 1436 if (definition == null) 1437 throw new Error("Cannot find definition for type '" + type + "'"); 1438 1439 var definitionType:QName = definition.name() as QName; 1440 if (definitionType == constants.complexTypeQName) 1441 { 1442 // <complexType> 1443 encodeComplexType(definition, parent, name, value, restriction); 1444 } 1445 else if (definitionType == constants.simpleTypeQName) 1446 { 1447 // <simpleType> 1448 encodeSimpleType(definition, parent, name, value, restriction); 1449 } 1450 else 1451 { 1452 throw new Error("Invalid type definition " + definitionType); 1453 } 1454 } 1455 // If we found our type definition by name we release the schema scope. 1456 if (definition != null) 1457 schemaManager.releaseScope(); 1458 } 1459 1460 1461 /** 1462 * Sets the xsi:nil attribute when necessary 1463 * 1464 * @param definition The Schema definition of the expected type. If 1465 * nillable is strictly enforced, this definition must explicitly 1466 * specify nillable=true. 1467 * 1468 * @param name The name of the element to be created 1469 * 1470 * @param value The value to check 1471 * 1472 * @return content The element where xsi:nil was set, or null if xsi:nil was 1473 * not set. 1474 */ 1475 public function encodeXSINil(definition:XML, name:QName, value:*, isRequired:Boolean = true):XML 1476 { 1477 // Check for nillable in the definition only if strictNillability is true. 1478 // Otherwise assume nillable=true. 1479 var nillable:Boolean = true; 1480 if (strictNillability) 1481 { 1482 if (definition != null) 1483 nillable = definition.@nillable.toString() == "true" ? true : false; 1484 else 1485 nillable = false; //XML schema default for nillable 1486 } 1487 1488 var item:XML; 1489 1490 // <element fixed="..."> 1491 // Fixed is forbidden when nillable="true". We enforce that only if 1492 // strictNillability==true. Otherwise we take the fixed value if it 1493 // is provided. 1494 var fixedValue:String = getAttributeFromNode("fixed", definition); 1495 if (!(strictNillability && nillable) && fixedValue != null) 1496 { 1497 item = createElement(name); 1498 setValue(item, schemaManager.marshall(fixedValue, schemaManager.schemaDatatypes.stringQName)); 1499 return item; 1500 } 1501 1502 // After we are done with fixed, which can replace even a non-null value, 1503 // we only care about cases where value is null, so we can return otherwise. 1504 if (value != null) 1505 return null; 1506 1507 // <element default="..."> 1508 var defaultValue:String = getAttributeFromNode("default", definition); 1509 if (value == null && defaultValue != null) 1510 { 1511 item = createElement(name); 1512 setValue(item, schemaManager.marshall(defaultValue, schemaManager.schemaDatatypes.stringQName)); 1513 return item; 1514 } 1515 1516 // If null or undefined, and nillable, we set xsi:nil="true" 1517 // and return the element 1518 if (nillable && value === null && isRequired == true) 1519 { 1520 item = createElement(name); 1521 setValue(item, null); 1522 return item; 1523 } 1524 1525 return null; 1526 } 1527 1528 1529 /** 1530 * @private 1531 */ 1532 public function getAttribute(parent:*, name:*):* 1533 { 1534 return getValue(parent, name); 1535 } 1536 1537 /** 1538 * @private 1539 */ 1540 public function hasAttribute(parent:*, name:*):Boolean 1541 { 1542 return (getAttribute(parent, name) !== undefined); 1543 } 1544 1545 /** 1546 * @private 1547 */ 1548 public function setAttribute(parent:XML, name:*, value:*):void 1549 { 1550 if (value != null) 1551 parent.@[name] = value.toString(); 1552 } 1553 1554 /** 1555 * @private 1556 */ 1557 public function getProperties(value:*):Array 1558 { 1559 var classInfo:Object = ObjectUtil.getClassInfo(value as Object, null, {includeReadOnly:false}); 1560 return classInfo.properties; 1561 } 1562 1563 /** 1564 * Returns a single XML node with the given name 1565 * 1566 * @private 1567 */ 1568 public function createElement(name:*):XML 1569 { 1570 var element:XML; 1571 var elementName:QName; 1572 if (name is QName) 1573 elementName = name as QName; 1574 else 1575 elementName = new QName("", name.toString()); 1576 1577 element = <{elementName.localName} />; 1578 if (elementName.uri != null && elementName.uri.length > 0) 1579 { 1580 var prefix:String = schemaManager.getOrCreatePrefix(elementName.uri); 1581 var ns:Namespace = new Namespace(prefix, elementName.uri); 1582 element.setNamespace(ns); 1583 } 1584 return element; 1585 } 1586 1587 1588 /** 1589 * @private 1590 */ 1591 public function getSimpleValue(parent:*, name:*):* 1592 { 1593 var simpleValue:* = getValue(parent, name); 1594 1595 // Support legacy _value property for simpleContent 1596 if (simpleValue === undefined) 1597 simpleValue = getValue(parent, "_value"); 1598 1599 return simpleValue; 1600 } 1601 1602 /** 1603 * Determines whether a value should be representable as a single, simple 1604 * value, otherwise the object is regarded as "complex" and contains 1605 * child values referenced by index or name. 1606 * 1607 * @private 1608 */ 1609 public function isSimpleValue(value:*):Boolean 1610 { 1611 if (value is String || value is Number || value is Boolean 1612 || value is Date || value is int || value is uint 1613 || value is ByteArray) 1614 { 1615 return true; 1616 } 1617 1618 return false; 1619 } 1620 1621 /** 1622 * @private 1623 */ 1624 public function getValue(parent:*, name:*):* 1625 { 1626 var value:*; 1627 1628 if (parent is XML || parent is XMLList) 1629 { 1630 var node:XMLList = parent[name]; 1631 if (node.length() > 0) 1632 value = node; 1633 } 1634 else if (TypeIterator.isIterable(parent)) 1635 { 1636 // We may have an associative Array 1637 if (parent.hasOwnProperty(name) && parent[name] !== undefined) 1638 { 1639 value = resolveNamedProperty(parent, name); 1640 } 1641 else 1642 { 1643 // Otherwise, we just return the value as this may be for an 1644 // ArrayOfSomeType that needs special casing to map directly 1645 // to an Array without a wrapper type 1646 value = parent; 1647 } 1648 } 1649 else if (!isSimpleValue(parent)) 1650 { 1651 // We only support the public namespace for now 1652 if (name is QName) 1653 name = QName(name).localName; 1654 1655 value = resolveNamedProperty(parent, name); 1656 } 1657 else 1658 { 1659 // FIXME: Shouldn't this be an error condition? 1660 value = parent; 1661 } 1662 1663 return value; 1664 } 1665 1666 1667 /** 1668 * @private 1669 */ 1670 public function hasValue(parent:*, name:*):Boolean 1671 { 1672 return (getValue(parent, name) !== undefined); 1673 } 1674 1675 1676 /** 1677 * @private 1678 */ 1679 public function containsNodeByName(list:XMLList, name:QName, strict:Boolean=false):Boolean 1680 { 1681 var currentURI:String = schemaManager.currentSchema.targetNamespace.uri; 1682 for each (var node:XML in list) 1683 { 1684 if (strict || (name.uri != "" && name.uri != null)) 1685 { 1686 // If we need strict comparisons, or if name is qualified, and 1687 // not in the default namespace, we match the full QName. However, 1688 // elements already contained in the encoded XMLList could be 1689 // unqualified, so to match them against a qualified name, we use 1690 // the target namespace of the current schema used in encoding, 1691 // which will be the namespace the unqualified elements assume. 1692 if (node.name().uri == "" && currentURI == name.uri) 1693 { 1694 //compare by localName 1695 if (node.name().localName == name.localName) 1696 return true; 1697 } 1698 else if (node.name() == name) 1699 { 1700 return true; 1701 } 1702 } 1703 else 1704 { 1705 // If we only have a localName, and don't need strict comparisons, 1706 // look for any node with that localName, regardless of namespace. 1707 if (node.name().localName == name.localName) 1708 return true; 1709 } 1710 } 1711 return false; 1712 } 1713 1714 1715 /** 1716 * Looks up value by name on a complex parent object, considering that the 1717 * name might have to be prepended with an underscore. 1718 * @private 1719 */ 1720 public function resolveNamedProperty(parent:*, name:*):* 1721 { 1722 var value:*; 1723 var fallbackName:String = null; 1724 1725 if (!isSimpleValue(parent)) 1726 { 1727 try 1728 { 1729 value = parent[name]; 1730 // If a value by this name is not defined on a dynamic object, 1731 // try looking up with an underscore. 1732 if (value === undefined) 1733 fallbackName = "_" + name.toString(); 1734 } 1735 catch (e:Error) 1736 { 1737 // If a property with that name doesn't exist on a non-dynamic 1738 // object, an error will be thrown. We should still try the 1739 // fallback name. 1740 fallbackName = "_" + name.toString(); 1741 1742 } 1743 1744 if (fallbackName != null && parent.hasOwnProperty(fallbackName)) 1745 value = parent[fallbackName]; 1746 } 1747 1748 return value; 1749 } 1750 1751 1752 1753 /** 1754 * Assigns value to an XML node. 1755 * 1756 * @param parent The node to assign to. Must be either XML or XMLList. 1757 * If XMLList, it must contain at least one XML element. The value is 1758 * assigned on the last element in the list. If XML, the value is assigned 1759 * directly on parent. 1760 * @param value The value to assign on the parent. If null, the xsi:nil 1761 * attribute is set on the parent. If XML or XMLList, the value is appended 1762 * as child node(s) on the parent. Otherwise the string representation of the 1763 * value is appended as a text node. A value that is explicitly undefined is 1764 * skipped. 1765 * 1766 * @private 1767 */ 1768 public function setValue(parent:*, value:*):void 1769 { 1770 1771 if (value !== undefined) 1772 { 1773 var currentChild:XML; 1774 if (parent is XML) 1775 currentChild = parent as XML; 1776 else if (parent is XMLList && parent.length() > 0) 1777 currentChild = parent[parent.length()-1]; 1778 1779 if (currentChild != null) 1780 { 1781 if (value === null) 1782 { 1783 // set xsi:nil attribute if value is specifically null. 1784 currentChild.@[schemaManager.schemaConstants.nilQName] = "true"; 1785 currentChild.addNamespace(constants.xsiNamespace); 1786 } 1787 else if (value is XML || value is XMLList) 1788 { 1789 currentChild.appendChild(value); 1790 } 1791 else if (value !== undefined) 1792 { 1793 // Everything else is treated as simple content, except 1794 // for undefined, which is skipped. 1795 currentChild.appendChild(xmlSpecialCharsFilter(Object(value))); 1796 } 1797 } 1798 } 1799 } 1800 1801 /** 1802 * Appends a value (or list of values) directly as 1803 * members of the parent XMLList. Effectively merges 1804 * two XMLLists. 1805 * 1806 * @private 1807 */ 1808 public function appendValue(parent:XMLList, value:*):void 1809 { 1810 parent[parent.length()] = value; 1811 } 1812 1813 /** 1814 * Checks to see whether a value defines a custom XSI type to be used 1815 * during encoding, otherwise the default type is returned. 1816 */ 1817 protected function getXSIType(value:*):QName 1818 { 1819 var xsiType:QName; 1820 1821 // Allow IXMLSchemaInstance or ObjectProxy to override XSI type 1822 // information, if provided... 1823 if (value != null) 1824 { 1825 if (value is ObjectProxy && value.object_proxy::type != null) 1826 { 1827 xsiType = value.object_proxy::type; 1828 } 1829 else if (value is IXMLSchemaInstance && IXMLSchemaInstance(value).xsiType != null) 1830 { 1831 xsiType = IXMLSchemaInstance(value).xsiType; 1832 } 1833 } 1834 1835 return xsiType; 1836 } 1837 1838 /** 1839 * Record custom XSI type information for this XML node by adding an 1840 * xsi:type attribute with the value set to the qualified type name. 1841 */ 1842 protected function setXSIType(parent:XML, type:QName):void 1843 { 1844 var namespaceURI:String = type.uri; 1845 var prefix:String = schemaManager.getOrCreatePrefix(namespaceURI); 1846 var prefixNamespace:Namespace = new Namespace(prefix, namespaceURI); 1847 parent.addNamespace(prefixNamespace); 1848 parent.@[constants.getXSIToken(constants.typeAttrQName)] = prefix + ":" + type.localName; 1849 } 1850 1851 /** 1852 * @private 1853 */ 1854 protected function deriveXSIType(parent:XML, type:QName, value:*):void 1855 { 1856 } 1857 1858 /** 1859 * @private 1860 * Default implementation of xmlSpecialCharsFilter. Escapes "&" and "<". 1861 */ 1862 private function escapeXML(value:Object):String 1863 { 1864 var str:String = value.toString(); 1865 if (_escapeXMLChars) 1866 { 1867 str = str.replace(/&/g, "&").replace(/</g, "<"); 1868 } 1869 return str; 1870 } 1871 1872 1873 //-------------------------------------------------------------------------- 1874 // 1875 // Properties 1876 // 1877 //-------------------------------------------------------------------------- 1878 1879 /** 1880 * 1881 */ 1882 public function get strictNillability():Boolean 1883 { 1884 return _strictNillability; 1885 } 1886 1887 /** 1888 * 1889 */ 1890 public function set strictNillability(strict:Boolean):void 1891 { 1892 _strictNillability = strict; 1893 } 1894 1895 1896 /** 1897 * Function to be used for escaping XML special characters in simple content. 1898 * Returns default implementation in this class. 1899 */ 1900 public function get xmlSpecialCharsFilter():Function 1901 { 1902 return _xmlSpecialCharsFilter; 1903 } 1904 1905 /** 1906 * 1907 */ 1908 public function set xmlSpecialCharsFilter(func:Function):void 1909 { 1910 if (func != null) 1911 _xmlSpecialCharsFilter = func; 1912 else 1913 // If setting to null, we revert to built-in default. 1914 _xmlSpecialCharsFilter = escapeXML; 1915 } 1916 1917 //-------------------------------------------------------------------------- 1918 // 1919 // Variables 1920 // 1921 //-------------------------------------------------------------------------- 1922 1923 private var _strictNillability:Boolean = false; 1924 private var _xmlSpecialCharsFilter:Function = escapeXML; 1925 private var _escapeXMLChars:Boolean = true; 1926 1927} 1928 1929} 1930