AS3 E4X Rundown

by

View this article in other languages:

I’ve been using E4X in ActionScript 3.0 for a while now and the rumors of its simplicity have been greatly exaggerated. A lot of operations are easy and make sense, but others are less… obvious. E4X is essentially a whole new language which is part of the AS3 grammar, just as regular expressions have their own completely different language but exist within ActionScript 3.0. And being so new, there really isn’t a whole lot of documentation out there. This is further complicated by the fact that since E4X seems simple enough at first glance to explain in a few lines, a lot of documentation stops short.

Anyway. I’ve been infuriated by E4X more than a few times now, and I’d like to start a running post to demystify some of it. Please! use the comments to ask any questions you might have, and I’ll keep adding to this post. Hopefully this can turn into a decent resource for E4X lore. The fun begins after the cut.

For the following post, let’s use this XML block as an example:

var thePeople:XML = <people>
    <person name="Mims Wright" suffix="III">
        <age>27</age>
        <aka>Mims H Wright</aka>
        <aka>Teh AWesoeomes!</aka>
        <bio><![CDATA[This guy <b>rulz<b>!]]></bio>
    </person>
    <person name="Roger Braunstein">
        <age>26</age>
        <aka>Rog</aka>
        <aka>That guy</aka>
        <bio><![CDATA[Likes food.]]></bio>
    </person>
</people>;

XMLList vs XML

Ok, first thing you notice is that you can just type in XML and it becomes an XML typed variable. You should also know that this XML class is not the same as the XML class in AS2. All new!

Second thing you should know is that there are two principally related classes: XML and XMLList. XML is well-formed XML (mostly; see Node Types for exceptions). XMLList is a list of XML nodes. The difference here is that XML must always have one single root node; XMLLists have multiple nodes at the base depth. For instance, the whole example above is XML because it is typed as XML and it has a single root node, <people>. In contrast, an XMLList is composed of zero or more root nodes, like:

<age>27</age>
<age>26</age>

This can’t be XML because there are two root nodes. You can, however, think of it as a list of XML, since each <age> node is itself a single root node. It’s important to note that an XMLList can also have one (like XML) or zero nodes.

Anyway, most of the basic operations for E4X are filtering operations. You take the XML and find some subset of the XML. Often, you want to find a particular node. Any filtering you do is going to result in a number of possible nodes, so you will notice soon that XML always turns into XMLList when you filter it. Most of the time you’ll be dealing with XMLLists. The same kinds of filters are available on both classes, except where noted.

The Basics

These are the basics of E4X. You’ve probably figured these ones out already cuz you is a pimp!

thePeople.person.age

Use dot syntax to select child nodes by name. The variable is already associated with the root node so there’s no need to write <people> into the expression. The expression above has two successive filters. The first one gets you all the <person> nodes immediately below the root node, in other words, an XMLList. The second one gets you all the <age> nodes below all the nodes in the first filter. Result:

<age>27</age>
<age>26</age>

Again, this is an XMLList, and notice how it retrieves all the age nodes under all the person nodes.

thePeople.person.@name

Use the @ symbol to select attributes instead of child nodes. This example is two subsequent filters. The first finds all the <person> nodes under the root, and the second finds all the name attributes of those nodes. This time, the result is not a list of nodes but a list of attributes. It gets weird here, but the result is an XMLList of attributes. This sure isn’t valid XML, but we’ll talk more about node types later.

thePeople.person.(age >= 21)

Possibly your most powerful tool, a parenthetical can filter nodes based on arbitrary criteria. This expression returns an XMLList with both Mims and I because we’re both totally legal drinking age AWESOME! Another expression you could create:

thePeople.person.(@name.charAt(0) == "R"); //Roger's <person> node
thePeople.person[1]

Use square brackets to get XML nodes out of XMLLists, like an indexed array. This is your way to get from XMLList to XML. Result:

