You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by gr...@apache.org on 2019/05/19 06:47:13 UTC

[royale-asjs] 01/05: Numerous updates to XML/XMLList to address issues identified during testing

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 <gr...@gmail.com>
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] = "&lt;";
+						arr[i] = "&lt;";
 						break;
 					case "&":
-						if(arr[i+1] == "#")
-							outArr[i] = "&";
-						else
-							outArr[i] = "&amp;";
+						if(arr[i+1] != "#")
+							arr[i] = "&amp;";
 						break;
 					case '"':
-						outArr[i] = "&quot;";
+						arr[i] = "&quot;";
 						break;
 					case "\u000A":
-						outArr[i] = "&#xA;";
+						arr[i] = "&#xA;";
 						break;
 					case "\u000D":
-						outArr[i] = "&#xD;";
+						arr[i] = "&#xD;";
 						break;
 					case "\u0009":
-						outArr[i] = "&#x9;";
+						arr[i] = "&#x9;";
 						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] = "&lt;";
+						arr[i] = "&lt;";
 						break;
 					case ">":
-						outArr[i] = "&gt;";
+						arr[i] = "&gt;";
 						break;
 					case "&":
-						if(arr[i+1] == "#")
-							outArr[i] = "&";
-						else
-							outArr[i] = "&amp;";
+						if(arr[i+1] != "#")
+							arr[i] = "&amp;";
 						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");
 		}
 		
 		/**