ColdFusion XML Nodes Are Passed By Value, Not By Reference
I have been doing some exploration with ColdFusion's XML capabilities and I came up against something very interesting. It seems that the XmlRoot node is passed by reference but any other nodes below that are passed by value. Here is a demo of this in action:
<!--- Create a blank ColdFusion XML document for actresses. ---> <cfset xmlActresses = XmlNew() /> <!--- Create a new XML node that we will use as the XML root. All other nodes in the document will live under this node. ---> <cfset xmlRoot = XmlElemNew( xmlActresses, "", "actresses" ) /> <!--- Set the new root element as the root node of our actresses DOM. ---> <cfset xmlActresses.xmlRoot = xmlRoot /> <!--- Create a new actress node element. ---> <cfset xmlActress = XmlElemNew( xmlActresses, "", "actress" ) /> <!--- Set some of the actress properties. ---> <cfset xmlActress.XmlAttributes.Name = "Maria Bello" /> <cfset xmlActress.XmlAttributes.HowSexy = "Too Sexy" /> <!--- Append the actress node to the child nodes of the root (Actresses). Notice here that we are using the xmlRoot that was generated above and the xmlActress node that we just created for Maria Bello. ---> <cfset ArrayAppend( xmlRoot.XmlChildren, xmlActress ) /> <!--- Now that we have added the new Actress node to the XML DOM, let's try to update some of its values without going back into the XML document path. ---> <cfset xmlActress.XmlAttributes.BirthDay = "18 April, 1967" /> <cfset xmlActress.XmlAttributes.Hair = "Blonde" /> <!--- Now, let's add some attributes, but this time, instead of referencing our Actress xml node, let's go back through the XML DOM. ---> <cfset xmlActresses.Actresses.Actress.XmlAttributes.Eyes = "Brown" /> <cfset xmlActresses.Actresses.Actress.XmlAttributes.Smile = "Sly" /> <!--- Now, let's dump out our actress node. ---> <cfdump var="#xmlActress#" label="xmlActress Node" /> <!--- Let's dump out our actresses XML DOM. If the nodes are passed by reference when using ArrayAppend(), then the CFDump above should be reflected in the CFDump below. ---> <cfdump var="#xmlActresses#" labe="xmlActressed DOM" />
Notice that when I append the XML node, xmlActress, to the xmlActresses document, I am using the existing node reference, xmlRoot. Also notice that the node being appended is the existing node reference to xmlActress. Since both of these nodes are valid references, it should work fine.
But now, look at what I do after that - I update the xmlActress node properties, adding the two attributes Birthday and Hair. Then, I update the "same" xml node, but this time, I go though the XML DOM, xmlActresses. If the node, xmlActress, was added by reference, then all of these CFSet tags should affect the same element.
Here is a CFDump of the xmlActress node by itself:
And, here is a CFDump of the resultant xmlActresses XML DOM:
Notice that BOTH CFDumps contain the original attributes, Name and HowSexy. These were added prior to any child node appending. Also notice that only the xmlActress has BirthDay and Hair attributes which were set directly into the Xml node reference after it was appended to the xmlActresses DOM.
So clearly, when it comes to XmlAttributes, the XML Nodes are appended by value. But what about the XmlChildren? If you look at the above code, our xmlRoot reference was able to update the resultant document. This means that xmlRoot was somehow set by reference, not by value. Perhaps, ArrayAppend() will work via references for the child nodes as well:
<!--- Now, let's try to create a child node of the actress node. This node will hold a list of all the related movies. ---> <cfset xmlMovies = XmlElemNew( xmlActresses, "", "movies" ) /> <!--- Add this movie to the existing xmlActress node reference that we created above. ---> <cfset ArrayAppend( xmlActress.XmlChildren, xmlMovies ) /> <!--- Now, let's dump out our actress node. ---> <cfdump var="#xmlActress#" label="xmlActress Node" /> <!--- Let's dump out our actresses XML DOM. If the nodes are passed by reference when using ArrayAppend(), then the CFDump above should be reflected in the CFDump below. ---> <cfdump var="#xmlActresses#" labe="xmlActressed DOM" />
Here, we are adding an xml child node, xmlMovies, to the xmlActress node. Here is the CFDump of the xmlActress node:
And, here is the CFDump of the resultant xmlActresses XML DOM:
Notice that again, the actions to update the child reference did not affect the final XML document. I wonder why xmlRoot was able to act as a reference and xmlActress was only able to act as a copy. I see that xmlRoot was set using "=" operator and the other via ArrayAppend()... but, any value that cannot be passed by reference is copied no matter what operator is used (sort of).
Want to use code from this post? Check out the license.
What you've discovered here is that XML nodes are inserted into an XML document *by value*, not that " ColdFusion XML Nodes Are Passed By Value, Not By Reference".
In all real-world situations I've used them, they are certainly passed by reference.
<bbb foo="bar">Hello World</bbb>
<cfdump var="#x#" label="Original doc">
<cfset a = xmlSearch(x, "/aaa/bbb")>
<cfdump var="#a#" label="Original array">
<!--- update the array --->
<cfset a.XmlAttributes["foo"] = "No Foo">
<cfset a.Xmltext = "Updated">
<cfdump var="#a#" label="Updated array">
<cfdump var="#x#" label="Updated doc?">
<!--- create a reference to a node --->
<cfset n = x.aaa.bbb>
<cfdump var="#n#" label="Original node">
<!--- update the node reference --->
<cfset n.xmlText = "Updated xmlText by node ref">
<cfset n.XmlAttributes["foo"] = "Updated XmlAttributes value by node ref">
<cfdump var="#n#" label="Updated node">
<cfdump var="#x#" label="Updated doc?">
Nice to read blog about Cold Fusion. There are several of them in the network. I hope number will be increased
There is something that doesn't quite sit right with me about it. First off, XmlElemNew() requires you to pass in an XML reference when creating a new element. I assume that this is used to set up ownership of the Node before it actually gets put into any node tree. So, at that point, the node should be owned by the document and ready to be inserted into the DOM... what would be the point of adding by value?
And then, to add the node to the DOM, I am just calling an ArrayAppend(). There is nothing inherent to ArrayAppend() that would cause me to think that it would add to the child nodes array by value, not by reference.
I can only assume that behind the scenes, ArrayAppend() is actually checking to see what type of object it is acting on and then is performing a different task when it comes to XML than when it comes to , say, a standard array to which a structure is being appended.
Something about it just seems a little misleading. But then again, maybe it just hasn't had a chance to seep into my mind yet :)
I didn't say it was expected, predictable, or that it makes any sense (it's none of those things, from where I'm sitting)! I was more pointing out the inaccuracy of your article heading.
I would expect it to pass-by-reference when first inserting the node (which is what you seem to have expected as well).
Sorry, I didn't mean to come off as sounding attacking what you said in any way. I was just venting my slight frustrations with the way it was working.
Yes, you are totally correct in your statement - xml nodes do get passed by reference (which is why getting intermediary pointers to them work as well as XmlSearch). My title could have been much more clear.
I didn't think you were having a go. I was just clarifying what I meant.
I have another couple of tests up my sleeve... but work has unfortunately got in my way, so I'll have to have a look @ them tomorrow.
Something else I find interesting is what is returned for the parent of the free-standing node and the node from the document.
<cfif structKeyExists(xmlActress, 'XmlParent')>
<cfif structKeyExists(xmlActresses.actresses.actress, 'XmlParent') >
label="xmlActress DOM parent"
I stumbled upon this problem, and found a way around this, the problem seem to be specific to ArrayAppend/ArrayInsertAt
xmlChild = XmlElemNew(Xml,tag);
xmlChild.XmlText = "string";
However this is a bit slower than ArrayAppend (using Bluedragon at least)