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.
This post didn’t solve my problem directly, but you got me headed in the right direction so I thought I’d share the solution.
I needed to remove an arbitrary node at an arbitrary depth in an XML object. The solution is in two parts.
First, each element that will be deleted needs a UID so I used the UIDUtil.createUID(); function.
var xDoc:XML = XML(”);
I get the item to delete from a Tree.selectedItem call
var delItem:XML = XML(Tree.selectedItem);
Then the delete command is:
delete xDoc.descendants().(@uid==delItem.@uid)[0];
That searches recursively for all elements that have a uid parameter and matches them to the selected item’s uid. Since the return value is an XMLList (even if only 1 item is returned) you have to use the [0] index or the delete function will puke.
Enjoy :)
Wow! They can make things so darn tricky it’s, like searching for a needle in a haystack, only the needle could also randomly be disguised as either a thumbtack, a bobby pin, a screw, or a paperclip!
Your post helped me a lot, but I must say it was this little nugget that finally solved my node deletion issue!
“… you have to use the [0] index or the delete function will puke.”
Praise the code demons!
The delete operator is indeed the official way (as mentioned in the “appendChild()” documentation) to remove a child node. Perhaps the “Attempt to delete the fixed property…” error arises because grandChild is not the result of an E4X expression? Whatever the cause, you can get a child node’s index with the childIndex() method:
delete grandChild.parent().children()[grandChild.childIndex()]A bit lengthy, .
kanenas, I agree, that’s the exact same piece of code I just determined for deleting a known XML node from its parent.
quinthar, thanks for your article, I agree that deleting XML nodes is harder than it looks! Part of the reason is that Adobe doesn’t explain very well how these XML and XMLList objects are related. I see some wording in your article that indicates a misunderstanding.
When you discuss child.appendChild(grandChild), you say that you expected it to return a reference to the node it just “allocated”. But there is no node allocated: grandChild is changed to be part of child’s tree. So now grandChild’s parent() and childIndex() are now valid. This has some consequences, such as you can’t take a node and append it to several other XML nodes and expect it to be in all of them. (You can use the XML object’s copy() function for that).
Similarly, when you call children() on an XML object, the XMLList returned is a reference to the actual child list inside the XML object. So if you delete a node from this list, you’ve actually deleted it from the parent!
I wish Adobe was clearer about this stuff…
If you ever want to hear a reader’s feedback :) , I rate this post for four from five. Decent info, but I just have to go to that damn msn to find the missed bits. Thanks, anyway!