<person name="Roger Braunstein">
    <age>26</age>
    <aka>Rog</aka>
    <aka>That guy</aka>
    <bio><![CDATA[Likes food.]]></bio>
</person>

Also, since all filters available to you return all possible matches, when searching out a specific node you’ll use this all the time. For instance, you can create a sequence of filters that should only have one match, but you might find yourself picking out the first result of every test. E.g.,

thePeople.person.(@name == "Roger Braunstein").age; //XMLList with one node
thePeople.person.(@name == "Roger Braunstein")[0].age[0]; //XML

I think it’s kind of unfortunate that there’s no shortcut to pick the only result of a filter, because complex E4X expressions can frequently become polluted with [0]s. Alternately, you can just place one at the end of a chain of filters. In the second example above, the expression goes from XML→XMLList→XMLList→XML→XMLList→XML.

thePeople..age

Using two dots instead of one (..) finds all the applicable nodes that are at any depth rather than at the the next depth. It lets you search for descendents instead of children. For example, you could find all <div> tags in an XHTML document this way. The result of this query is:

<age>27</age>
<age>26</age>

We didn’t bother to look for the <person> tags; those <age> tags can come from anywhere in the XML.

thePeople.*.age

The asterisk operator (*) selects all children of the node. If the example had a non-<person> tag with an <age> child, this line would return it as well as the <age> nodes under the <person> nodes. It doesn’t care what the first generation child is, as long as the second generation child is <age>

Strings and XML

When you’re printing out XML elements, if you don’t specify a different conversion function, toString() is called. This will print out “complex content” like XML and “simple content” like its string value. You can use hasSimpleContent() and hasComplexContent() to test these: simple content are text nodes, attribute nodes, or a single XML element with no XML node children (such as <age>26</age>). This means you can print out my age with:

trace(thePeople.person.(@name == "Roger Braunstein").age); //26

Even though this expression returns an XMLList, that list happens to have just one element, an XML node with a text child, and XMLList.toString() prints out its text value, 26. The same applies for:

trace(thePeople.person[0].@name); //Mims Wright

Use toString() as a simple way to get text content out of attributes and simple XML containers like <age>26</age>.

If you want to ensure that the content prints out like XML, use toXMLString().

trace(thePeople.person.(@name == "Roger Braunstein").age.toXMLString()); //<age>26</age>

You can use the text() filter to grab text nodes out of XML. For instance, grabbing an attribute like thePeople.person[0].@name returns a text node. The only child of <age>26</age> is a text node.

trace(thePeople.person[0].age.text()[0]); //27

Again, this filter returns an XMLList of all the text nodes found, and we use array access to grab the first one. We could extract a list of all of the text nodes inside all the age tags with the following:

trace(thePeople..age.text()); //2726

Note that toString() doesn’t add any space. toXMLString() does, but since they’re both text nodes, it prints out as:

27
26

Function-style Properties

In E4X, using a property name in an expression finds child nodes with the specified name, like thePeople.person. We want to be able to name XML nodes anything we want, so all the filters, calculated properties, and functions of XML nodes are implemented as functions. In other words, thePeople.person.length would look for <length> nodes in the XML; but we can find the number of <person> nodes with:

thePeople.person.length(); //2

All the tests and filters are implemented as functions, even if corresponding properties in other classes are implemented as implicit accessors.

Synonyms, Other Axes

An “axis” is a direction of movement. In space, we can travel over the axes x, y, and z. In XML, however, we can travel down to child nodes, sibling nodes, and the like. In the E4X vs. XPath comparison, E4X loses out on axes big-time. It’s missing several important ones.

In the basics, I covered a bunch of directions you can travel already. Those were actually shortcuts for more verbose functions. This list will show which shortcuts exist.

attribute(name), attributes()

These axes find attributes of the corresponding XML. The first filters by the name of the attribute, and the second retrieves a list of all attributes. Their shortcuts are @name and @*.

Important! The attribute(name) axis will frequently serve you better than the @name shortcut! The shortcut only works when the attribute exists on all nodes in the list. This is a huge limitation, and a good reason to use the function version.

