More XML filtering with E4X in ActionScript 3

If you haven’t read my first post about filtering data with E4X, go check it out now. Today, I’ll expand on it to add a few more options that you have at your disposal when working with XML in ActionScript 3.

The following is a simple XHTML document that I will reference in the examples below:

var html:XML = <html>
<body>
	<ul class="links">
		<li><a href="http://www.yahoo.com/" class="josh">Yahoo!</a></li>
		<li><a href="http://www.adobe.com/">Adobe</a></li>
		<li><a href="http://joshblog.net/" class="josh">Josh's Blog</a></li>
	</ul>
	<div id="intro">
		<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
	</div>
	<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</body>
</html>;

Consider a situation where you want to retrieve all the paragraphs from an XHTML document. Each paragraph could be nested in DIVs, lists, tables, or anything. If we want to retrieve an XMLList of every paragraph in that document, we need to use E4X’s .. operator. A single dot only allows you to access direct children of a node, but this operator allows you to drill-down into the hierarchy of every descendant in an XML object to access grandchildren and more.

var paragraphs:XMLList = html..p;

You can combine this new operator with the filtering methods I introduced last time to build some pretty complex queries. The following example retrieves the URLs from all the anchors (links) in unordered lists with the class “links”. Notice that I must use the attribute() function because class is an ActionScript keyword.

var urls:XMLList = html..ul.(attribute("class") == "links")..a.@href;

Of course, we don’t need to access attributes through the complete hierarchy of their containing elements. Consider a situation where you want want a list of all the values of “class” attributes in an XHTML document. This attribute could appear on almost every element in the document. In most cases, you can directly reference the attribute with the @ symbol.

var classes:XMLList = html..@class; //error for "class"

However, like the previous example, we can’t access the “class” attribute as easily because it’s an ActionScript keyword. Instead, let’s use the decendants() function, which returns every element (not limited to direct children) that is below an XML node, along with the attribute() function to get the “class” attributes. The query will be a little more complex, but I don’t think it’s unwieldy.

var classes:XMLList = html.descendants().attribute("class");

You’ll notice that the query returns every single class attribute in the document, including duplicates. For instance, there are two anchors in document that I have given the class “josh” because they happen to point to sites that have content created by me (this blog, and my employer, Yahoo!). What if I want a list containing only unique items? In other words, how can I get results where the “josh” class only appears once?

Unfortunately, E4X doesn’t provide a native way to retrieve a list with only unique values. That’s unfortunate because I’ve found that it’s a common requirement. Based on my previous knowledge of combining E4X with ActionScript (remember the trace() call from my last post?), I built the following query that should do the job:

var filteredClasses:XMLList = new XMLList();
html.descendants().(filteredClasses = addUniqueValue(attribute("class"), filteredClasses));

That snippet calls a simple function addUniqueValue() which I’ve included below.

private function addUniqueValue(value:Object, list:XMLList):XMLList
{
	if(!list.contains(value))
	{
		list += value;
	}
	return list;
}

As I demonstrated last time, code within parentheses in an E4X query basically runs in a loop for every item in an XMLList. I’ve used this to my advantage to call a function for each item in the list that checks to see if has been included in the filtered list. Once the full query finishes, an XMLList with unique items has been placed into the filteredClasses variable.

By the way, did you notice that XMLLists support the += operator to append additional items? I didn’t know that until I just tried it. Nice.

That should cover everything you need to know to go crazy with E4X. Keep in mind that E4X can be a bit slow if written poorly, and it’s important to optimize your queries for large datasets. If you need to filter your data, like I did to get unique values, remember that you can call ActionScript functions and assign values within the parentheses. Finally, don’t forget about the attribute() and child() functions. They’re very useful for using dynamic strings in your queries. E4X rocks.

Update: I’ve written a followup article:

And be sure to check out the original:

About Josh Tynjala

Josh Tynjala is a frontend developer, open source contributor, bowler hat enthusiast, and karaoke addict. You might be familiar with his project, Feathers UI, an open source user interface library for Starling Framework that is included in the Adobe Gaming SDK.

