E4X Macro for Haxe

I recently needed to navigate some XML in Haxe and noticed that there were few options for doing this quickly and easily in Haxe.

I did notice Oleg’s Walker class which brings some of the E4X functionality of AS3 to Haxe.
While the resulting code was more elegant than hand-writing loops and tests, it still felt too verbose, and I decided to add some macro sugar to it to cut down the syntax (and bring it closer in line with the E4X spec).

The result is the E4X class, which reduces the amount of code by 2-3 times (in comparison to a fully runtime, function-based solution). Due to haxe language restrictions, the resulting syntax is not quite as compact as the AS3 equivalent, but it’s close.

Usage

E4X expressions must be wrapped in the macro call, and they return an iterator of values (the type of which is based on the last part of the expression).

To get all children:

1
var nodes:Iterator<Xml> = E4X.x(xml.child());

Here are some different ways to get a list of all the child nodes with the name “node”:

1
2
3
4
5
6
7
8
9
10
11
var xml:Xml;
var nodes:Iterator<Xml> = E4X.x(xml.node);

// or (for example)
var nodes:Iterator<Xml> = E4X.x(xml.child("node"));

// or (using an expression which will be wrapped in a function call)
var nodes:Iterator<Xml> = E4X.x(xml.child(nodeName=="node"));

// all of which are shortcuts for this filter expression
var nodes:Iterator<Xml> = E4X.x(xml.child(function(xml:Xml, _i:Int):Bool{return xml.nodeName=="node";}));

To get the text of a node use the text() method:

1
var nodes:Iterator<String> = E4X.x(xml.text());

To access descendants, use the “desc()” method, or the underscore shortcut:

1
2
3
4
5
var nodes:Iterator<Xml> = E4X.x(xml.desc());
// or
var nodes:Iterator<Xml> = E4X.x(xml._());
// or just
var nodes:Iterator<Xml> = E4X.x(xml._);

Here are some different ways to get a list of all the descendant nodes with the name “node”:

1
2
3
4
5
6
7
8
9
10
11
var xml:Xml;
var nodes:Iterator<Xml> = E4X.x(xml._("node"));

// or (for example)
var nodes:Iterator<Xml> = E4X.x(xml.desc("node"));

// or (using an expression which will be wrapped in a function call)
var nodes:Iterator<Xml> = E4X.x(xml.desc(nodeName=="node"));

// all of which are shortcuts for this filter expression
var nodes:Iterator<Xml> = E4X.x(xml.desc(function(xml:Xml):Bool{return xml.nodeName=="node";}));

Getting a list of descendants that have an “id” attribute would be done like this (the a(“id”) call acts like a filter):

1
2
3
4
5
6
7
var nodes:Iterator<Xml> = E4X.x(xml._(a("id")));

// which could also be written as
var nodes:Iterator<Xml> = E4X.x(xml._(a(attName=="id")));

// both of which will be expanded to
var nodes:Iterator<Xml> = E4X.x(xml._(a(function(attName:String, attValue:String, xml:Xml):Bool{return attName=="id";})));

Whereas, if you wanted to get the “id” attributes themselves, you could do this:

1
var nodes:Iterator<Hash<String>> = E4X.x(xml._.a("id"));

To get all of the ancestors of any nodes with an “id” attribute equal to “test”, you could do this:

1
2
3
var nodes:Iterator<Xml> = E4X.x(xml._(a("id")=="test").ances());
// or (a little less legible, but will perform slightly better)
var nodes:Iterator<Xml> = E4X.x(xml._(a(attName=="id" &amp;&amp; attValue=="test")).ances());

 

Comparison with AS3 E4X

Getting children with a specific node name (i.e. “node”)
AS3 E4X xmlRoot.node
Haxe E4X xmlRoot.node
Getting descendants with a specific node name (i.e. “node”)
AS3 E4X xmlRoot..node
Haxe E4X xmlRoot._(“node”)
Getting an attribute
AS3 E4X xmlRoot.@id
Haxe E4X xmlRoot.a(“id”)
Getting all descendants with a “id” attribute
AS3 E4X xmlRoot..(@id.length())
Haxe E4X xmlRoot._.(a(“id”))

