This is an automated email from the ASF dual-hosted git repository. gregdove pushed a commit to branch improvements/Language in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
commit 722f16599441be4fd0243d6fe9f2cd856f1a6b0b Author: greg-dove <[email protected]> AuthorDate: Wed May 15 10:54:34 2019 +1200 Numerous updates to XML/XMLList to address issues identified during testing --- frameworks/projects/XML/src/main/royale/XML.as | 258 ++++++++++++++------- frameworks/projects/XML/src/main/royale/XMLList.as | 43 +++- 2 files changed, 206 insertions(+), 95 deletions(-) diff --git a/frameworks/projects/XML/src/main/royale/XML.as b/frameworks/projects/XML/src/main/royale/XML.as index e6ea42e..01229c2 100644 --- a/frameworks/projects/XML/src/main/royale/XML.as +++ b/frameworks/projects/XML/src/main/royale/XML.as @@ -19,6 +19,9 @@ package { COMPILE::JS + /** + * @royaleignorepublicvarwarning + */ public class XML { import org.apache.royale.debugging.assert; @@ -127,7 +130,6 @@ package static private function escapeAttributeValue(value:String):String { - var outArr:Array = []; var arr:Array = String(value).split(""); var len:int = arr.length; for(var i:int=0;i<len;i++) @@ -135,61 +137,55 @@ package switch(arr[i]) { case "<": - outArr[i] = "<"; + arr[i] = "<"; break; case "&": - if(arr[i+1] == "#") - outArr[i] = "&"; - else - outArr[i] = "&"; + if(arr[i+1] != "#") + arr[i] = "&"; break; case '"': - outArr[i] = """; + arr[i] = """; break; case "\u000A": - outArr[i] = "
"; + arr[i] = "
"; break; case "\u000D": - outArr[i] = "
"; + arr[i] = "
"; break; case "\u0009": - outArr[i] = "	"; + arr[i] = "	"; break; default: - outArr[i] = arr[i]; break; } } - return outArr.join(""); + return arr.join(""); } static private function escapeElementValue(value:String):String { var i:int; - var outArr:Array = []; var arr:Array = value.split(""); - for(i=0;i<arr.length;i++) + const len:uint = arr.length; + for(i=0;i<len;i++) { switch(arr[i]) { case "<": - outArr[i] = "<"; + arr[i] = "<"; break; case ">": - outArr[i] = ">"; + arr[i] = ">"; break; case "&": - if(arr[i+1] == "#") - outArr[i] = "&"; - else - outArr[i] = "&"; + if(arr[i+1] != "#") + arr[i] = "&"; break; default: - outArr[i] = arr[i]; break; } } - return outArr.join(""); + return arr.join(""); } static private function insertAttribute(att:Attr,parent:XML):XML @@ -208,9 +204,15 @@ package // add attributes var attrs:* = node.attributes; var len:int = node.attributes.length; + for(i=0;i<len;i++) { - insertAttribute(attrs[i],xml); + var att:Attr = attrs[i]; + if (att.prefix == 'xmlns' && att.namespaceURI == 'http://www.w3.org/2000/xmlns/') { + //from e4x spec: NOTE Although namespaces are declared using attribute syntax in XML, they are not represented in the [[Attributes]] property. + xml.addNamespace(new Namespace(att.localName, att.nodeValue)); + } + else insertAttribute(att,xml); } // loop through childNodes which will be one of: // text, cdata, processing instrution or comment and add them as children of the element @@ -218,18 +220,24 @@ package len = childNodes.length; for(i=0;i<len;i++) { - var child:XML = fromNode(childNodes[i]); - xml.addChildInternal(child); + var nativeNode:Node = childNodes[i]; + if (nativeNode.nodeType == 7 && XML.ignoreProcessingInstructions) { + continue; + } + var child:XML = fromNode(nativeNode); + if (child) + xml.addChildInternal(child); } } /** * returns an XML object from an existing node without the need to parse the XML. * The new XML object is not normalized + * + * @royaleignorecoercion Element */ - static private function fromNode(node:Element):XML + static private function fromNode(node:Node):XML { var xml:XML; - var i:int; var data:* = node.nodeValue; var localName:String = node.nodeName; var prefix:String = node.prefix; @@ -245,22 +253,24 @@ package xml = new XML(); xml.setNodeKind("element"); xml.setName(qname); - iterateElement(node,xml); + iterateElement(node as Element,xml); break; //case 2:break;// ATTRIBUTE_NODE (handled separately) case 3: //TEXT_NODE + if (XML.ignoreWhitespace) { + data = data.trim(); + if (!data) return null; + } xml = new XML(); xml.setNodeKind("text"); - xml.setName(qname); - if(XML.ignoreWhitespace) - data = data.trim(); + //xml.setName(qname); e4X: the name must be null (text node rules) xml.setValue(data); break; case 4: //CDATA_SECTION_NODE xml = new XML(); - xml.setName(qname); + //xml.setName(qname); e4X: the name must be null (text node rules) xml.setNodeKind("text"); data = "<![CDATA[" + data + "]]>"; xml.setValue(data); @@ -276,8 +286,10 @@ package break; case 8: //COMMENT_NODE + xml = new XML(); xml.setNodeKind("comment"); + //e4X: the name must be null (comment node rules) xml.setValue(data); break; //case 9:break;//DOCUMENT_NODE @@ -398,7 +410,7 @@ package // _children = []; if(xml) { - var xmlStr:String = "" + xml; + var xmlStr:String = ignoreWhitespace ? trimXMLWhitespace("" + xml) : "" + xml; if(xmlStr.indexOf("<") == -1) { _nodeKind = "text"; @@ -418,11 +430,15 @@ package configurable: true } ); - } private static var xmlRegEx:RegExp = /&(?![\w]+;)/g; private static var parser:DOMParser; private static var errorNS:String; + + /** + * + * @royaleignorecoercion Element + */ private function parseXMLStr(xml:String):void { //escape ampersands @@ -440,6 +456,10 @@ package errorNS = "na"; } } + //various node types not supported directly + //maybe it should always wrap (e4x seems to say yes on p34, 'Semantics' of e4x-Ecma-357.pdf) + var wrap:Boolean = (xml.indexOf('<?') == 0 && xml.indexOf('<?xml ') != 0) || (xml.indexOf('<![CDATA[') == 0) || (xml.indexOf('<!--') == 0); + if (wrap) xml = '<parseRoot>'+xml+'</parseRoot>'; try { var doc:Document = parser.parseFromString(xml, "application/xml"); @@ -453,9 +473,10 @@ package var errorNodes:NodeList = doc.getElementsByTagNameNS(errorNS, 'parsererror'); if(errorNodes.length > 0) throw new Error(errorNodes[0].innerHTML); + if (wrap) doc = doc.childNodes[0]; for(var i:int=0;i<doc.childNodes.length;i++) { - var node:Element = doc.childNodes[i]; + var node:Node = doc.childNodes[i]; if(node.nodeType == 1) { _version = doc.xmlVersion; @@ -465,16 +486,52 @@ package // _name.prefix = node.prefix; // _name.uri = node.namespaceURI; // _name.localName = node.localName; - iterateElement(node,this); + iterateElement(node as Element,this); } else { + if (node.nodeType == 7) { + if (XML.ignoreProcessingInstructions) { + this.setNodeKind('text'); + //e4x: The value of the [[Name]] property is null if and only if the XML object represents an XML comment or text node + _name = null; + this.setValue(''); + } else { + this.setNodeKind('processing-instruction'); + this.setName(node.nodeName); + this.setValue(node.nodeValue); + } + + } else if (node.nodeType == 4) { + this.setNodeKind('text'); + //e4x: The value of the [[Name]] property is null if and only if the XML object represents an XML comment or text node + _name = null; + if (node.nodeName == '#cdata-section') { + this.setValue('<![CDATA[' + node.nodeValue + ']]>'); + } else { + this.setValue(node.nodeValue); + } + } else if (node.nodeType == 8) { + //e4x: The value of the [[Name]] property is null if and only if the XML object represents an XML comment or text node + _name = null; + if (XML.ignoreComments) { + this.setNodeKind('text'); + this.setValue(''); + } else { + this.setNodeKind('comment'); + this.setValue(node.nodeValue); + } + + + } + // Do we record the nodes which are probably processing instructions? // var child:XML = XML.fromNode(node); // addChild(child); } } - normalize(); + //normalize seems wrong here: + //normalize(); //need to deal with errors https://bugzilla.mozilla.org/show_bug.cgi?id=45566 // get rid of nodes we do not want //loop through the child nodes and build XML obejcts for each. @@ -499,8 +556,6 @@ package return _namespaces; } - private var _origStr:String; - /** * @private @@ -614,34 +669,38 @@ package * @param child * @return * + * @royaleignorecoercion XML + * */ public function appendChild(child:*):XML { /* - [[Insert]] (P, V) - 1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return - 2. Let i = ToUint32(P) - 3. If (ToString(i) is not equal to P), throw a TypeError exception - 4. If Type(V) is XML and (V is x or an ancestor of x) throw an Error exception - 5. Let n = 1 - 6. If Type(V) is XMLList, let n = V.[[Length]] - 7. If n == 0, Return - 8. For j = x.[[Length]]-1 downto i, rename property ToString(j) of x to ToString(j + n) - 9. Let x.[[Length]] = x.[[Length]] + n - 10. If Type(V) is XMLList - a. For j = 0 to V.[[Length-1]] - i. V[j].[[Parent]] = x - ii. x[i + j] = V[j] - 11. Else - a. Call the [[Replace]] method of x with arguments i and V - 12. Return + 1. Let children be the result of calling the [[Get]] method of x with argument "*" + 2. Call the [[Put]] method of children with arguments children.[[Length]] and child + 3. Return x + */ var childType:String = typeof child; - if(childType != "object") - child = xmlFromStringable(child); + + if(childType != "object") { + var last:uint = childrenLength(); + const lastChild:XML = last ? _children[last-1] : null; + if (lastChild && lastChild.nodeKind() == 'element') { + + const wrapper:XML = new XML(); + child = new XML(child.toString()); + wrapper.setName(lastChild.name()); + child.setParent(wrapper); + wrapper.getChildren().push(child); + child = wrapper; + } else { + child = xmlFromStringable(child); + } + } appendChildInternal(child); - normalize(); + //normalize seems not correct here: + //normalize(); return this; } @@ -1405,7 +1464,7 @@ package */ public function localName():String { - return name().localName; + return _name? _name.localName : null; } private var _name:QName; @@ -1418,8 +1477,8 @@ package */ public function name():QName { - if(!_name) - _name = getQName("","","",false); + /*if(!_name) + _name = getQName("","","",false);*/ return _name; } @@ -2257,6 +2316,20 @@ package */ public function setName(name:*):void { + //@todo add tests to review against the following: + /*1. If x.[[Class]] ∈ {"text", "comment"}, return + 2. If (Type(name) is Object) and (name.[[Class]] == "QName") and (name.uri == null) + a. Let name = name.localName + 3. Let n be a new QName created if by calling the constructor new QName(name) + 4. If x.[[Class]] == "processing-instruction", let n.uri be the empty string + 5. Let x.[[Name]] = n + 6. Let ns be a new Namespace created as if by calling the constructor new Namespace(n.prefix, n.uri) + 7. If x.[[Class]] == "attribute" + a. If x.[[Parent]] == null, return + b. Call x.[[Parent]].[[AddInScopeNamespace]](ns) + 8. If x.[[Class]] == "element" + a. Call x.[[AddInScopeNamespace]](ns)*/ + if (_nodeKind == 'text' || _nodeKind == 'comment') return; //e4x, see 1 above var nameRef:QName; if(name is QName) nameRef = name; @@ -2274,22 +2347,23 @@ package */ public function setNamespace(ns:Object):void { - if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction") + var kind:String = _nodeKind; + if(kind == "text" || kind == "comment" || kind == "processing-instruction") return; var ns2:Namespace = new Namespace(ns); var nameRef:QName = new QName(ns2,name()); - if(_nodeKind == "attribute") + if(kind == "attribute") { - nameRef.isAttribute = true; if(_parent == null) return; + nameRef.isAttribute = true; _parent.addNamespace(ns2); } - + _name = getQName(nameRef.localName,nameRef.prefix,nameRef.uri,nameRef.isAttribute); - if(_nodeKind == "element") + if(kind == "element") addNamespace(ns2); } @@ -2553,26 +2627,28 @@ package indentArr.push(_indentStr); var indent:String = indentArr.join(""); - if(this.nodeKind() == "text") + const nodeType:String = this.nodeKind(); + if(nodeType == "text") //4. { if(prettyPrinting) { var v:String = trimXMLWhitespace(_value); - if(name().localName == "#cdata-section") + if (v.indexOf('<![CDATA[') == 0) { return indent + v; + } return indent + escapeElementValue(v); } - if(name().localName == "#cdata-section") + if (_value.indexOf('<![CDATA[') == 0) return _value; return escapeElementValue(_value); } - if(this.nodeKind() == "attribute") + if(nodeType == "attribute") return indent + escapeAttributeValue(_value); - if(this.nodeKind() == "comment") + if(nodeType == "comment") return indent + "<!--" + _value + "-->"; - if(this.nodeKind() == "processing-instruction") + if(nodeType == "processing-instruction") return indent + "<?" + name().localName + " " + _value + "?>"; // We excluded the other types, so it's a normal element @@ -2613,24 +2689,8 @@ package if(ns.prefix) strArr.push(ns.prefix+":"); strArr.push(name().localName); - + //attributes and namespace declarations... (15-16) - for(i=0;i<declarations.length;i++) - { - var decVal:String = escapeAttributeValue(declarations[i].uri); - if(decVal) - { - strArr.push(" xmlns"); - if(declarations[i].prefix) - { - strArr.push(":"); - strArr.push(declarations[i].prefix); - } - strArr.push('="'); - strArr.push(decVal); - strArr.push('"'); - } - } len = attributeLength(); for(i=0;i<len;i++) { @@ -2649,6 +2709,24 @@ package strArr.push(escapeAttributeValue(_attributes[i].getValue())); strArr.push('"'); } + // see 15. namespace declarations is after + for(i=0;i<declarations.length;i++) + { + var decVal:String = escapeAttributeValue(declarations[i].uri); + if(decVal) + { + strArr.push(" xmlns"); + if(declarations[i].prefix) + { + strArr.push(":"); + strArr.push(declarations[i].prefix); + } + strArr.push('="'); + strArr.push(decVal); + strArr.push('"'); + } + } + // now write elements or close the tag if none exist len = childrenLength(); if(len == 0) diff --git a/frameworks/projects/XML/src/main/royale/XMLList.as b/frameworks/projects/XML/src/main/royale/XMLList.as index 49a902d..a54df14 100644 --- a/frameworks/projects/XML/src/main/royale/XMLList.as +++ b/frameworks/projects/XML/src/main/royale/XMLList.as @@ -603,6 +603,28 @@ package i. Let i = i + 1 3. Return list */ + var len:uint = _xmlArray.length; + var textAccumulator:XML; + for (var i:int=0; i<len; i++) { + var node:XML = XML(_xmlArray[i]); + var nodeKind:String = node.nodeKind(); + if (nodeKind == 'element' ) { + node.normalize(); + textAccumulator = null; + } else if (nodeKind == 'text') { + if (textAccumulator) { + textAccumulator.setValue(textAccumulator.getValue() + node.getValue()); + removeChildAt(i); + i--; + len--; + } else { + textAccumulator = node; + } + } else { + textAccumulator = null; + } + } + return this; } @@ -973,26 +995,37 @@ package if(str) retVal.push(str); } - return retVal.join(""); + return retVal.join("\n"); } /** * Returns a string representation of all the XML objects in an XMLList object. * * @return - * + * + * @royaleignorecoercion XML */ public function toString():String { var retVal:Array = []; var len:int = _xmlArray.length; + var cumulativeText:String = ''; for (var i:int=0;i<len;i++) { var str:String = _xmlArray[i].toString(); - if(str) - retVal.push(str); + if (XML(_xmlArray[i]).nodeKind() == 'text') { + cumulativeText += str; + } else { + if (cumulativeText) { + retVal.push(cumulativeText); + cumulativeText = ''; + } + if(str) + retVal.push(str); + } } - return retVal.join(""); + if (cumulativeText) retVal.push(cumulativeText); + return retVal.join("\n"); } /**
