blob: b9e86949d094fea81e173f2dacabad5b2b4eafae [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package
{
COMPILE::JS
public class XML
{
import org.apache.royale.debugging.assert;
import org.apache.royale.debugging.assertType;
/*
* Dealing with namespaces:
* If the name is qualified, it has a prefix. Otherwise, the prefix is null.
* Additionally, it has a namespaceURI. Otherwise the namespaceURI is null.
* the prefix together with the namespaceURI form a QName
*/
/**
* Memory optimization.
* Creating a new QName for each XML instance significantly adds memory usage.
* The XML QName can be a significant percentage of an XML object size.
* By retaining a lookup of QNames and reusing QName objects, we can save quite a bit of memory.
*/
static private var _nameMap:Object = {};
static private function getQName(localName:String,prefix:String,uri:String,isAttribute:Boolean):QName{
localName = localName || "";
prefix = prefix || "";
uri = uri || "";
var key:String = localName + ":" + prefix + ":" + uri + ":" + isAttribute;
var qname:QName = _nameMap[key];
if(!qname){
qname = new QName();
if(prefix)
qname.prefix = prefix;
if(uri)
qname.uri = uri;
if(localName)
qname.localName = localName;
qname.isAttribute = isAttribute;
_nameMap[key] = qname;
}
return qname;
}
/**
* Method to free up references to shared QName objects.
* Probably only worth doing if most or all XML instances can be garbage-collected.
* @langversion 3.0
* @productversion Royale 0.9
*/
static public function clearQNameCache():void
{
_nameMap = {};
}
static private var defaultNamespace:Namespace;
static public function setDefaultNamespace(ns:*):void
{
if(!ns)
defaultNamespace = null;
else
defaultNamespace = new Namespace(ns);
}
/**
* [static] Determines whether XML comments are ignored when XML objects parse the source XML data.
*
*/
static public var ignoreComments:Boolean = true;
/**
* [static] Determines whether XML processing instructions are ignored when XML objects parse the source XML data.
*
*/
static public var ignoreProcessingInstructions:Boolean = true;
/**
* [static] Determines whether white space characters at the beginning and end of text nodes are ignored during parsing.
*
*/
static public var ignoreWhitespace:Boolean = true;
static private var _prettyIndent:int = 2;
/**
* [static] Determines the amount of indentation applied by the toString() and toXMLString() methods when the XML.prettyPrinting property is set to true.
*
*/
static public function set prettyIndent(value:int):void
{
_prettyIndent = value;
_indentStr = "";
for(var i:int = 0; i < value; i++)
{
_indentStr = _indentStr + INDENT_CHAR;
}
}
static public function get prettyIndent():int
{
return _prettyIndent;
}
static private var _indentStr:String = " ";
static private var INDENT_CHAR:String = " ";
/**
* [static] Determines whether the toString() and toXMLString() methods normalize white space characters between some tags.
*
*/
static public var prettyPrinting:Boolean = true;
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++)
{
switch(arr[i])
{
case "<":
outArr[i] = "&lt;";
break;
case "&":
outArr[i] = "&amp;";
break;
case '"':
outArr[i] = "&quot;";
break;
case "\u000A":
outArr[i] = "&#xA;";
break;
case "\u000D":
outArr[i] = "&#xD;";
break;
case "\u0009":
outArr[i] = "&#x9;";
break;
default:
outArr[i] = arr[i];
break;
}
}
return outArr.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++)
{
switch(arr[i])
{
case "<":
outArr[i] = "&lt;";
break;
case ">":
outArr[i] = "&gt;";
break;
case "&":
outArr[i] = "&amp;";
break;
default:
outArr[i] = arr[i];
break;
}
}
return outArr.join("");
}
static private function insertAttribute(att:Attr,parent:XML):XML
{
var xml:XML = new XML();
xml.setParent(parent);
xml.setNodeKind("attribute");
xml.setName(att.name);
xml.setValue(att.value);
parent.addChildInternal(xml);
return xml;
}
static private function iterateElement(node:Element,xml:XML):void
{
var i:int;
// add attributes
var attrs:* = node.attributes;
var len:int = node.attributes.length;
for(i=0;i<len;i++)
{
insertAttribute(attrs[i],xml);
}
// loop through childNodes which will be one of:
// text, cdata, processing instrution or comment and add them as children of the element
var childNodes:NodeList = node.childNodes;
len = childNodes.length;
for(i=0;i<len;i++)
{
var child:XML = fromNode(childNodes[i]);
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
*/
static private function fromNode(node:Element):XML
{
var xml:XML;
var i:int;
var data:* = node.nodeValue;
var localName:String = node.nodeName;
var prefix:String = node.prefix;
if(prefix && localName.indexOf(prefix + ":") == 0)
{
localName = localName.substr(prefix.length+1);
}
var qname:QName = getQName(localName, prefix, node.namespaceURI,false);
switch(node.nodeType)
{
case 1:
//ELEMENT_NODE
xml = new XML();
xml.setNodeKind("element");
xml.setName(qname);
iterateElement(node,xml);
break;
//case 2:break;// ATTRIBUTE_NODE (handled separately)
case 3:
//TEXT_NODE
xml = new XML();
xml.setNodeKind("text");
xml.setName(qname);
if(XML.ignoreWhitespace)
data = data.trim();
xml.setValue(data);
break;
case 4:
//CDATA_SECTION_NODE
xml = new XML();
xml.setName(qname);
xml.setNodeKind("text");
data = "<![CDATA[" + data + "]]>";
xml.setValue(data);
break;
//case 5:break;//ENTITY_REFERENCE_NODE
//case 6:break;//ENTITY_NODE
case 7:
//PROCESSING_INSTRUCTION_NODE
xml = new XML();
xml.setNodeKind("processing-instruction");
xml.setName(qname);
xml.setValue(data);
break;
case 8:
//COMMENT_NODE
xml = new XML();
xml.setNodeKind("comment");
xml.setValue(data);
break;
//case 9:break;//DOCUMENT_NODE
//case 10:break;//DOCUMENT_TYPE_NODE
//case 11:break;//DOCUMENT_FRAGMENT_NODE
//case 12:break;//NOTATION_NODE
default:
throw new TypeError("Unknown XML node type!");
break;
}
return xml;
}
static private function namespaceInArray(ns:Namespace,arr:Array,considerPrefix:Boolean=true):Boolean
{
if(!arr)
return false;
var i:int;
for(i=0;i<arr.length;i++)
{
if(ns.uri == arr[i].uri)
{
if(!considerPrefix)
return true;
if(ns.prefix == arr[i].prefix)
return true;
}
}
return false;
}
static private function trimXMLWhitespace(value:String):String
{
return value.replace(/^\s+|\s+$/gm,'');
}
/**
* [static] Returns an object with the following properties set to the default values: ignoreComments, ignoreProcessingInstructions, ignoreWhitespace, prettyIndent, and prettyPrinting.
* @return
*
*/
static public function defaultSettings():Object
{
return {
ignoreComments : true,
ignoreProcessingInstructions : true,
ignoreWhitespace : true,
prettyIndent : 2,
prettyPrinting : true
}
}
/**
* [static] Sets values for the following XML properties: ignoreComments, ignoreProcessingInstructions, ignoreWhitespace, prettyIndent, and prettyPrinting.
* @param rest
*
*/
static public function setSettings(value:Object):void
{
if(!value)
return;
ignoreComments = value.ignoreComments === undefined ? ignoreComments : value.ignoreComments;
ignoreProcessingInstructions = value.ignoreProcessingInstructions === undefined ? ignoreProcessingInstructions : value.ignoreProcessingInstructions;
ignoreWhitespace = value.ignoreWhitespace === undefined ? ignoreWhitespace : value.ignoreWhitespace;
prettyIndent = value.prettyIndent === undefined ? prettyIndent : value.prettyIndent;
prettyPrinting = value.prettyPrinting === undefined ? prettyPrinting : value.prettyPrinting;
}
/**
* [static] Retrieves the following properties: ignoreComments, ignoreProcessingInstructions, ignoreWhitespace, prettyIndent, and prettyPrinting.
*
* @return
*
*/
static public function settings():Object
{
return {
ignoreComments : ignoreComments,
ignoreProcessingInstructions : ignoreProcessingInstructions,
ignoreWhitespace : ignoreWhitespace,
prettyIndent : prettyIndent,
prettyPrinting : prettyPrinting
}
}
/**
* mimics the top-level XML function
* @royaleignorecoercion XMLList
*/
public static function conversion(xml:*):XML
{
if (xml == null)
{
// throw TypeError
return null;
}
else if (xml.ROYALE_CLASS_INFO != null)
{
var className:String = xml.ROYALE_CLASS_INFO.names[0].name;
if (className == "XML")
return xml;
else if (className == "XMLList")
{
var xmlList:XMLList = xml as XMLList;
if (xmlList.length() == 1)
return xmlList[0];
// throw TypeError
return null;
}
}
return new XML(xml);
}
public function XML(xml:* = null)
{
// _origStr = xml;
// _children = [];
if(xml)
{
var xmlStr:String = "" + xml;
if(xmlStr.indexOf("<") == -1)
{
_nodeKind = "text";
_value = xmlStr;
}
else
{
parseXMLStr(xmlStr);
}
}
Object.defineProperty(this,"0",
{
"get": function():* { return this; },
"set": function(newValue:*):void {
},
enumerable: true,
configurable: true
}
);
}
private static var xmlRegEx:RegExp = /&(?![\w]+;)/g;
private static var parser:DOMParser;
private static var errorNS:String;
private function parseXMLStr(xml:String):void
{
//escape ampersands
xml = xml.replace(xmlRegEx,"&amp;");
if(!parser)
parser = new DOMParser();
if(errorNS == null)
{
// get error namespace. It's different in different browsers.
try{
errorNS = parser.parseFromString('<', 'application/xml').getElementsByTagName("parsererror")[0].namespaceURI;
}
catch(err:Error){
// Some browsers (i.e. IE) just throw an error
errorNS = "na";
}
}
try
{
var doc:Document = parser.parseFromString(xml, "application/xml");
}
catch(err:Error)
{
throw err;
}
//check for errors
var errorNodes:NodeList = doc.getElementsByTagNameNS(errorNS, 'parsererror');
if(errorNodes.length > 0)
throw new Error(errorNodes[0].innerHTML);
for(var i:int=0;i<doc.childNodes.length;i++)
{
var node:Element = doc.childNodes[i];
if(node.nodeType == 1)
{
_version = doc.xmlVersion;
_encoding = doc.xmlEncoding;
_name = getQName(node.localName,node.prefix,node.namespaceURI,false);
// _name = new QName();
// _name.prefix = node.prefix;
// _name.uri = node.namespaceURI;
// _name.localName = node.localName;
iterateElement(node,this);
}
else
{
// Do we record the nodes which are probably processing instructions?
// var child:XML = XML.fromNode(node);
// addChild(child);
}
}
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.
}
private var _children:Array;
private var _attributes:Array;
private var _processingInstructions:Array;
private var _parent:XML;
private var _value:String;
private var _version:String;
private var _encoding:String;
private var _appliedNamespace:Namespace;
/**
* Memory optimization: Don't create the array unless needed.
*/
private var _namespaces:Array;
private function getNamespaces():Array
{
if(!_namespaces)
_namespaces = [];
return _namespaces;
}
private var _origStr:String;
/**
* @private
*
* Similar to appendChild, but accepts all XML types (text, comment, processing-instruction, attribute, or element)
*
*
*/
public function addChild(child:XML):void
{
if(!child)
return;
addChildInternal(child);
normalize();
}
private function addChildInternal(child:XML):void
{
assertType(child,XML,"Type must be XML");
child.setParent(this);
if(child.nodeKind() =="attribute")
getAttributes().push(child);
else
getChildren().push(child);
}
private function getChildren():Array
{
if(!_children)
_children = [];
return _children;
}
private function getAttributes():Array
{
if(!_attributes)
_attributes = [];
return _attributes;
}
/**
* Adds a namespace to the set of in-scope namespaces for the XML object.
*
* @param ns
* @return
*
*/
public function addNamespace(ns:Namespace):XML
{
//TODO cached QNames will not work very well here.
/*
When the [[AddInScopeNamespace]] method of an XML object x is called with a namespace N, the following steps are taken:
1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", “attribute”}, return
2. If N.prefix != undefined
a. If N.prefix == "" and x.[[Name]].uri == "", return
b. Let match be null
c. For each ns in x.[[InScopeNamespaces]]
i. If N.prefix == ns.prefix, let match = ns
d. If match is not null and match.uri is not equal to N.uri
i. Remove match from x.[[InScopeNamespaces]]
e. Let x.[[InScopeNamespaces]] = x.[[InScopeNamespaces]] ∪ { N }
f. If x.[[Name]].[[Prefix]] == N.prefix
i. Let x.[[Name]].prefix = undefined
g. For each attr in x.[[Attributes]]
i. If attr.[[Name]].[[Prefix]] == N.prefix, let attr.[[Name]].prefix = undefined
3. Return
*/
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return this;
if(ns.prefix === null)
return this;
if(ns.prefix == "" && name().uri == "")
return this;
var match:Namespace = null;
var i:int;
var nspaces:Array = getNamespaces();
for(i=0;i<nspaces.length;i++)
{
if(nspaces[i].prefix == ns.prefix)
{
match = nspaces[i];
break;
}
}
if(match)
nspaces[i] = ns;
else
nspaces.push(ns);
if(ns.prefix == name().prefix)
name().prefix = null;
var len:int = attributeLength();
for(i=0;i<len;i++)
{
if(_attributes[i].name().prefix == ns.prefix)
_attributes[i].name().prefix = null;
}
return this;
}
/**
* Appends the given child to the end of the XML object's properties.
*
* @param child
* @return
*
*/
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
*/
var childType:String = typeof child;
if(childType != "object")
child = xmlFromStringable(child);
appendChildInternal(child);
normalize();
return this;
}
private function appendChildInternal(child:*):void
{
if(child is XMLList)
{
var len:int = child.length();
for(var i:int=0; i<len; i++)
{
appendChildInternal(child[0]);
}
}
else
{
assertType(child,XML,"Type must be XML");
child.setParent(this);
getChildren().push(child);
}
}
/**
* Returns the XML value of the attribute that has the name matching the attributeName parameter.
*
* @param attributeName
* @return
*
*/
public function attribute(attributeName:*):XMLList
{
var i:int;
if(attributeName == "*")
return attributes();
attributeName = toAttributeName(attributeName);
var list:XMLList = new XMLList();
var len:int = attributeLength();
for(i=0;i<len;i++)
{
if(_attributes[i].name().matches(attributeName))
list.append(_attributes[i]);
}
list.targetObject = this;
list.targetProperty = attributeName;
return list;
}
/**
* Returns a list of attribute values for the given XML object.
*
* @return
*
*/
public function attributes():XMLList
{
var i:int;
var list:XMLList = new XMLList();
var len:int = attributeLength();
for(i=0;i<len;i++)
list.append(_attributes[i]);
list.targetObject = this;
return list;
}
/**
* Lists the children of an XML object.
*
* @param propertyName
* @return
*
*/
public function child(propertyName:Object):XMLList
{
/*
*
When the [[Get]] method of an XML object x is called with property name P, the following steps are taken:
1. If ToString(ToUint32(P)) == P
a. Let list = ToXMLList(x)
b. Return the result of calling the [[Get]] method of list with argument P
2. Let n = ToXMLName(P)
3. Let list be a new XMLList with list.[[TargetObject]] = x and list.[[TargetProperty]] = n
4. If Type(n) is AttributeName
a. For each a in x.[[Attributes]]
i. If ((n.[[Name]].localName == "*") or (n.[[Name]].localName == a.[[Name]].localName)) and ((n.[[Name]].uri == null) or (n.[[Name]].uri == a.[[Name]].uri))
1. Call the [[Append]] method of list with argument a
b. Return list
5. For (k = 0 to x.[[Length]]-1)
a. If ((n.localName == "*") or ((x[k].[[Class]] == "element") and (x[k].[[Name]].localName == n.localName))) and ((n.uri == null) or ((x[k].[[Class]] == “element”) and (n.uri == x[k].[[Name]].uri)))
i. Call the [[Append]] method of list with argument x[k]
6. Return list
*/
var i:int;
var len:int;
var list:XMLList = new XMLList();
if(parseInt(propertyName,10).toString() == propertyName)
{
if(propertyName != "0")
return null;
list.append(this);
list.targetObject = this;
return list;
}
propertyName = toXMLName(propertyName);
if(propertyName.isAttribute)
{
len = attributeLength();
for(i=0;i<len;i++)
{
if(propertyName.matches(_attributes[i].name()))
list.append(_attributes[i]);
}
}
else
{
len = childrenLength();
for(i=0;i<len;i++)
{
if(propertyName.matches(_children[i].name()))
list.append(_children[i]);
}
}
list.targetObject = this;
list.targetProperty = propertyName;
return list;
}
/**
* Identifies the zero-indexed position of this XML object within the context of its parent.
*
* @return
*
*/
public function childIndex():int
{
if(!_parent)
return -1;
return _parent.getIndexOf(this);
}
/**
* Lists the children of the XML object in the sequence in which they appear.
*
* @return
*
*/
public function children():XMLList
{
var i:int;
var list:XMLList = new XMLList();
var len:int = childrenLength();
for(i=0;i<len;i++)
list.append(_children[i]);
list.targetObject = this;
return list;
}
/**
* Lists the properties of the XML object that contain XML comments.
*
* @return
*
*/
public function comments():XMLList
{
var i:int;
var list:XMLList = new XMLList();
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "comment")
list.append(_children[i]);
}
list.targetObject = this;
return list;
}
public function concat(list:*):XMLList
{
if(list is XML)
{
var newList:XMLList = new XMLList();
newList.append(list);
list = newList;
}
if(!(list is XMLList))
throw new TypeError("invalid type");
var retVal:XMLList = new XMLList();
retVal.append(this);
var item:XML;
for each(item in list)
retVal.append(item);
return retVal;
}
/**
* Compares the XML object against the given value parameter.
*
* @param value
* @return
*
*/
public function contains(value:*):Boolean
{
if(value is XML || value is XMLList)
return this.equals(value);
return value == this;
}
/**
* Returns a copy of the given XML object.
*
* @return
*
*/
public function copy():XML
{
/*
When the [[DeepCopy]] method of an XML object x is called, the following steps are taken:
1. Let y be a new XML object with y.[[Prototype]] = x.[[Prototype]], y.[[Class]] = x.[[Class]], y.[[Value]] = x.[[Value]], y.[[Name]] = x.[[Name]], y.[[Length]] = x.[[Length]]
2. For each ns in x.[[InScopeNamespaces]]
a. Let ns2 be a new Namespace created as if by calling the constructor new Namespace(ns)
b. Let y.[[InScopeNamespaces]] = y.[[InScopeNamespaces]] ∪ { ns2 }
3. Let y.[[Parent]] = null
4. For each a in x.[[Attributes]]
a. Let b be the result of calling the [[DeepCopy]] method of a
b. Let b.[[Parent]] = y
c. Let y.[[Attributes]] = y.[[Attributes]] ∪ { b }
5. For i = 0 to x.[[Length]]-1
a. Let c be the result of calling the [[DeepCopy]] method of x[i]
b. Let y[i] = c
c. Let c.[[Parent]] = y
6. Return y
*/
var i:int;
var xml:XML = new XML();
xml.setNodeKind(_nodeKind);
xml.setName(name());
xml.setValue(_value);
var len:int;
len = namespaceLength();
for(i=0;i<len;i++)
{
xml.addNamespace(new Namespace(_namespaces[i]));
}
//parent should be null by default
len = attributeLength();
for(i=0;i<len;i++)
xml.addChildInternal(_attributes[i].copy());
len = childrenLength();
for(i=0;i<len;i++)
xml.addChildInternal(_children[i].copy());
return xml;
}
private function deleteChildAt(idx:int):void
{
if(idx < 0)
return;
if(idx >= childrenLength())
return;
var child:XML = _children[idx];
child._parent = null;
_children.splice(idx,1);
}
/**
* Returns all descendants (children, grandchildren, great-grandchildren, and so on) of the XML object that have the given name parameter.
*
* @param name
* @return
*
*/
public function descendants(name:Object = "*"):XMLList
{
/*
When the [[Descendants]] method of an XML object x is called with property name P, the following steps are taken:
1. Let n = ToXMLName(P)
2. Let list be a new XMLList with list.[[TargetObject]] = null
3. If Type(n) is AttributeName
a. For each a in x.[[Attributes]]
i. If ((n.[[Name]].localName == "*") or (n.[[Name]].localName == a.[[Name]].localName)) and ((n.[[Name]].uri == null) or (n.[[Name]].uri == a.[[Name]].uri ))
1. Call the [[Append]] method of list with argument a
4. For (k = 0 to x.[[Length]]-1)
a. If ((n.localName == "*") or ((x[k].[[Class]] == "element") and (x[k].[[Name]].localName == n.localName))) and ((n.uri == null) or ((x[k].[[Class]] == "element") and (n.uri == x[k].[[Name]].uri)))
i. Call the [[Append]] method of list with argument x[k]
b. Let dq be the resultsof calling the [[Descendants]] method of x[k] with argument P
c. If dq.[[Length]] > 0, call the [[Append]] method of list with argument dq
5. Return list
*/
var i:int;
var len:int;
if(!name)
name = "*";
name = toXMLName(name);
var list:XMLList = new XMLList();
if(name.isAttribute)
{
len = attributeLength();
for(i=0;i<len;i++)
{
if(name.matches(_attributes[i].name()))
list.append(_attributes[i]);
}
}
len = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "element")
{
if(name.matches(_children[i].name()))
list.append(_children[i]);
list.concat(_children[i].descendants(name));
}
}
return list;
}
/**
* Lists the elements of an XML object. (handles E4X dot notation)
*
* @param name
* @return
*
*/
public function elements(name:Object = "*"):XMLList
{
if(!name)
name = "*";
name = toXMLName(name);
var i:int;
var list:XMLList = new XMLList();
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "element" && name.matches(_children[i].name()))
list.append(_children[i]);
}
list.targetObject = this;
list.targetProperty = name;
return list;
}
/**
* for each should work on XML too
* @private
*/
public function elementNames():Array
{
return [0];
}
public function equals(xml:*):Boolean
{
/*
When the [[Equals]] method of an XML object x is called with value V, the following steps are taken:
1. If Type(V) is not XML, return false
2. If x.[[Class]] is not equal to V.[[Class]], return false
3. If x.[[Name]] is not null
a. If V.[[Name]] is null, return false
b. If x.[[Name]].localName is not equal to V.[[Name]].localName, return false
c. If x.[[Name]].uri is not equal to V.[[Name]].uri, return false
4. Else if V.[[Name]] is not null, return false
5. If x.[[Attributes]] does not contain the same number of items as V.[[Attributes]], return false
6. If x.[[Length]] is not equal to V.[[Length]], return false
7. If x.[[Value]] is not equal to y[[Value]], return false
8. For each a in x.[[Attributes]]
a. If V.[[Attributes]] does not contain an attribute b, such that b.[[Name]].localName == a.[[Name]].localName, b.[[Name]].uri == a.[[Name]].uri and b.[[Value]] == a.[[Value]], return false
9. For i = 0 to x.[[Length]]-1
a. Let r be the result of calling the [[Equals]] method of x[i] with argument V[i]
b. If r == false, return false
10. Return true
*/
var i:int;
if(!(xml is XML))
return false;
if(xml.nodeKind() != _nodeKind)
return false;
if(!name().equals(xml.name()))
return false;
var selfAttrs:Array = getAttributeArray();
var xmlAttrs:Array = xml.getAttributeArray();
if(selfAttrs.length != xmlAttrs.length)
return false;
//length comparison should not be necessary because xml always has a length of 1
if(getValue() != xml.getValue())
return false;
for(i=0;i<selfAttrs.length;i++)
{
if(!xml.hasAttribute(selfAttrs[i]))
return false;
}
var selfChldrn:Array = getChildrenArray();
var xmlChildren:Array = xml.getChildrenArray();
if(selfChldrn.length != xmlChildren.length)
return false;
for(i=0;i<selfChldrn.length;i++)
{
if(!selfChldrn[i].equals(xmlChildren[i]))
return false;
}
return true;
}
public function hasAttribute(nameOrXML:*,value:String=null):Boolean
{
if(!_attributes)
return false;
var name:QName;
if(nameOrXML is XML)
{
name = nameOrXML.name();
value = nameOrXML.getValue();
}
else
{
name = new QName(nameOrXML);
}
var i:int;
var len:int = attributeLength();
for(i=0;i<len;i++)
{
if(name.matches(_attributes[i].name()))
{
if(!value)
return true;
return value == _attributes[i].getValue();
}
}
return false;
}
private function getAncestorNamespaces(namespaces:Array):Array
{
//don't modify original
namespaces = namespaces.slice();
var nsIdx:int;
var pIdx:int;
if(_parent)
{
var parentNS:Array = _parent.inScopeNamespaces();
var len:int = parentNS.length;
for(pIdx=0;pIdx<len;pIdx++)
{
var curNS:Namespace = parentNS[pIdx];
var doInsert:Boolean = true;
for(nsIdx=0;nsIdx<namespaces.length;nsIdx++)
{
if(curNS.uri == namespaces[nsIdx].uri && curNS.prefix == namespaces[nsIdx].prefix)
{
doInsert = false;
break;
}
}
if(doInsert)
namespaces.push(curNS);
}
namespaces = _parent.getAncestorNamespaces(namespaces);
}
return namespaces;
}
public function getAttributeArray():Array
{
return _attributes ? _attributes.slice() : [];
}
public function getChildrenArray():Array
{
return _children ? _children.slice() : [];
}
public function getIndexOf(elem:XML):int
{
return _children ? _children.indexOf(elem) : -1;
}
private function childrenLength():int
{
return _children ? _children.length : 0;
}
private function attributeLength():int
{
return _attributes ? _attributes.length : 0;
}
private function namespaceLength():int
{
return _namespaces ? _namespaces.length : 0;
}
private function getURI(prefix:String):String
{
var i:int;
if(!_namespaces)
return "";
var namespaces:Array = getAncestorNamespaces(_namespaces);
for(i=0;i<namespaces.length;i++)
{
if(namespaces[i].prefix == prefix)
return namespaces[i].uri;
}
return "";
}
public function getValue():String
{
return _value;
}
public function hasAncestor(obj:*):Boolean
{
if(!obj)
return false;
var parent:XML = this.parent();
while(parent)
{
if(obj == parent)
return true;
parent = parent.parent();
}
return false;
}
/**
* Checks to see whether the XML object contains complex content.
*
* @return
*
*/
public function hasComplexContent():Boolean
{
/*
When the hasComplexContent method is called on an XML object x, the following steps are taken:
1. If x.[[Class]] ∈ {"attribute", "comment", "processing-instruction", "text"}, return false
2. For each property p in x
a. If p.[[Class]] == "element", return true
3. Return false
*/
if(_nodeKind == "attribute" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "text")
return false;
var i:int;
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "element")
return true;
}
return false;
}
override public function hasOwnProperty(p:*):Boolean
{
/*
When the [[HasProperty]] method of an XML object x is called with property name P, the following steps are taken:
1. If ToString(ToUint32(P)) == P
a. Return (P == "0")
2. Let n = ToXMLName(P)
3. If Type(n) is AttributeName
a. For each a in x.[[Attributes]]
i. If ((n.[[Name]].localName == "*") or (n.[[Name]].localName == a.[[Name]].localName)) and ((n.[[Name]].uri == null) or (n.[[Name]].uri == a.[[Name]].uri))
1. Return true
b. Return false
4. For (k = 0 to x.[[Length]]-1)
a. If ((n.localName == "*") or ((x[k].[[Class]] == "element") and (x[k].[[Name]].localName == n.localName))) and ((n.uri == null) or (x[k].[[Class]] == "element") and (n.uri == x[k].[[Name]].uri)))
i. Return true
5. Return false
*/
if(parseInt(p,10).toString() == p)
return p == "0";
var name:QName = toXMLName(p);
var i:int;
var len:int
if(name.isAttribute)
{
len = attributeLength();
for(i=0;i<len;i++)
{
if(_attributes[i].name().matches(name))
return true;
}
}
else
{
len = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() != "element")
continue;
if(_children[i].name().matches(name))
return true;
}
}
return false;
}
/**
* Checks to see whether the XML object contains simple content.
*
* @return
*
*/
public function hasSimpleContent():Boolean
{
/*
When the hasSimpleContent method is called on an XML object x, the following steps are taken:
1. If x.[[Class]] ∈ {"comment", "processing-instruction"}, return false
2. For each property p in x
a. If p.[[Class]] == "element", return false
3. Return true
*/
if(_nodeKind == "comment" || _nodeKind == "processing-instruction")
return false;
var i:int;
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "element")
return false;
}
return true;
}
/**
* Lists the namespaces for the XML object, based on the object's parent.
*
* @return
*
*/
public function inScopeNamespaces():Array
{
return _namespaces ? _namespaces.slice() : [];
}
private function insertChildAt(child:XML,idx:int):void{
/*
When the [[Insert]] method of an XML object x is called with property name P and value V, the following steps are taken:
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
*/
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return;
if(!child)
return;
var parent:XML = child.parent();
if(parent)
parent.removeChild(child);
child.setParent(this);
getChildren().splice(idx,0,child);
}
/**
* Inserts the given child2 parameter after the child1 parameter in this XML object and returns the resulting object.
*
* @param child1
* @param child2
* @return
*
*/
public function insertChildAfter(child1:XML, child2:XML):XML
{
/*
When the insertChildAfter method is called on an XML object x with parameters child1 and child2, the following steps are taken:
1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return
2. If (child1 == null)
a. Call the [[Insert]] method of x with arguments "0" and child2
b. Return x
3. Else if Type(child1) is XML
a. For i = 0 to x.[[Length]]-1
i. If x[i] is the same object as child1
1. Call the [[Insert]] method of x with a
*/
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return null;
if(!child1)
{
insertChildAt(child2,0);
return child2;
}
var idx:int = getIndexOf(child1);
if(idx >= 0)
{
insertChildAt(child2,idx+1);
}
return child2;
}
/**
* Inserts the given child2 parameter before the child1 parameter in this XML object and returns the resulting object.
*
* @param child1
* @param child2
* @return
*
*/
public function insertChildBefore(child1:XML, child2:XML):XML
{
/*
When the insertChildBefore method is called on an XML object x with parameters child1 and child2, the following steps are taken:
1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return
2. If (child1 == null)
a. Call the [[Insert]] method of x with arguments ToString(x.[[Length]]) and child2
b. Return x
3. Else if Type(child1) is XML
a. For i = 0 to x.[[Length]]-1
i. If x[i] is the same object as child1
1. Call the [[Insert]] method of x with arguments ToString(i) and child2
2. Return x
4. Return
*/
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return null;
if(!child1)
{
var len:int = childrenLength();
insertChildAt(child2,len);
return child2;
}
var idx:int = getIndexOf(child1);
if(idx >= 0)
{
insertChildAt(child2,idx);
}
return child2;
}
/**
* For XML objects, this method always returns the integer 1.
*
* @return
*
*/
public function length():int
{
return 1;
}
/**
* Gives the local name portion of the qualified name of the XML object.
*
* @return
*
*/
public function localName():String
{
return name().localName;
}
private var _name:QName;
/**
* Gives the qualified name for the XML object.
*
* @return
*
*/
public function name():QName
{
if(!_name)
_name = getQName("","","",false);
return _name;
}
/**
* If no parameter is provided, gives the namespace associated with the qualified name of this XML object.
*
* @param prefix
* @return
*
*/
public function namespace(prefix:String = null):*
{
/*
When the namespace method is called on an XML object x with zero arguments or one argument prefix, the following steps are taken:
1. Let y = x
2. Let inScopeNS = { }
3. While (y is not null)
a. For each ns in y.[[InScopeNamespaces]]
i. If there exists no n ∈ inScopeNS, such that n.prefix == ns.prefix
1. Let inScopeNS = inScopeNS ∪ { ns }
b. Let y = y.[[Parent]]
4. If prefix was not specified
a. If x.[[Class]] ∈ {"text", "comment", "processing-instruction"}, return null
b. Return the result of calling the [[GetNamespace]] method of x.[[Name]] with argument inScopeNS
5. Else
a. Let prefix = ToString(prefix)
b. Find a Namespace ns ∈ inScopeNS, such that ns.prefix = prefix. If no such ns exists, let ns = undefined.
c. Return ns
*/
var i:int;
if(prefix)
{
var len:int = namespaceLength();
for(i=0;i<len;i++)
{
if(_namespaces[i].prefix == prefix)
return _namespaces[i];
}
if(_parent)
return _parent.namespace(prefix);
return null;
}
//no prefix. get the namespace of our object
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction")
return null;
return name().getNamespace(namespaceDeclarations());
}
/**
* Lists namespace declarations associated with the XML object in the context of its parent.
*
* @return
*
*/
public function namespaceDeclarations():Array
{
/*
When the namespaceDeclarations method is called on an XML object x, the following steps are taken:
1. Let a be a new Array created as if by calling the constructor, new Array()
2. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return a
3. Let y = x.[[Parent]]
4. Let ancestorNS = { }
5. While (y is not null)
a. For each ns in y.[[InScopeNamespaces]]
i. If there exists no n ∈ ancestorNS, such that n.prefix == ns.prefix
1. Let ancestorNS = ancestorNS ∪ { ns }
b. Let y = y.[[Parent]]
6. Let declaredNS = { }
7. For each ns in x.[[InScopeNamespaces]]
a. If there exists no n ∈ ancestorNS, such that n.prefix == ns.prefix and n.uri == ns.uri
i. Let declaredNS = declaredNS ∪ { ns }
8. Let i = 0
9. For each ns in declaredNS
a. Call the [[Put]] method of a with arguments ToString(i) and ns
b. Let i = i + 1
10. Return a
*/
var i:int;
var retVal:Array = [];
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return retVal;
var declaredNS:Array = _namespaces ? _namespaces.slice() : [];
var parent:XML = _parent;
while(parent)
{
var parentNS:Array = parent.inScopeNamespaces();
var idx:int;
var pIdx:int;
for(pIdx=0;i<parentNS.length;pIdx++)
{
var uri:String = parentNS[pIdx].uri;
var prefix:String = parentNS[pIdx].prefix;
for(idx=0;i<declaredNS.length;idx++)
{
if(declaredNS[idx].uri == uri && declaredNS[idx].prefix == prefix)
{
declaredNS.push(parentNS[pIdx]);
break;
}
}
}
parent = parent.parent();
}
return declaredNS;
}
private var _nodeKind:String = "element";
/**
* Specifies the type of node: text, comment, processing-instruction, attribute, or element.
* @return
*
*/
public function nodeKind():String
{
return _nodeKind;
}
/**
* For the XML object and all descendant XML objects, merges adjacent text nodes and eliminates empty text nodes.
*
* @return
*
*/
public function normalize():XML
{
var len:int = childrenLength() - 1;
var lastChild:XML;
for(var i:int=len;i>=0;i--)
{
var child:XML = _children[i];
// can we have a null child?
if(child.nodeKind() == "element")
{
child.normalize();
}
else if(child.nodeKind() == "text")
{
if(lastChild && lastChild.nodeKind() == "text")
{
child.setValue(child.s() + lastChild.s());
deleteChildAt(i+1);
}
if(!child.s())
deleteChildAt(i);
}
lastChild = child;
}
return this;
}
/**
* Returns the parent of the XML object.
*
* @return
*
*/
public function parent():*
{
return _parent;
}
public function plus(rightHand:*):*
{
var list:XMLList = new XMLList();
list.append(this);
return list.plus(rightHand);
}
private function xmlFromStringable(value:*):XML
{
var str:String = value.toString();
var xml:XML = new XML();
xml.setNodeKind("text");
xml.setValue(str);
return xml;
}
/**
* Inserts the provided child object into the XML element before any existing XML properties for that element.
* @param value
* @return
*
*/
public function prependChild(child:XML):XML
{
var childType:String = typeof child;
if(childType != "object")
child = xmlFromStringable(child);
prependChildInternal(child);
normalize();
return this;
}
private function prependChildInternal(child:*):void
{
if(child is XMLList)
{
var len:int = child.length();
for(var i:int=0; i<len; i++)
{
prependChildInternal(child[0]);
}
}
else
{
assertType(child,XML,"Type must be XML");
child.setParent(this);
getChildren().unshift(child);
}
}
/**
* If a name parameter is provided, lists all the children of the XML object that contain processing instructions with that name.
*
* @param name
* @return
*
*/
public function processingInstructions(name:String = "*"):XMLList
{
var i:int;
var list:XMLList = new XMLList();
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "processing-instruction")
list.append(_children[i]);
}
list.targetObject = this;
return list;
}
/**
* Removes the given chid for this object and returns the removed child.
*
* @param child
* @return
*
*/
public function removeChild(child:XML):Boolean
{
/*
When the [[Delete]] method of an XML object x is called with property name P, the following steps are taken:
1. If ToString(ToUint32(P)) == P, throw a TypeError exception
NOTE this operation is reserved for future versions of E4X.
2. Let n = ToXMLName(P)
3. If Type(n) is AttributeName
a. For each a in x.[[Attributes]]
i. If ((n.[[Name]].localName == "*") or (n.[[Name]].localName == a.[[Name]].localName)) and ((n.[[Name]].uri == null) or (n.[[Name]].uri == a.[[Name]].uri))
1. Let a.[[Parent]] = null
2. Remove the attribute a from x.[[Attributes]]
b. Return true
4. Let dp = 0
5. For q = 0 to x.[[Length]]-1
a. If ((n.localName == "*") or (x[q].[[Class]] == "element" and x[q].[[Name]].localName == n.localName)) and ((n.uri == null) or (x[q].[[Class]] == “element” and n.uri == x[q].[[Name]].uri ))
i. Let x[q].[[Parent]] = null
ii. Remove the property with the name ToString(q) from x
iii. Let dp = dp + 1
b. Else
i. If dp > 0, rename property ToString(q) of x to ToString(q – dp)
6. Let x.[[Length]] = x.[[Length]] - dp
7. Return true.
*/
var i:int;
var removed:XML;
if(!child)
return false;
if(!(child is XML))
return removeChildByName(child);
if(child.nodeKind() == "attribute")
{
var len:int = attributeLength();
for(i=0;i<len;i++)
{
if(child.equals(_attributes[i]))
{
removed = _attributes[i];
removed._parent = null;
_attributes.splice(i,1);
return true;
}
}
return false;
}
var idx:int = getIndexOf(child);
if(idx < 0)
return false;
removed = _children.splice(idx,1);
child._parent = null;
return true;
}
private function removeChildByName(name:*):Boolean
{
var i:int;
var len:int;
name = toXMLName(name);
var child:XML = null;
var removedItem:Boolean = false;
if(name.isAttribute)
{
len = attributeLength() -1;
for(i=len;i>=0;i--)
{
if(_attributes[i].name().matches(name))
{
child = _attributes[i];
child._parent = null;
_attributes.splice(i,1);
removedItem = true;
}
}
return removedItem;
}
//QUESTION am I handling non-elements correctly?
len = childrenLength() - 1;
for(i=len;i>=0;i--)
{
if(_children[i].name().matches(name))
{
child = _children[i];
child._parent = null;
_children.splice(i,1);
removedItem = true;
}
}
return removedItem;
}
public function removeChildAt(index:int):void
{
/*
When the [[DeleteByIndex]] method of an XML object x is called with property name P, the following steps are taken:
1. Let i = ToUint32(P)
2. If ToString(i) == P
a. If i is less than x.[[Length]]
i. If x has a property with name P
1. Let x[P].[[Parent]] = null
2. Remove the property with the name P from x
ii. For q = i+1 to x.[[Length]]-1
1. Rename property ToString(q) of x to ToString(q – 1)
iii. Let x.[[Length]] = x.[[Length]] – 1
b. Return true
3. Else throw a TypeError exception
*/
//Do nothing for XML objects?
throw new Error("Cannot call delete on XML");
}
/**
* Removes the given namespace for this object and all descendants.
*
* @param ns
* @return
*
*/
public function removeNamespace(ns:*):XML
{
/*
Overview
The removeNamespace method removes the given namespace from the in scope namespaces of this object and all its descendents,
then returns a copy of this XML object. The removeNamespaces method will not remove a namespace from an object where it is referenced
by that object’s QName or the QNames of that object’s attributes.
Semantics
When the removeNamespace method is called on an XML object x with parameter namespace, the following steps are taken:
1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return x
2. Let ns be a Namespace object created as if by calling the function Namespace( namespace )
3. Let thisNS be the result of calling [[GetNamespace]] on x.[[Name]] with argument x.[[InScopeNamespaces]]
4. If (thisNS == ns), return x
5. For each a in x.[[Attributes]]
a. Let aNS be the result of calling [[GetNamespace]] on a.[[Name]] with argument x.[[InScopeNamespaces]]
b. If (aNS == ns), return x
6. If ns.prefix == undefined
a. If there exists a namespace n ∈ x.[[InScopeNamespaces]], such that n.uri == ns.uri, remove the namespace n from x.[[InScopeNamespaces]]
7. Else
a. If there exists a namespace n ∈ x.[[InScopeNamespaces]], such that n.uri == ns.uri and n.prefix == ns.prefix, remove the namespace n from x.[[InScopeNamespaces]]
8. For each property p of x
a. If p.[[Class]] = "element", call the removeNamespace method of p with argument ns
9. Return x
*/
var i:int;
var len:int;
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return this;
if(!(ns is Namespace))
ns = new Namespace(ns);
if(ns == name().getNamespace(_namespaces))
return this;
len = attributeLength();
for(i=0;i<len;i++)
{
if(ns == _attributes[i].name().getNamespace(_namespaces))
return this;
}
//
len = namespaceLength();
for(i=len-1;i>=0;i--)
{
if(_namespaces[i].uri == ns.uri && _namespaces[i].prefix == ns.prefix)
_namespaces.splice(i,1);
else if(ns.prefix == null && _namespaces[i].uri == ns.uri)
_namespaces.splice(i,1);
}
len = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "element")
_children[i].removeNamespace(ns);
}
return this;
}
/**
* Replaces the properties specified by the propertyName parameter with the given value parameter.
*
* @param propertyName
* @param value
* @return
*
*/
public function replace(propertyName:Object, value:*):*
{
/*
Semantics
When the replace method is called on an XML object x with parameters propertyName and value, the following steps are taken:
1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return x
2. If Type(value) ∉ {XML, XMLList}, let c = ToString(value)
3. Else let c be the result of calling the [[DeepCopy]] method of value
4. If ToString(ToUint32(P)) == P
a. Call the [[Replace]] method of x with arguments P and c and return x
5. Let n be a QName object created as if by calling the function QName(P)
6. Let i = undefined
7. For k = x.[[Length]]-1 downto 0
a. If ((n.localName == "*") or ((x[k].[[Class]] == "element") and (x[k].[[Name]].localName==n.localName))) and ((n.uri == null) or ((x[k].[[Class]] == "element") and (n.uri == x[k].[[Name]].uri )))
i. If (i is not undefined), call the [[DeleteByIndex]] method of x with argument ToString(i)
ii. Let i = k
8. If i == undefined, return x
9. Call the [[Replace]] method of x with arguments ToString(i) and c
10. Return x
*/
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
{
// Changing this to pretend we're a string
return s().replace(propertyName,value);
//return this;
}
if(value === null || value === undefined)
return this;
if((value is XML) || (value is XMLList))
value = value.copy();
else
value = value.toString();
return null;
}
public function replaceChildAt(idx:int,v:*):void
{
/*
When the [[Replace]] method of an XML object x is called with property name P and value V, the following steps are taken:
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 i is greater than or equal to x.[[Length]],
a. Let P = ToString(x.[[Length]])
b. Let x.[[Length]] = x.[[Length]] + 1
5. If Type(V) is XML and V.[[Class]] ∈ {"element", "comment", "processing-instruction", "text"}
a. If V.[[Class]] is “element” and (V is x or an ancestor of x) throw an Error exception
b. Let V.[[Parent]] = x
c. If x has a property with name P
i. Let x[P].[[Parent]] = null
d. Let x[P] = V
6. Else if Type(V) is XMLList
a. Call the [[DeleteByIndex]] method of x with argument P
b. Call the [[Insert]] method of x with arguments P and V
7. Else
a. Let s = ToString(V)
b. Create a new XML object t with t.[[Class]] = "text", t.[[Parent]] = x and t.[[Value]] = s
c. If x has a property with name P
i. Let x[P].[[Parent]] = null
d. Let the value of property P of x be t
8. Return
*/
var len:int;
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction" || _nodeKind == "attribute")
return;
len = childrenLength();
if(idx > len)
idx = len;
// make sure _children exist
getChildren();
if(v is XML && v.nodeKind() != "attribute")
{
if(v.nodeKind() == "element" && (v==this || isAncestor(v)) )
throw new TypeError("cannot assign parent xml as child");
v.setParent(this);
if(_children[idx])
removeChild(_children[idx]);
insertChildAt(v,idx);
}
else if(v is XMLList)
{
//6.
if(_children[idx])
_children[idx]._parent = null;
len = v.length();
v[0].setParent(this);
_children[idx] = v[0];
var listIdx:int = 1;
var chld:XML = v[0];
while(listIdx < len)
{
chld = v[listIdx];
insertChildAt(chld,idx+listIdx);
listIdx++;
}
}
else
{
//7. attribute?
}
}
private function isAncestor(xml:XML):Boolean
{
var p:XML = parent();
while(p)
{
if(p == xml)
return true;
p = p.parent();
}
return false;
}
public function setAttribute(attr:*,value:String):String
{
var i:int;
//make sure _attributes is not null
getAttributes();
if(attr is XML)
{
if(attr.nodeKind() == "attribute")
{
var len:int = attributeLength();
for(i=0;i<len;i++)
{
if(_attributes[i].name().equals(attr.name()))
{
_attributes[i].setValue(value);
return value;
}
//addChild(_att)
}
if(value)
attr.setValue(value);
addChild(attr);
}
return value;
}
if(attr.indexOf("xmlns") == 0)
{
//it's a namespace declaration
//TODO This does not seem right.
var ns:Namespace = new Namespace(value.toString());
if(attr.indexOf("xmlns:") == 0)// it has a prefix
ns.prefix = attr.split(":")[1];
this.addNamespace(ns);
}
else
{
//it's a regular attribute string
//TODO use toXMLName or toAttributeName to convert attr and assing it
var qname:QName = toAttributeName(attr);
var attrXML:XML = new XML();
attrXML.setNodeKind("attribute");
attrXML.setName(toAttributeName(attr));
attrXML.setValue(value);
len = attributeLength();
for(i=0;i<len;i++)
{
if(_attributes[i].name().equals(attrXML.name()))
{
_attributes[i].setValue(value);
return value;
}
//addChild(_att)
}
addChild(attrXML);
}
return value;
}
/**
* Replaces the child properties of the XML object with the specified name with the specified XML or XMLList.
* This is primarily used to support dot notation assignment of XML.
*
* @param value
* @return
*
*/
public function setChild(elementName:*, elements:Object):Object
{
/*
*
1. If ToString(ToUint32(P)) == P, throw a TypeError exception NOTE this operation is reserved for future versions of E4X.
2. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return
3. If (Type(V) ∉ {XML, XMLList}) or (V.[[Class]] ∈ {"text", "attribute"})
a. Let c = ToString(V)
4. Else
a. Let c be the result of calling the [[DeepCopy]] method of V
5. Let n = ToXMLName(P)
6. If Type(n) is AttributeName
a. Call the function isXMLName (section 13.1.2.1) with argument n.[[Name]] and if the result is false, return
b. If Type(c) is XMLList
i. If c.[[Length]] == 0, let c be the empty string
ii. Else
1. Let s = ToString(c[0])
2. For i = 1 to c.[[Length]]-1
a. Let s be the result of concatenating s, the string " " (space) and ToString(c[i])
3. Let c = s
c. Else
i. Let c = ToString(c)
d. Let a = null
e. For each j in x.[[Attributes]]
i. If (n.[[Name]].localName == j.[[Name]].localName) and ((n.[[Name]].uri == null) or (n.[[Name]].uri == j.[[Name]].uri))
1. If (a == null), a = j
2. Else call the [[Delete]] method of x with argument j.[[Name]]
f. If a == null
i. If n.[[Name]].uri == null
1. Let nons be a new Namespace created as if by calling the constructor new Namespace()
2. Let name be a new QName created as if by calling the constructor new QName(nons, n.[[Name]])
ii. Else
1. Let name be a new QName created as if by calling the constructor new QName(n.[[Name]])
iii. Create a new XML object a with a.[[Name]] = name, a.[[Class]] == "attribute" and a.[[Parent]] = x
iv. Let x.[[Attributes]] = x.[[Attributes]] ∪ { a }
v. Let ns be the result of calling the [[GetNamespace]] method of name with no arguments
vi. Call the [[AddInScopeNamespace]] method of x with argument ns
g. Let a.[[Value]] = c
h. Return
7. Let isValidName be the result of calling the function isXMLName (section 13.1.2.1) with argument n
8. If isValidName is false and n.localName is not equal to the string "*", return
9. Let i = undefined
10. Let primitiveAssign = (Type(c) ∉ {XML, XMLList}) and (n.localName is not equal to the string "*")
11. For (k = x.[[Length]]-1 downto 0)
a. If ((n.localName == "*") or ((x[k].[[Class]] == "element") and (x[k].[[Name]].localName==n.localName))) and ((n.uri == null) or ((x[k].[[Class]] == “element”) and (n.uri == x[k].[[Name]].uri )))
i. If (i is not undefined), call the [[DeleteByIndex]] property of x with argument ToString(i)
ii. Let i = k
12. If i == undefined
a. Let i = x.[[Length]]
b. If (primitiveAssign == true)
i. If (n.uri == null)
1. Let name be a new QName created as if by calling the constructor new QName(GetDefaultNamespace(), n)
ii. Else
1. Let name be a new QName created as if by calling the constructor new QName(n)
iii. Create a new XML object y with y.[[Name]] = name, y.[[Class]] = "element" and y.[[Parent]] = x
iv. Let ns be the result of calling [[GetNamespace]] on name with no arguments
v. Call the [[Replace]] method of x with arguments ToString(i) and y
vi. Call [[AddInScopeNamespace]] on y with argument ns
13. If (primitiveAssign == true)
a. Delete all the properties of the XML object x[i]
b. Let s = ToString(c)
c. If s is not the empty string, call the [[Replace]] method of x[i] with arguments "0" and s
14. Else
a. Call the [[Replace]] method of x with arguments ToString(i) and c
15. Return
*/
var i:int;
var len:int;
var chld:XML;
var retVal:Object = elements;
var chldrn:XMLList;
var childIdx:int;
// I'm not sure that this a strict interpretation of the spec but I think this does the "right thing".
var childType:String = typeof elements;
if(childType != "object")
{
var stringable:XML = xmlFromStringable(elements);
chldrn = this.child(elementName);
childIdx = children().length() -1;
if(chldrn.length())
childIdx = chldrn[0].childIndex()-1;
else
{
chld = new XML("<" + elementName + "/>");
chld.appendChild(stringable);
prependChild(chld);
return chld;
}
len = chldrn.length() -1;
for (i= len; i > 0; i--)
{
removeChild(chldrn[i]);
}
chld = chldrn[i];
chld.setChildren(stringable);
return chld;
}
if(elements is XML)
{
var list:XMLList = new XMLList();
list[0] = elements;
elements = list;
}
if(elements is XMLList)
{
chldrn = this.child(elementName);
childIdx = children().length() -1;
if(chldrn.length())
childIdx = chldrn[0].childIndex()-1;
len = chldrn.length() -1;
for (i= len; i >= 0; i--)
{
removeChild(chldrn[i]);
// remove the nodes
// remove the children
// adjust the childIndexes
}
var curChild:XML = getChildren()[childIdx];
// Now add them in.
len = elements.length();
for(i=0;i<len;i++)
{
chld = elements[i];
if(!curChild)
{
curChild = chld
if(childIdx < 0)
prependChild(chld);
else
appendChild(chld);
}
else {
insertChildAfter(curChild, chld);
curChild = chld;
}
}
}
//what to do if it's not XML or XMLList? Throw an error? Ignore?
return retVal;
}
/**
* Replaces the child properties of the XML object with the specified set of XML properties, provided in the value parameter.
*
* @param value
* @return
*
*/
public function setChildren(value:Object):XML
{
var i:int;
var len:int;
var chld:XML;
if(value is XML)
{
var list:XMLList = new XMLList();
list[0] = value;
value = list;
}
if(value is XMLList)
{
// remove all existing elements
var chldrn:XMLList = children();
var childIdx:int = chldrn.length() -1;
if(chldrn.length())
childIdx = chldrn[0].childIndex();
len = chldrn.length() -1;
for (i= len; i >= 0; i--)
{
removeChild(chldrn[i]);
// remove the nodes
// remove the children
// adjust the childIndexes
}
var curChild:XML = getChildren()[childIdx];
// Now add them in.
len = value.length();
for(i=0;i<len;i++)
{
chld = value[i];
if(!curChild)
{
curChild = chld;
appendChild(chld);
}
else {
insertChildAfter(curChild, chld);
curChild = chld;
}
}
}
return this;
}
/**
* Changes the local name of the XML object to the given name parameter.
*
* @param name
*
*/
public function setLocalName(name:String):void
{
if(!_name)
_name = new QName();
_name = getQName(name,_name.prefix,_name.uri,_name.isAttribute)
// _name.localName = name;
}
/**
* Sets the name of the XML object to the given qualified name or attribute name.
*
* @param name
*
*/
public function setName(name:*):void
{
var nameRef:QName;
if(name is QName)
nameRef = name;
else
nameRef = new QName(name);
_name = getQName(nameRef.localName,nameRef.prefix,nameRef.uri,nameRef.isAttribute);
}
/**
* Sets the namespace associated with the XML object.
*
* @param ns
*
*/
public function setNamespace(ns:Object):void
{
if(_nodeKind == "text" || _nodeKind == "comment" || _nodeKind == "processing-instruction")
return;
var ns2:Namespace = new Namespace(ns);
var nameRef:QName = new QName(ns2,name());
if(_nodeKind == "attribute")
{
nameRef.isAttribute = true;
if(_parent == null)
return;
_parent.addNamespace(ns2);
}
_name = getQName(nameRef.localName,nameRef.prefix,nameRef.uri,nameRef.isAttribute);
if(_nodeKind == "element")
addNamespace(ns2);
}
/**
* @private
*
*/
public function setNodeKind(value:String):void
{
// memory optimization. The default on the prototype is "element" and using the prototype saves memory
if(_nodeKind != value)
_nodeKind = value;
}
public function setParent(parent:XML):void
{
if(parent == _parent)
return;
var oldParent:XML = _parent;
if(oldParent)
oldParent.removeChild(this);
_parent = parent;
}
public function setValue(value:String):void
{
_value = value;
}
/**
* @private
*
* Allows XMLList to get the targetObject of its targetObject and not error when it gets the XML
*/
public function get targetObject():*
{
return null;
}
/**
* Returns an XMLList object of all XML properties of the XML object that represent XML text nodes.
*
* @return
*
*/
public function text():XMLList
{
var list:XMLList = new XMLList();
var i:int;
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "text")
list.append(_children[i]);
}
list.targetObject = this;
return list;
}
/**
* Provides an overridable method for customizing the JSON encoding of values in an XML object.
*
* @param k
* @return
*
*/
/*
override public function toJSON(k:String):String
{
return this.name();
}
*/
/**
* Returns a string representation of the XML object.
*
* @return
*
*/
public function toString():String
{
var i:int;
// text, comment, processing-instruction, attribute, or element
if(_nodeKind == "text" || _nodeKind == "attribute")
return _value;
if(_nodeKind == "comment")
return "";
if(_nodeKind == "processing-instruction")
return "";
if(this.hasSimpleContent())
{
var s:String = "";
var len:int = childrenLength();
for(i=0;i<len;i++)
{
if(_children[i].nodeKind() == "comment" || _children[i].nodeKind() == "processing-instruction")
continue;
s = s + _children[i].toString();
}
return s;
}
return toXMLString();
}
private function toAttributeName(name:*):QName
{
var qname:QName;
if(!(name is QName))
{
name = name.toString();
if(name.indexOf("@") > -1)
name = name.substring(name.indexOf("@") + 1);
}
qname = toXMLName(name);
qname.isAttribute = true;
return qname;
}
private function toXMLName(name:*):QName
{
var qname:QName;
if(name.toString().indexOf("@") > -1)
return toAttributeName(name);
/*
Given a string s, the ToXMLName conversion function returns a QName object or AttributeName. If the first character of s is "@", ToXMLName creates an AttributeName using the ToAttributeName operator. Otherwise, it creates a QName object using the QName constructor.
Semantics
Given a String value s, ToXMLName operator converts it to a QName object or AttributeName using the following steps:
1. If ToString(ToUint32(s)) == ToString(s), throw a TypeError exception
2. If the first character of s is "@"
a. Let name = s.substring(1, s.length)
b. Return ToAttributeName(name)
3. Else
a. Return a QName object created as if by calling the constructor new QName(s)
*/
if(parseInt(name,10).toString() == name)
throw new TypeError("invalid element name");
if(!name is QName)
{
name = name.toString();
if(name.indexOf(":") >= 0)
{
// Get the QName for prefix
qname = new QName();
qname.prefix = name.substring(0,name.indexOf(":"));
qname.localName = name.substring(name.lastIndexOf(":")+1);
//get the qname uri
qname.uri = getURI(qname.prefix);
}
else
{
qname = new QName(name());
if(!qname.uri && defaultNamespace)
{
qname = new QName(defaultNamespace);
}
qname.localName = name;
}
}
else
{
qname = new QName(name);
}
return qname;
}
/**
* Returns a string representation of the XML object.
*
* @return
*
*/
public function toXMLString(indentLevel:int=0,ancestors:Array=null):String
{
/*
Given an XML object x and an optional argument AncestorNamespaces and an optional argument IndentLevel, ToXMLString converts it to an XML encoded string s by taking the following steps:
1. Let s be the empty string
2. If IndentLevel was not provided, Let IndentLevel = 0
3. If (XML.prettyPrinting == true)
a. For i = 0 to IndentLevel-1, let s be the result of concatenating s and the space <SP> character
4. If x.[[Class]] == "text",
a. If (XML.prettyPrinting == true)
i. Let v be the result of removing all the leading and trailing XMLWhitespace characters from x.[[Value]]
ii. Return the result of concatenating s and EscapeElementValue(v)
b. Else
i. Return EscapeElementValue(x.[[Value]])
5. If x.[[Class]] == "attribute", return the result of concatenating s and EscapeAttributeValue(x.[[Value]])
6. If x.[[Class]] == "comment", return the result of concatenating s, the string "<!--", x.[[Value]] and the string "-->"
7. If x.[[Class]] == "processing-instruction", return the result of concatenating s, the string "<?", x.[[Name]].localName, the space <SP> character, x.[[Value]] and the string "?>"
8. If AncestorNamespaces was not provided, let AncestorNamespaces = { }
9. Let namespaceDeclarations = { }
10. For each ns in x.[[InScopeNamespaces]]
a. If there is no ans ∈ AncestorNamespaces, such that ans.uri == ns.uri and ans.prefix == ns.prefix
i. Let ns1 be a copy of ns
ii. Let namespaceDeclarations = namespaceDeclarations ∪ { ns1 } NOTE implementations may also exclude unused namespace declarations from namespaceDeclarations
11. For each name in the set of names consisting of x.[[Name]] and the name of each attribute in x.[[Attributes]]
a. Let namespace be a copy of the result of calling [[GetNamespace]] on name with argument (AncestorNamespaces ∪ namespaceDeclarations)
b. If (namespace.prefix == undefined),
i. Let namespace.prefix be an arbitrary implementation defined namespace prefix, such that there is no ns2 ∈ (AncestorNamespaces ∪ namespaceDeclarations) with namespace.prefix == ns2.prefix
ii. Note: implementations should prefer the empty string as the implementation defined prefix if it is not already used in the set (AncestorNamespaces ∪ namespaceDeclarations)
iii. Let namespaceDeclarations = namespaceDeclarations ∪ { namespace }
12. Let s be the result of concatenating s and the string "<"
13. If namespace.prefix is not the empty string,
a. Let s be the result of concatenating s, namespace.prefix and the string ":"
14. Let s be the result of concatenating s and x.[[Name]].localName
15. Let attrAndNamespaces = x.[[Attributes]] ∪ namespaceDeclarations
16. For each an in attrAndNamespaces
a. Let s be the result of concatenating s and the space <SP> character
b. If Type(an) is XML and an.[[Class]] == "attribute"
i. Let ans be a copy of the result of calling [[GetNamespace]] on a.[[Name]] with argument AncestorNamespaces
ii. If (ans.prefix == undefined),
1. Let ans.prefix be an arbitrary implementation defined namespace prefix, such that there is no ns2 ∈ (AncestorNamespaces ∪ namespaceDeclarations) with ans.prefix == ns2.prefix
2. If there is no ns2 ∈ (AncestorNamespaces ∪ namespaceDeclarations), such that ns2.uri == ans.uri and ns2.prefix == ans.prefix
a. Let namespaceDeclarations = namespaceDeclarations ∪ { ans }
iii. If ans.prefix is not the empty string
1. Let s be the result of concatenating s, namespace.prefix and the string ":"
iv. Let s be the result of concatenating s and a.[[Name]].localName
c. Else
i. Let s be the result of concatenating s and the string "xmlns"
ii. If (an.prefix == undefined),
1. Let an.prefix be an arbitrary implementation defined namespace prefix, such that there is no ns2 ∈ (AncestorNamespaces ∪ namespaceDeclarations) with an.prefix == ns2.prefix
iii. If an.prefix is not the empty string
1. Let s be the result of concatenating s, the string ":" and an.prefix
d. Let s be the result of concatenating s, the string "=" and a double-quote character (i.e. Unicode codepoint \u0022)
e. If an.[[Class]] == "attribute"
i. Let s be the result of concatenating s and EscapeAttributeValue(an.[[Value]])
f. Else
i. Let s be the result of concatenating s and EscapeAttributeValue(an.uri)
g. Let s be the result of concatenating s and a double-quote character (i.e. Unicode codepoint \u0022)
17. If x.[[Length]] == 0
a. Let s be the result of concatenating s and "/>"
b. Return s
18. Let s be the result of concatenating s and the string ">"
19. Let indentChildren = ((x.[[Length]] > 1) or (x.[[Length]] == 1 and x[0].[[Class]] is not equal to "text"))
20. If (XML.prettyPrinting == true and indentChildren == true)
a. Let nextIndentLevel = IndentLevel + XML.PrettyIndent.
21. Else
a. Let nextIndentLevel = 0
22. For i = 0 to x.[[Length]]-1
a. If (XML.prettyPrinting == true and indentChildren == true)
i. Let s be the result of concatenating s and a LineTerminator
b. Let child = ToXMLString (x[i], (AncestorNamespaces ∪ namespaceDeclarations), nextIndentLevel)
c. Let s be the result of concatenating s and child
23. If (XML.prettyPrinting == true and indentChildren == true),
a. Let s be the result of concatenating s and a LineTerminator
b. For i = 0 to IndentLevel, let s be the result of concatenating s and a space <SP> character
24. Let s be the result of concatenating s and the string "</"
25. If namespace.prefix is not the empty string
a. Let s be the result of concatenating s, namespace.prefix and the string ":"
26. Let s be the result of concatenating s, x.[[Name]].localName and the string ">"
27. Return s
NOTE Implementations may also preserve insignificant whitespace (e.g., inside and between element tags) and attribute quoting conventions in ToXMLString().
*/
var i:int;
var len:int;
var ns:Namespace;
var strArr:Array = [];
indentLevel = isNaN(indentLevel) ? 0 : indentLevel;
var indentArr:Array = [];
for(i=0;i<indentLevel;i++)
indentArr.push(_indentStr);
var indent:String = indentArr.join("");
if(this.nodeKind() == "text")
{
if(prettyPrinting)
{
var v:String = trimXMLWhitespace(_value);
if(name().localName == "#cdata-section")
return indent + v;
return indent + escapeElementValue(v);
}
if(name().localName == "#cdata-section")
return _value;
return escapeElementValue(_value);
}
if(this.nodeKind() == "attribute")
return indent + escapeAttributeValue(_value);
if(this.nodeKind() == "comment")
return indent + "<!--" + _value + "-->";
if(this.nodeKind() == "processing-instruction")
return indent + "<?" + name().localName + " " + _value + "?>";
// We excluded the other types, so it's a normal element
// step 8.
//ancestors
if(!ancestors)
ancestors = [];
var declarations:Array = [];
len = namespaceLength();
for(i=0;i<len;i++)
{
if(!namespaceInArray(_namespaces[i],ancestors))
declarations.push(new Namespace(_namespaces[i]));
}
//11
len = attributeLength();
for(i=0;i<len;i++)
{
ns = new Namespace(_attributes[i].name().getNamespace(ancestors.concat(declarations)));
if(ns.prefix === null)
{
ns.prefix = "";
declarations.push(ns);
}
}
ns = new Namespace(name().getNamespace(ancestors.concat(declarations)));
if(ns.prefix === null)
{
ns.prefix = "";
declarations.push(ns);
}
if(XML.prettyPrinting)
{
strArr.push(new Array(indentLevel).join(' '));
}
strArr.push("<");
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++)
{
strArr.push(" ");
// the following seems to be the spec, but it does not make sense to me.
//var ans:Namespace = _attributes[i].name().getNamespace(ancestors);
var aName:QName = _attributes[i].name();
var ans:Namespace = aName.getNamespace(ancestors.concat(declarations));
if(ans.prefix)
{
strArr.push(ans.prefix);
strArr.push(":");
}
strArr.push(aName.localName);
strArr.push('="');
strArr.push(escapeAttributeValue(_attributes[i].getValue()));
strArr.push('"');
}
// now write elements or close the tag if none exist
len = childrenLength();
if(len == 0)
{
strArr.push("/>");
return strArr.join("");
}
strArr.push(">");
var indentChildren:Boolean = len > 1 || (len == 1 && _children[0].nodeKind() != "text");
var nextIndentLevel:int;
if(XML.prettyPrinting && indentChildren)
nextIndentLevel = indentLevel + prettyIndent;
else
nextIndentLevel = 0;
for(i=0;i<len;i++)
{
//
if(XML.prettyPrinting && indentChildren)
strArr.push("\n");
strArr.push(_children[i].toXMLString(nextIndentLevel,ancestors.concat(declarations)));
}
if(XML.prettyPrinting && indentChildren)
{
strArr.push("\n");
strArr.push(new Array(indentLevel + 1).join(' '));
}
strArr.push("</");
if(ns.prefix)
{
strArr.push(ns.prefix);
strArr.push(":");
}
strArr.push(name().localName);
strArr.push(">");
return strArr.join("");
}
/**
* Returns the XML object.
*
* @return
*
*/
override public function valueOf():*
{
var str:String = this.toString();
if(str == "")
return str;
var num:Number = Number(str);
return isNaN(num) ? str : num;
}
////////////////////////////////////////////////////////////////
///
///
/// METHODS to allow XML to behave as if it's a string or number
///
///
////////////////////////////////////////////////////////////////
public function charAt(index:Number):String
{
return s().charAt(index);
}
public function charCodeAt(index:Number):Number
{
return s().charCodeAt(index);
}
public function codePointAt(pos:Number):Number
{
return s().codePointAt(pos);
}
/*
public function concat(... args):Array
{
return s().concat(args);
}
*/
public function indexOf(searchValue:String,fromIndex:Number=0):Number
{
return s().indexOf(searchValue,fromIndex);
}
public function lastIndexOf(searchValue:String,fromIndex:Number=0):Number
{
return s().lastIndexOf(searchValue,fromIndex);
}
public function localeCompare(compareString:String,locales:*=undefined, options:*=undefined):Number
{
return s().localeCompare(compareString,locales,options);
}
public function match(regexp:*):Array
{
return s().match(regexp);
}
/*
Moved this logic (partially) into the other replace method
public function replace(regexp:*,withStr:*):String
{
return s().replace(regexp,withStr);
}
*/
public function search(regexp:*):Number
{
return s().search(regexp);
}
public function slice(beginSlice:Number, endSlice:*=undefined):String
{
return s().slice(beginSlice,endSlice);
}
public function split(separator:*=undefined,limit:*=undefined):Array
{
return s().split(separator,limit);
}
public function substr(start:Number, length:*=undefined):String
{
return s().substr(start,length);
}
public function substring(indexStart:Number, indexEnd:*=undefined):String
{
return s().substring(indexStart,indexEnd);
}
public function toLocaleLowerCase():String
{
return s().toLocaleLowerCase();
}
public function toLocaleUpperCase():String
{
return s().toLocaleUpperCase();
}
public function toLowerCase():String
{
return s().toLowerCase();
}
public function toUpperCase():String
{
return s().toUpperCase();
}
public function trim():String
{
return s().trim();
}
// Number methods
/**
* @royaleignorecoercion Number
*/
public function toExponential(fractionDigits:*=undefined):Number
{
return v().toExponential(fractionDigits) as Number;
}
/**
* @royaleignorecoercion Number
*/
public function toFixed(digits:*=undefined):Number
{
return v().toFixed(digits) as Number;
}
/**
* @royaleignorecoercion Number
*/
public function toPrecision(precision:*=undefined):Number
{
return v().toPrecision(precision) as Number;
}
private function s():String
{
return this.toString();
}
private function v():Number
{
return Number(s());
}
}
}