Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

ColdFusion XML Nodes Are Passed By Value, Not By Reference

By Ben Nadel on
Tags: ColdFusion

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:


 
 
 

 
ColdFusion XML Actress Post ArrayAppend()  
 
 
 

And, here is a CFDump of the resultant xmlActresses XML DOM:


 
 
 

 
ColdFusion XML Actresses Post ArrayAppend() 
 
 
 

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:


 
 
 

 
ColdFusion XML Actress Post ArrayAppend() 2  
 
 
 

And, here is the CFDump of the resultant xmlActresses XML DOM:


 
 
 

 
ColdFusion XML Actresses Post ArrayAppend() 2  
 
 
 

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).




Reader Comments

Ben,
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.

Sample code:

<cfxml variable="x">
<aaa>
<bbb foo="bar">Hello World</bbb>
</aaa>
</cfxml>
<cfdump var="#x#" label="Original doc">

<cfset a = xmlSearch(x, "/aaa/bbb")>
<cfdump var="#a#" label="Original array">

<!--- update the array --->
<cfset a[1].XmlAttributes["foo"] = "No Foo">
<cfset a[1].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?">

--
Adam

Reply to this Comment

Nice to read blog about Cold Fusion. There are several of them in the network. I hope number will be increased

Reply to this Comment

@Adam,

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 :)

Reply to this Comment

Hi Ben.
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).

--
Adam

Reply to this Comment

@Adam,

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.

Reply to this Comment

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.

--
Adam

Reply to this Comment

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')>
<cfdump
var="#xmlActress.XmlParent#"
label="xmlActress Parent"
/>
</cfif>

<cfif structKeyExists(xmlActresses.actresses[1].actress, 'XmlParent') >
<cfdump
var="#xmlActresses.actresses[1].actress.XmlParent#"
label="xmlActress DOM parent"
/>
</cfif>

Reply to this Comment

Hi,

I stumbled upon this problem, and found a way around this, the problem seem to be specific to ArrayAppend/ArrayInsertAt

xmlChild = XmlElemNew(Xml,tag);
pos=ArrayLen(xmlbase.XmlChildren);
xmlbase.XmlChildren[pos+1]=xmlChild;

xmlChild.XmlText = "string";

However this is a bit slower than ArrayAppend (using Bluedragon at least)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.