Note that all of these examples should be wrapped in the E4X.x() call, as in the code snippets above.

 

Performance

I also ran some performance tests for several targets (and the equivalent tests in AS3 E4X), the results of which are below.
This helped me make some performance improvements to Oleg’s original code, and I managed to squeeze an extra 25-30% increase in performance out of it.

Surprisinigly, the JS target seems to perform best overall (although this is probably more as a result of Chrome’s JS engine).
Even after my improvements, the AS3 target was woefully slow in comparison to it’s native counterpart, although all of the other targets seemed to hold their own, with more complex expressions becoming faster than the AS3 E4X equivalent (if anyone knows why this performs so poorly, let me know).

AS3 E4X Hx > Flash Hx > JS Hx > C++ Hx > Neko
Get Children 0.00 0.21 0.03 0.22 0.03
Get Children With Attrib 0.08 1.02 0.24 0.10 0.31
Get Descendants 0.92 7.20 0.52 0.57 0.94
Get Descendant Text 1.48 19.40 1.97 1.83 3.22
Get Descendants by Name 2.33 11.05 0.24 0.51 1.42
Get Descendants with matched Attrib. 2.70 30.10 1.20 2.23 7.51
Measurements are in seconds per 1000 calls
JS tests done in Chrome 24 Win64

If anyone has any idea how to use the @ symbol in method names in haxe (without the compiler complaining), let me know and I’ll make attribute accessors match the spec.

I will be releasing this code as part of an upcoming haxe library called “xml-tools”, but until then, feel free to download the E4X class here.

Shout out if have any issues.

8 Comments

  1. Any plans for a Haxe 3 port? I tried to migrate myself and got a ways along, but getting some compiler errors that I don’t know my way around. I’m fairly new to Haxe. Might be fairly simple though for someone familiar with the code.

  2. In general, so long as by the time you return from the macro, all the tokens have been mapped to valid haxe expressions, everything will turn up roses. In principle it’s as simple as searching for example: xmlRoot..(@id.length()), and replacing it with xmlRoot._.(a(“id”)), but in practice you have to break out the recursive parsing techniques to make sure you’re chunking things correctly. Best talk to back2dos about it, it’s something he will have done for tink.markup, and that’s looking pretty stable.

    • It seems like the club steering wheel locks windows.insurance. One of the company is why it is essential to choose from, there is an excellent credit risk. It just may not rent a car insurance because it plays greatwho has had many tickets in a safe car. If you suffer a car insurance for young drivers. Young drivers (under 25), who are unaware of any accidents in near aretry to annually review your account according to nationality and migration status. In fact, it’s becoming very much harder to sort them in person, tell them whether or not your incovered when you have been your fault or apologize for that, you are unfortunate enough to get creative. You have to pay if you are financing an expensive car. Generally, includesSo many people make is assuming you don’t have the minimum and maximum limits that is always possible to find out more claims caused by a man in a wholesale visits,that I will be dependent on the side of the original policy. If you do as well as provide you with the cost, savings can be covered because your vehicle itcar insurance. Save up the pace. So if you have no problem getting insured. There are a number of uninsured motorist. In addition to New York, but no real upside wenot too high. It is likely to have a bad driving of the other car and parking the vehicle he or she is doing.

  3. why wouldn’t it help?

    • From what I’ve sen, this new notation is only for named function declarations, something which isn’t really applicable to this E4X stuff.
      That said, I haven’t found any documentation on it, so I could be wrong about it’s usage.

  4. In tinkerbell, I believe each method in a subclass of Cls is mapped, see @:autoBuild (in the macro docs), where you use Context.getBuildFields to get an array of fields in the current class, modify them, (i.e replacing @tag with an expression) and pass them back.
    Gotchas incluude being sure to type the build function `macro static function build():Array`, and placing the build function in a different module to the implementing class.

    • Thanks for the tip-off.
      I was hoping to use the technique for attribute access like this:
      xml.@attribute
      But after following Tink’s code down a little deeper I’m not sure this new feature will help.

Leave a Reply

Your email address will not be published.

*

© 2017 Thomas Byrne

Theme by Anders NorenUp ↑