thePeople.person.@suffix; //OK, list of all suffix attributes
thePeople.person.(@suffix == "III"); //ERROR!!!
thePeople.person.(attribute("suffix") == "III"); //OK, Mims' node
child(name), children()

These axes find the children of the given node[s]; by child I mean a first-generation descendent. The first finds a child of a specific name, and is the same as typing that name in literally. The second finds all children, which we’ve seen before as the asterisk operator.

The following holds true:

thePeople.child("*") == thePeople.children() == thePeople.*

There are benefits to using this (and all the filters) as axis functions rather than shortcuts. The major benefit is that you can use a variable name as the parameter to an axis function.

var interestedNode:String = "bio";
thePeople.person[0].child(interestedNode); //Mims' bio

You can also pass an index to child() to retrieve a child at a certain index, just like using the array access notation. In my opinion this is more confusing than helpful.

descendants(name)

This axis returns all descendants (children, grandchildren, etc.) of a node set whose node name matches the name passed. Unlike child() and children(), this axis has one function, but if a name is not passed, it will return all the descendants. In other words, it defaults to *. Its shortcut is the double dot (..).

thePeople..age == thePeople.descendants("age")
thePeople..* == thePeople.descendants() == thePeople.descendants("*")
parent()

This axis returns the node’s parent or nodeset’s immediate parents. There is no shortcut for this. For instance:

var age0:XML = thePeople..age[0]; //<age>27</age>
trace(age0.parent().@name); //Mims Wright

Do you notice what’s missing? Yes, siblings would be really nice. It was so easy in AS2 to use nextSibling and previousSibling. But believe it or not, you have to hack your way around this by going to the parent and finding the next index. This is my workaround for sibling traversal. Let me know if you have a better one.

var node:XML = thePeople.person[0];

//in general, for any node:
node.parent().children(); //all siblings
node.parent().*[node.childIndex() + 1]; //next sibling
node.parent().*[node.childIndex() - 1]; //previous sibling

The ancestor axis (parent, grandparent, great-grandparent, etc.) is also missing from E4X.

Node Types

In addition, there are axes which select specific kinds of nodes. In actuality, an XML instance can hold a node of any type: element, comment, text, or processing instruction. This means that XML typed variables aren’t always valid XML documents. A text node by itself is not valid XML.

You can use the nodeKind() function to identify the type of a node. It will return “element,” “comment,” “text,” or “processing-instruction.” Note also that processing instructions and comments are typically ignored unless you change those settings on the XML object.

The elements(), comments(), text(), and processingInstructions() axes select nodes of these types as children.

Creating and Updating Values

When you select nodes with a filter chain, you’ll end up with an XMLList object or XML object. These objects are always pointers to the original data (unless you use copy() to clone them), and they are writable! So you can change attributes and elements that already exist:

thePeople.person[1].age = 80; //sets my age to 80
thePeople.person.age = 80; //ERROR! you can't set multiple elements with one assignment
thePeople.person[1].@suffix = "Sr."; //set my suffix to Sr.

You can create new XML nodes, as we’ve seen, by literally typing them in. In addition, E4X lets you embed variables in XML literals using curly braces:

var names:Array = ["Alice", "Bob", "Ivan"];
var newPerson:XML = <person name={names[int(Math.random() * names.length)]}></person>;

You can use stored values and expressions as not only attribute names, but node names and whole nodes, as well.

var nodeName:String = "age";
var newAge:XML = <{nodeName}>{Math.round(Math.random() * 100)}</{nodeName}>;

var names:Array = ["Alice", "Bob", "Ivan"];
var newPerson:XML =
  <person name={names[int(Math.random() * names.length)]}>
    {newAge}
  </person>;

You can create XMLList literals by using a root tag with no node name. This is kind of cracked out:

var aliases:XMLList = <>
	<aka>Elmo</aka>
	<aka>The Fonz</aka>
	<aka>Peanut Butter</aka>
