Reply to comment
While porting some xpath-heavy AS2 code to AS3, I ran into problems. E4X (new and improved xml support in AS3) seems to make xpath obsolete. However, all the E4X query examples in the docs use hard-coded literals. I can't use literals because I don't know at code-time what the queries are going to be. My AS2 code creates xpath paths dynamically from variables. What is the E4X equivalent?
After some initial confusion on my part, I now get it. In retrospect, what's below seems obvious -- but that's the way it always is.
Selecting a variable-named node type
Let's work with this xml:
<animals>
<animal type='baboon' hairColor='auburn'>
<fav>
<food name='pizza' />
<color name='blue' />
<activity name='combing' />
</fav>
</animal>
<animal type='baboon' hairColor='blond'>
<fav>
<food name='donuts' />
<color name='green' />
</fav>
</animal>
<animal type='elephant' hairColor='bald'>
<fav>
<food name='peanut butter' />
<color name='beige' />
<activity name='sneezing'/>
</fav>
</animal>
</animals>
To find out the the auburn-haired baboon's favorite food, the e4x query is:
var result:XMLList = animals_xml.animal.(@name=='baboon' && @hairColor=='auburn').fav.food.@name;
Now, let if the favorite grouping is a variable, we replace the dot syntax with good, old array/bracket syntax. This is the normal way of accessing variable-named properties on an object.
var favGroup:String = "color"; var result:XMLList = animals_xml.animal.(@name=='elephant').fav[favGroup].@name;
Just be careful with the dots. Notice there is no dot before the opening bracket.
Variables in a filter
Let's variable-ize this hard-coded query:
var result:XMLList = animals_xml.animal.(@hairColor=='blond').fav.color.@name;
and make the attribute and value variables.
var attr:String = "hairColor"; var val:String = "blond"; var result:XMLList = animals_xml.animal.(attribute(attr)==val).fav.color.@name;
Each dot separates a step. Each step has an input and an output XMLList. If the step is a node type, the output list will be a subset of the input list's children, i.e. child nodes with that node type.
If the step is an expression in parentheses, the output list is a filtered subset of the input list itself (not it's children).
If the expression in the parentheses evaluates to true, the 'current node' will be included in the output XMLList. The current node is an XML object. This expression is evaluated once for every node in the incoming XML list.
Note: you might think that you can refer to the current node as 'this'. You can't. 'this' is scoped to the containing code. To get at the current node, use valueOf() (a function of the XML class). We'll see it in action below.
BTW, when I first saw this XML.valueOf function in the docs, I could not figure out why on earth you would ever need a function that returns the object itself. Seemed pointless. But now I know.
Calling custom functions
We are not limited to simple comparisons. This example in the docs gives us a hint of what's possible:
x.employee.(position.toString().search("analyst") > -1)
It is possible to do more than simple comparisons of attributes and node names to values.
We can create custom functions and give them the current node to decide whether to keep that node. For example:
function f(node:XML, color:String):Boolean
{
return node.@hairColor.toString().toUpperCase() ==
color.toUpperCase();
}
var result:XMLList =
animals_xml.animal.(f(valueOf(),'blond').fav.activity.@name;
Of course, the above example is trivial and you would not really implement it that way. It would be better to simply compare the attribute with a value. But using a custom filter function opens infinite possibilities. For example, take a look at this mxml file.
And here's the result:
song xml: <songs> <song title="My Dog has Fleas" artistID="100"/> <song title="I Hate Wet Socks" artistID="101"/> <song title="Hello to the Nice People" artistID="201"/> <song title="Moldy Baloney" artistID="202"/> <song title="Rainy Days" artistID="203"/> <song title="Cloudy Days" artistID="100"/> <song title="Mooney Nights" artistID="201"/> <song title="Sunburned Noggin" artistID="202"/> </songs> artist xml: <artists> <artist id="100" firstName="Sue" lastName="Jones"/> <artist id="101" firstName="John" lastName="Ambercrombie"/> <artist id="201" firstName="Ed" lastName="Fish"/> <artist id="202" firstName="Jane" lastName="Wolf"/> <artist id="203" firstName="Mary" lastName="Birch"/> </artists> E4X Query using custom function: songs2XML.song.(songsByArtistAlphaRange(valueOf(),'A','G')) Result xml: <song title="I Hate Wet Socks" artistID="101" artistName="John Ambercrombie"/> <song title="Hello to the Nice People" artistID="201" artistName="Ed Fish"/> <song title="Rainy Days" artistID="203" artistName="Mary Birch"/> <song title="Mooney Nights" artistID="201" artistName="Ed Fish"/> Songs by Artists A-G: John Ambercrombie: I Hate Wet Socks Ed Fish: Hello to the Nice People Mary Birch: Rainy Days Ed Fish: Mooney Nights
Here, we're using a second xml file to affect the query of our first xml file. Basically, we're linking database tables. I think that's cool.
Port of XPathAPI
Before I came to my senses on how this worked, I actually ported the XPathAPI code to ActionScript 3 and used it in my project. After I figured out how E4X really works, I went back to my code to remove the XPath code.
But I found that in some cases, I liked the xpath code better. The way I wrote the original code -- lots of string manipulation of the xpath paths, arrays of these strings, etc. -- it just turned out that way and using E4X navigation was actually more awkward. So I left the XPathAPI code in.
In case you find yourself in a similar situation, here's the ported XPathAPI code. You might find this useful when porting AS2 to AS3.