Discussion

  1. Josh Tynjala

    I just discovered that E4X supports string-based access to children through brackets, just like regular ActionScript:

    var classes:XMLList = html..@["class"];

    Notice that the @ symbol is outside the brackets.

    I’ll leave my code examples above intact because it’s important to know how to use functions like descendants() and attribute().

  2. Paulius Uza

    Hey,

    Thanks for taking time to write this. Coincidentally you saved me an hour or two today as I was just stuck with “class” keyword.

    Very useful and detailed article

  3. Pingback: Getting Advanced with E4X » Zeus Labs

  4. Pingback: reintroducing.com Blogging Receptacle » Blog Archive » E4X Articles

  5. Roger Braunstein

    Oum. Josh. That really is a lovely trick, using the filter expression as a loop body. I will have to use that one — bless you.

  6. Robert Dougan

    Hi, love the blog entries and they both have came in very useful for myself.

    I was wondering if it was possible to display HTML code within a dynamic text box? I.e. persing <b>, etc tags.

    Thanks!

  7. Josh Tynjala

    Robert, TextField supports a property named htmlText which has limited support for HTML including simple tags like <b>. If you need something very complex, you’ll need to build it yourself.

  8. Brendon Smith

    Quick question Zeus. Dealing with Yahoo pipes(by the way pipes completely rocks! http://pipes.yahoo.com/SeaCloud9) and rss.
    Here is my conundrum. Yes it is the infamous TypeError: Error #1085:
    …The element type “p” must be terminated by the matching end-tag “”.
    I have been working with yahoo pipes quite a bit for fun lately and I would like to inject some of my pipes into flash. The problem is some of the feeds I have been hitting have malformed html. Ahhhh puts my whole fun bus up on blocks. To be quite honest it is like an itch you can’t stop scratching. I have been thinking about writing a class that parses the html and ends the code properly. If this was my xml it would be easy to fix but of course it is not; it is coming from my pipes. So to make a long story short I was wondering if you were aware of a work around or an existing class? Anyway I dig your site so much I gave your blog a link on my blog feel free to join me on Facebook for some geek speak. Thanks -Brendon

    http://www.facebook.com/people/Brendon_Smith/1193303206

  9. Josh Tynjala

    Brendon, I’ve worked a bit with Pipes, but I’m not sure if the following suggestion is possible. Try wrapping the feed’s data with CDATA.

    Start with <![CDATA[, put the invalid XML in between, and end with ]]>.

    The XML parser will treat the data in those sections as raw text and won’t try to parse it as XML.

  10. Brendon Smith

    Thanks for the tip. Still stumped I have actually tried it a vairety of ways example: var p:XML = new XML(“”+ “”+””); I will keep on working on it but I think the ideal solution would be a class that takes the string looks for tags counts how many times they occur to make sure the number is even and then if the number is not even scans the list for the item that is broken and appends the correct tag by scanning where the other tag starts. Don’t know if it is possible but it is highly frustrating when you are close but then you get nothing ahhh. I do enjoy using flash with pipes definitely learned a great deal in the process. -Thanks for the tip if I find a solution I will let you know. Thanks -Brendon Smith

  11. Pingback: Ways to use E4X to filter data in ActionScript 3 - Josh Talks Flash

  12. Pingback: evolutioneer.com/blog » Blog Archive » Manipulate XML Data in AS3

  13. Jeremy Daley

    what if i wanted to run this through a “for each” loop? i can’t seem to get this to work:

    for each (var castNode:XML in configXML..sound.(attribute("file") == mp3FilePath){
  14. Josh Tynjala

    Jeremy, I would run the E4X statement outside the loop and just use the result:

    var castNodes:XMLList = configXML..sound.(attribute(”file”) == mp3FilePath);
    for each (var castNode:XML in castNodes){

    It’s possible that the statement is being evaluated on every iteration of the loop, which could make it behave strangely.

  15. John

    I need to filter xml to get nodes where a value is a value in another xmllist. So return element1 if valueA is on the xmllist

    valueA

    valueB

    — Checked against the following xmlList —
    var xmlList:xmlList =
    valueB
    valueC

    — looking for somthing like… —
    node.(element.memberOfList(xmlList) == true)

  16. John

    Sorry… here’s a better form:

    — looking for something like —
    node.(element.memberOfList(xmlList) == true)

    <node>
    <element>valueA</element>
    </node>
    <node>
    <element>valueB</element>
    </node>
    — Checked against the following xmlList —
    var xmlList:xmlList =
    <item>valueB</item>
    <item>valueC</item>

  17. Viktor

    great article, congrat.

    I guess, this is the shortest form of getting every element that has the “title” class.

    xml.*.(hasOwnProperty(“@class”) && @[“class”] == “title”)

    best,
    V

  18. Alex

    Enjoyed the basics. I enjoyed your method for filtering using actionscript similar to what I was doing but a touch more simple. thanks for the good write-up

  19. mediamob

    This is a really great E4X crash course in beyond the basic techniques. This has really helped me but I still don’t understand how to implement this last example in the case where “class” is not an attribute, but an element. What am I missing here?

  20. netan

    Can you please tell me how you have embaded the xml on this blog page and which WP plugin or editor are you using to the formatted first xml snippet.

    Thanks in advanced.

  21. Ignacio

    One billion thanks man,

    I was stuck in this project for a week because I couldn’t figure out how to obtain the specific information I wanted from the xml source!

    Great work, I really appreciate your effort!

    Ignacio

  22. Pingback: Getting Advanced with E4X - Josh Talks Flash

  23. newbie11

    Hi,

    very useful, thank you. I have a question though: Do you know if it’s possible to sort items alphabetically?

    example:

    <Peter/>
    <Astrid/>
    <John/>

    return
    <Astrid/>
    <John/>
    <Peter/>

  24. Pingback: 12 Very Helpful XML Related Tutorials In ActionScript - Ntt.cc

  25. Eric

    HI, I am trying to do something like this:

    [action script]

    var filteredList:XMLList = new XMLList();
    getUniqueValues2(attribute(“ctyName”),xmlTest, filteredList);

    private function getUniqueValues2(value:Object, inputList:XML, outputList:XMLList):void
    {
    if(!inputList.contains(value))
    {
    outputList += value;
    }
    }

    flash builder is telling me: Call to a possibly undefined method attribute.
    Is there a way to pass an attribute in a funtion? Because when I use your system with:

    tempListColl.source = xmlTest.descendants().(filteredName = removeDuplicate(attribute(“ctyName”), filteredName));

    (removeDuplicate() being a carbon-copy of your addUniqueValues())

    The filteredName XMLList has exactly what I want but the tempListColl is unusable. Or maybe I messed up my syntax somewhere.

    Very nice tutorial by the way.

    Regards,

  26. Bart

    You can do this with node-name’s too! Usefull if you want to filter a list based on the name’s of the node but want to allow more then one. Note this will mess-up if you have a local variable named ‘name’ (this confuses the scope).

    var list:XMLList = myXML.children().(name() == 'nameA' || name() == 'nameB');

    You could also combine it with an Array to make it really dynamic, but don’t forget to convert name() to a String! This is because it actually is something else, a QName or Object):

    var allowedNodes:Array = ['nodeA', 'nodeB', 'nodeC'];
    var list:XMLList = myXML.children().(allowedNodes.indexOf(name().toString() > -1));
  27. Ivan

    Hi, first is really helpfull this article. I´m trying to do something like this

    var searchResults:XMLList = xmlData.Rows;
    searchResults = searchResults.( attribute(“id”) == 15 || attribute(“id”) == 16 );
    This works but i have an Array with the id´s to search somebody know if exist something like the clause “IN” ( SQL ).

    searchResults = searchResults.( attribute(“id”) IN [15,16] );

    if somebody have an idea, I really appreciate.