</>;

Adding Values

There are many ways to append values to existing XML. As with the rest of E4X, there are a lot of shortcuts that sometimes make sense and sometimes not…

Say I want to add an alias to my name. The += operator is overridden for XMLLists, so you can use this as a quick way to append nodes:

thePeople.person[1].aka += <aka>Rog</aka>;
thePeople.person[1].* += <eyes>Brown</eyes>;
thePeople.person[1].children() += <test>hi</test>; //ERROR!

I’m not sure why the last line doesn’t work.

You can also immediately set non-extant values to create them, like pushing to an array by assigning to the first vacant index.

thePeople.person[0].eyes[0] = "Green"; //adds <eyes>Green</eyes> to Mims' node.

Perhaps a more sensical way to insert new nodes into existing XML is to use the appendChild(), insertChildBefore(), and insertChildAfter() methods. These methods take the Object type because they will attempt to convert their argument to a String and insert it as text if you don’t pass XML. But pass XML, and it will work as you might expect:

thePeople.person.(@name == "Mims Wright").appendChild(<aka>Von Kaiser</aka>);

Deleting Values

There is no corresponding removeChild() method. Instead, the delete operator is your only way to remove values. You can use delete with a single node or a whole XMLList, or an attribute.

delete thePeople.person.bio; //delete all <bio> tags
delete thePeople..bio; //Doesn't work but no error. Why?!?
delete thePeople.person.@suffix; //deletes all suffix attributes of <person> tags
delete thePeople.person.(@name == "Roger Braunstein").*; //clears out children of my node

Namespaces

I can write more about namespaces in a future revision, but for now check out the good discussion started in my previous post, Using E4X? Watch Your Namespaces.

There’s one odd addition to the language that’s specified in the E4X spec, and that’s the construct default xml namespace. Whereas opening a namespace allows you to read in XML in that namespace, setting a default XML namespace will also implicitly apply that namespace to new nodes you create.

var xhtml:Namespace = new Namespace("http://www.w3.org/1999/xhtml");
default xml namespace = xhtml;
var xml:XML = <html/>; //notice we didn't set a namespace manually
trace(xml.toXMLString()); //<html xmlns="http://www.w3.org/1999/xhtml"/>


Loading External Data

You can convert any String to XML by using the top-level XML() function. This looks like a cast operator and acts like one as well: it converts its argument to the XML type, but instead of just changing the type annotation, it performs a conversion.

You can use this function to get XML from other functions and variables:

var xmlString:String = '<root><device name="mouse" buttons="2"><connection type="usb"/></device></root>';
var xml:XML = XML(xmlString);
trace(xml.device[0].connection.@type); //usb

You can also use it to load data from an external file with the URLLoader class. Check out the documentation for more info on how to use this class.

var loader:URLLoader = new URLLoader(new URLRequest("http://partlyhuman.com/crossdomain.xml"));
loader.addEventListener(Event.COMPLETE, onLoadSuccess);
loader.addEventListener(IOErrorEvent.IO_ERROR, onLoadFailure);

function onLoadSuccess(event:Event):void
{
	trace("Loaded successfully!");
	var loader:URLLoader = URLLoader(event.target);
	var xml:XML = XML(loader.data);
	trace(xml.toXMLString());
}

function onLoadFailure(event:Event):void
{
	trace("Error loading file: " + event.type);
}

Conclusion

75% of what you’ll need to do with E4X is simple, but there are a bunch of gotchas, and I think it’s not doing AS3 any favors to try to gloss this over. I’m really not sure adding E4X was a great idea — certainly native XML and XML literals are excellent. But according to ECMA, the reasoning behind creating a whole new language for filtering when we have existing languages such as XPath and the Selectors API is that they are too difficult to learn, and not “simple” enough. I’m certainly happy that there’s some native XML support in AS3, but let’s be serious about explaining it.

Again, please add your comments, especially if you have any problems!

