XML support in ActionScript is actually quite nice (though it’s a bit creepy to see raw XML inserted straight into the code). The whole E4X thing takes some getting used to, but in general everything just works surprisingly well. Except for deleting. That just doesn’t work at all like you’d expect. Consider the following ActionScript code:
var xml:XML = <foo>
<bar id=”1″/>
</bar>
<bar id=”2″/>
</foo>;
That’s right, you can just assign XML straight to a variable. Wiggy, right? Anway, so the operations are a little weird, but not too bad. trace( xml ) spits out the entire <foo> tree in well-formatted XML to the log file, as you’d expect. trace( xml.bar ) outputs the all the <bar> elements as an array, so that’s sorta reasonable. trace( xml.bar.(@id==1) ) starts to get trippy, as the dot before the parenthesis isn’t a typo but rather an E4X selector of all children of bar with an “id” equal to 1. Anyway, all this is spelled out in way more detail in the docs.
So hard-coding XML is incredibly easy, and accessing it is weird but pretty easy too. It also turns out that adding more nodes to the tree is really easy: just assign some node to a variable, and then call appendChild(). Duh, what could be easier?
var child:XML = xml.bar[0]; // xml.bar is an array, we want the first one
var grandChild:XML = <blah>; // That’s right, this is valid
child.appendChild( grandChild );
Ok, so we’ve hard coded some XML, we’ve added some nodes, now all we need to do is remove it. Everything else has been easy, this should be too, right? Wrong. At least for me, deleting nodes from an XML tree was really non-intuitive. Here’s what I assumed I could do:
child.removeChild( grandChild );
I mean, we could add it that way, why not remove it? Nope! Doesn’t work that way. That function doesn’t exist. Ok, that’s fine you think — we still have a reference to grandChild; let’s just delete that:
delete grandChild;
Nope, that doesn’t work either — you get this message “Error: Attempt to delete the fixed property grandChild. Only dynamically defined properties can be deleted.” I’m not entirely sure what that means, but it sounds bad. Maybe it’s because grandChild is just defined with a constant XML object that screws it up? Ok, let’s get a reference to grandchild and then try to delete that. How to get the reference? Well, you might notice that appendChild() returns an XML object. And you might think that means it returns a reference to the new XML object it just allocated. That would mean you could do this:
var appended:XML = child.appendChild( grandChild );
delete appended;
Alas, that doesn’t work. “appended” in this example is just set equal to “child”, for no reason I can surmise. In other words, there’s no easy way to get a reference to the thing you just created. You’ve got to turn to the black arts of E4X to dig it out:
var grandChild:XML = <blah>;
child.appendChild( grandChild );
var appended:XML = child.blah[ child.blah.length()-1 ]; // The last one is the one we appended
delete appended;
So, append grandChild to child, then get a reference to appended, and then just delete appended, right? Wrong! You get that same mysterious “Attempt to delete the fixed property” compile error as before. Hrm. Ok, so how the hell do we delete something? Turns out, the only way (that I can find) is to use more E4X magic, this time in conjunction with a special “delete” operator:
delete child.blah[ child.blah.length()-1 ];
So, you can use references to XML elements to query them and add children, but you need E4X in order to delete them. Now, in the above example you might be thinking “why do the whole “length-1″ bit — why not just use “blah[0]“? The answer is “because generally you don’t know how many children are already there and thus need to find the last one”. Which brings up a really, really excellent question: how do you delete an item from the *middle* of a child list. So glad you asked!
Well, if you know its index, then you can just delete it with the array operator (eg, “delete child.blah[2];”). But if you don’t know its index, then you need to give it an attribute (child.blah[2].@id=1337;) and then delete using that attribute (delete child.blah.(@id==1337);). All in all, it sorta makes sense now that I know it, but it sure wasn’t an intuitive leap to get from there to here. Anyway, that’s my learning process, I hope it helped you through yours. Here’s some test code, as well as the output it generates:
var xml:XML = <foo>
<bar id=”1″/>
<bar id=”2″/>
</foo>;
trace( “—- xml —-” );
trace( xml );
trace( “—- xml.bar —-” );
trace( xml.bar );
trace( “—- xml.bar.(@id==1) —-” );
trace( xml.bar.(@id==1).toXMLString() );
trace( “—- before append —” );
var child:XML = xml.bar[0];
trace( child.toXMLString() );
trace( “—- after append —-” );
var grandChild:XML = <blah/>
child.appendChild( grandChild );
trace( child.toXMLString() );
trace( “—- after delete —-” );
delete child.blah[ child.blah.length()-1 ];
trace( child.toXMLString() );—- xml —-
<foo>
<bar id=”1″/>
<bar id=”2″/>
</foo>
—- xml.bar —-
<bar id=”1″/>
<bar id=”2″/>
—- xml.bar.(@id==1) —-
<bar id=”1″/>
—- before append —
<bar id=”1″/>
—- after append —-
<bar id=”1″>
<blah/>
</bar>
—- after delete —-
<bar id=”1″/>
PS: The “toXMLString()” is needed because if you don’t use it, then trace() outputs nothing for XML nodes that have no children (even if they have attributes). Strange, but true.