Note: If you are going to include XML in your comment, please convert the tags to HTML entities or they will be eaten! Use this tool.

  • Andy

    Great Article! I am new to E4X and this helped a lot. I was wondering if you knew of a way to solve this problem.

    XML Example

    Essentially i have an organization chart with n levels. Each Node has one parent but can have multiple other sections or people under it. Is there a way to do a filter on the entire set and subset to filter out all the type=”persons”. All the filters i have seen require you to dictate what level/root you want to filter on but is there anything that will parse the entire XML file and just return back nodes that have a type != person?

  • LEmlich

    I must have returned to this post 50 times in the last few months. Today, you saved me hours of work. I was trying to use the shortcut to find elements with a certain attribute and was getting an error because not all elements contained that attribute.

    This didn’t work

    var zz:XML = t.*.el.(@axis == “evil”)[0];

    But this did

    var zz:XML = t.*.el.(attribute(“axis”) == “evil”)[0];

    The first form works fine as long as every element contains the “axis” attribute, but I was getting an “axis undefined” error because that attribute was missing (on purpose) from some elements.

    This post IS my e4x documentation.

  • Pingback: Xml, xmllist fundamentals in Flex « Create possibility

  • Alex

    Great overview, thanks! I’d love to see an example of using using e4x with Flex charting capabilities. So if I have an xml file with 100 countries in it, and I want to display a certain variable for 5 specific countries, how would I do it?

    Any ideas?

  • Mukesh

    Hi,\nI want to add the child in xmllist at the 0th index. I can not found the solution , but instead I get the solution to add it at the 1st index\n\nxml.book[0]+= child\n\nI want to know how can I add child at the index 0\nThanks.

  • http://lpip.com David Hay

    This is a great article, I keep coming back to it. Thanks.\n\nI have one question though, I need to change the childIndex of an xml node. I’ve got move up and down buttons on a clip and would like to just move their corresponding node up or down in the list but I can’t see how it’s possible without looping through them all.\n\nThanks,\nDave

  • Guido

    Hi\n\nwe noticed problems when trying to retreive data when using our own namespace. \n\nThis worked :\n \n\n\n \n 440096\n 195467\n 2009-10-02T09:20:46.45\n\n\nbut this did not\n\n\n \n 440096\n 195467\n 2009-10-02T09:20:46.45\n\n\nAnyone ?

  • Aaron

    Hey guys,\nI am currently beating my head against the wall trying to clean up the text display from my xml nodes, without going into to much detail I am loading an XML file and displaying a particular node in a dynamic text field. The problem is, the text field is also showing the surround tags not just the node content.\n\nmy relevant code is\nwireXML = new XML(event.target.data);\nvar il:XMLList = wireXML.data.contententries.children();\n\nthis.feedText_1_txt.text = il.headline;\n\nthe textbox actually displays:\n\n\nAny help would be appreciated it’s possible my head will explode.\n\nThanks\n-Aaron

  • http://onegiantmedia.com one giant media

    Can you think of a slick 1-liner to convert an XMLList (somxml.node.@attrib filter results) to an Array?

    I wish this would work:

    var _titles:Array = XMLList(someXML.allOfSomeNode.@title) as Array;

    or XMLList class had something like this:

    var _titles:Array = XMLList(someXML.allOfSomeNode.@title).toArray();

    Without it, I made a Util class with function doing this:

    public static function xmlListToArray($xmlList:XMLList):Array {
    var a:Array=[], i:int, t:int = $xmlList.length;
    for (i = 0; i < t; ++i) a.push( $xmlList[i] );
    return a;
    }

  • http://www.onegiantmedia.com onegiantmedia

    just noticed in my last comment – $xmlList.length should be $xmlList.length() … XMLList class is simnilar to an array, but it’s different than array notation (which really makes no sense, adobe).

  • Jon

    I have an xml value that is blah=”ted, joe frank”

    i’m wondering if i can look through that value for “joe” and have it return the node, is that possible solely with E4X filtering?

  • http://www.mikemaurerdesign.com Mike

    In your example above…each person may have multiple nodes if they have multiple alias associate with their name…is there a way to filter through all of those nodes to pull out the ones you want?

    example…i want to search through the entire xml and pull out the people that have alias names that equal “that guy”.

    you can’t just say

    thePeople.person.(aka == “That guy”);

    you can surely have an xmlList of all of the aka nodes and using a for loop go through them all….but is there a shortcut to this?

  • http://www.fo.com Web Developer

    “\n”! I see all of the \n issues in the comments; and I wonder if ths actually demonstrates my question. I can not seem to pull CDATA text out of your sample correctly if it contains “\n or \r” — and have the return, new line actually work if the text is used. If I directly enter the same text with Actionscript3 it does work. I have tried {‘\n’}, \\n, etc, nothing works. Any ideas?

  • bub

    Hi! Thanks for this article, this is very useful and saved me a headache, glad to find people like you that wants to help us!

  • Pingback: ActionScript 3, XML and MySQL « byK

  • pinacolada

    There is nothing in this article explaining how to read the content of a cdata node. Is it impossible ?

  • Pingback: Using XML (E4X) inside Actionscript 3

  • f0ru0l0rd

    I’m new to XML and E4X. I want to find ways to implement E4X into AS3. While it was a little above my head, I got most (80%) of it, and I am really grateful to you for that! This is a very good primer for those who know XML already, and even for someone like me who can pick it up real quick! Thank You!

  • http://www.milkisevil.com/blog Philip

    I keep on coming back to this post, year after year, every time I forget a bit of e4x syntax. This is my e4x bible. Please never remove it ;)

  • http://mimswright.com Mims H Wright

    @Philip and everyone else

    Thanks for the great comments. We’re so glad this post has been helpful to everyone.

  • steveB

    How can I change the value of a node, which may either be a text node or contain other nodes?
    Typical structure:
    today
    or might be:
    today
    I can write a new value to rootnode.date = ‘tomorrow’ but this also destroys
    If I try rootNode.date.text()=’tomorrow’ I get an error.

    I am resorting to searching for the contents of rootNode.date.text() and doing a strong replace, but it is possible that the value may also be elsewhere. Ther must be a bettre way?

  • steveB

    Sorry all tags stripped out of last comment- making it nonsense!

    How can I change the value of a node, which may either be a text node or contain other nodes?
    Typical structure- using curlies instead of angled
    (rootnode) (date) today (/date) (/rootnode)
    or might be:
    (rootnode) (date)(anotherNode att=’keep me’ /) today (/date) (/rootnode)

    I can write a new value to rootnode.date = ‘tomorrow’ but this also destroys
    If I try rootNode.date.text()=’tomorrow’ I get an error.

    I am resorting to searching for the contents of rootNode.date.text() and doing a strong replace, but it is possible that the value may also be elsewhere. Ther must be a better way?

  • Jerome R Westrick

    The example: thePeople.person[1].age = 80; //sets my age to 80
    only seems to udate the age tag, in actuality it create a new age node.

    Creating a new node has several side effects that the users should be warned of:
    1) any attributes of the age tag are lost.
    2) any sub elements of the age tag are lost.
    3) any references you have to the age tag still point to the old tag.

    This can get real nasty, and I’ve found no work around…

  • uasy


    case "status1":
    //Здесь чудеса --fairy tail
    var t31:Object = appModel.roomsStates.ROOM.(@NRN==item.@NRN); //null
    var t32:Object = appModel.roomsStates.ROOM.(@NRN==item.@NRN); //XMLList

  • Raf

    Just wanted to let you know that whenever I have to load XML in AS3 I look for this exact page, since I never bother to remember how to do it. Your page has been my crutch for YEARS.

  • http://mimswright.com Mims H Wright

    Thanks Raf! That’s awesome!

  • http://www.milkisevil.com Philip

    over a year later… back again ;)