Getting A ColdFusion XML Document Reference From An XML Node In Order To Create A New XML Element

Posted November 15, 2010 at 10:20 AM by Ben Nadel

Tags: ColdFusion

This weekend, I was experimenting with some sexy ColdFusion XML manipulation when I ran into a bit of a problem. I found myself in a situation in which I needed to create a new XML element based on an existing XML element. The problem with doing this is that in order to create a new XML element in ColdFusion, you need a reference to the XML document in which the new element will be created (XML elements are "owned" by an XML document). So, if you only have an existing XML element, you need to finagle your way back up to the original XML document reference.

In ColdFusion, XML can be a bit confusing because there is a subtle yet very important difference between a ColdFusion XML document and the XML data that it contains. This difference is made even less obvious when you consider that xmlSearch() can be used to search both XML documents and XML nodes. Most of the time, this difference doesn't matter to us; but, when we need to create a new XML element, understanding this difference is critical. To see why, take a look at the following code:

  • <!--- Create a simple XML document. --->
  • <cfxml variable="girls">
  •  
  • <girls>
  • <girl>
  • <name>Katie</name>
  • </girl>
  • <girl>
  • <name>Kim</name>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!---
  • Create a new element for the girls document. When doing
  • this, we are passing in the XML document reference.
  • --->
  • <cfset isHotNode = xmlElemNew( girls, "isHot" ) />
  •  
  • <!---
  • Create another new element for the girls document; however,
  • this time, we are going to use an XML node as the "parent"
  • document.
  • --->
  • <cfset isCuteNode = xmlElemNew( girls.xmlRoot, "isCute" ) />
  •  
  •  
  • <!---
  • Now that we have tried to create our two nodes, let's check
  • to make sure that both of them exist as variables.
  • --->
  • <cfoutput>
  •  
  • isHotNode Exists: #structKeyExists( variables, "isHotNode" )#
  •  
  • <br />
  • <br />
  •  
  • isCuteNode Exists: #structKeyExists( variables, "isCuteNode" )#
  •  
  • </cfoutput>

In this demo, we have a ColdFusion XML document referenced in the variable, girls. When then try to create two new XML elements for said XML document using xmlElemNew(). One call to xmlElemNew() uses the girls variable while the other uses the root XML element. When we run this code, we get the following output:

isHotNode Exists: YES

isCuteNode Exists: NO

As you can see, when using the XML document reference, xmlElemNew() was able to create and return a new XML element. When using an XML element, on the other hand, xmlElemNew() returned null. In other words, ColdFusion could not create a new XML element based on another XML element.

I think this is a significant shortcoming of the xmlElemNew() function; but, we can get around it. Look at how we got the root XML element in the above demo:

  • girls.xmlRoot

The XML element that we used in the latter part of our demo - the root XML element - is a child of the XML document. This then begs the question - is the ColdFusion XML document a parent of the root XML element? To test this, we can rerun the above code using an xmlParent reference:

  • <!--- Create a simple XML document. --->
  • <cfxml variable="girls">
  •  
  • <girls>
  • <girl>
  • <name>Katie</name>
  • </girl>
  • <girl>
  • <name>Kim</name>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!---
  • Create a new element for the girls document. When doing
  • this, we are passing in the XML document reference.
  • --->
  • <cfset isHotNode = xmlElemNew( girls, "isHot" ) />
  •  
  • <!---
  • Create another new element for the girls document; however,
  • this time, we are going to dip down into the actual XML nodes
  • and then come back up to the document level using xmlParent.
  • --->
  • <cfset isCuteNode = xmlElemNew( girls.xmlRoot.xmlParent, "isCute" ) />
  •  
  •  
  • <!---
  • Now that we have tried to create our two nodes, let's check
  • to make sure that both of them exist as variables.
  • --->
  • <cfoutput>
  •  
  • isHotNode Exists: #structKeyExists( variables, "isHotNode" )#
  •  
  • <br />
  • <br />
  •  
  • isCuteNode Exists: #structKeyExists( variables, "isCuteNode" )#
  •  
  • </cfoutput>

This time, when creating the isCuteNode XML element, we dip down into the root XML element but then try to climb our way back up to the XML document using xmlParent:

  • girls.xmlRoot.xmlParent

When we run this code, we get the following output:

isHotNode Exists: YES

isCuteNode Exists: YES

Woohoo! ColdFusion was able to create both XML elements. This tells us that the XML document reference required by the xmlElemNew() function can be obtained from the root XML element.

In the preceding example, it was easy to get the XML document reference because we started with the root XML node; but, what if we don't have the root XML node? What if we have some deeply nested XML element? This was exactly the situation I found myself in over the weekend - I had to create a new XML element based on some arbitrary existing XML node reference.

In that kind of a situation, a single call to xmlParent probably won't do the trick (unless you have the root XML element). As such, we need a way to get to the root XLM element such that we can get to the parent XML document. Luckily, xmlSearch() allows us to do just that. By using the XPath query, "/*", we can select the root XML element from any XML node starting point:

  • <!--- Create a simple XML document. --->
  • <cfxml variable="girls">
  •  
  • <girls>
  • <girl>
  • <name>Katie</name>
  • </girl>
  • <girl>
  • <name>Kim</name>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!--- Get the katie node. --->
  • <cfset katieNodes = xmlSearch( girls, "//name[ text() = 'Katie' ]" ) />
  •  
  • <!---
  • xmlSearch() always returns an array of nodes. Get the first
  • (only) resultant node.
  • --->
  • <cfset katie = katieNodes[ 1 ] />
  •  
  •  
  • <!---
  • Now that we have our katie node, we need to get our XML Document
  • object. We can do this in two ways - using pure XPath or using a
  • combination of XPath and node traversal. In either case, the
  • trick is to remember that our XML Doc is the parent object of
  • the root node (xmlRoot).
  • --->
  •  
  • <!---
  • Get the doc using just XPath. In this case, we are going to go
  • to our root node and then get its parent.
  • --->
  • <cfset xmlDocNodes = xmlSearch( katieNodes[ 1 ], "/*/.." ) />
  •  
  • <!---
  • Get the doc using XPath and xmlParent. In this case, we'll use
  • XPath to get the root and then xmlParent to get the doc.
  •  
  • NOTE: See output for further analysis.
  • --->
  • <cfset xmlRootNodes = xmlSearch( katieNodes[ 1 ], "/*" ) />
  •  
  •  
  • <!--- Check to make sure both approaches result in an xml doc. --->
  • <cfoutput>
  •  
  • xmlDocNodes: #isXmlDoc( xmlDocNodes[ 1 ] )#
  •  
  • <br />
  • <br />
  •  
  • xmlRootNodes: #isXmlDoc( xmlRootNodes[ 1 ].xmlParent )#
  •  
  • </cfoutput>

This demo uses two approaches for obtaining the XML document reference. Both use xmlSearch(); but, each gets the XML document reference in a slightly different way. The first uses the ".." path traversal directive to jump from the root XML element to the XML document reference; the second uses only the "/*" traversal directive and then uses the xmlParent property to get the XML document reference (much like our second demo). When we run this code, we can see that both approaches work:

xmlDocNodes: YES

xmlRootNodes: YES

We now know that we can get the XML document reference based on any XML element; but, this is kind of sloppy. As a last step in our exploration, let's encapsulate this logic in a nice ColdFusion user defined function (UDF), xmlGetDoc():

  • <cffunction
  • name="xmlGetDoc"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I get the XML docuemnt reference that contains the given XML node.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="node"
  • type="any"
  • required="true"
  • hint="I am the XML node for which we are getting the parent XML document."
  • />
  •  
  • <!--- Search the document nodes. --->
  • <cfset var xmlDocNodes = xmlSearch( arguments.node, "/*/.." ) />
  •  
  • <!--- Return the XML document reference. --->
  • <cfreturn xmlDocNodes[ 1 ] />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Create a simple XML document. --->
  • <cfxml variable="girls">
  •  
  • <girls>
  • <girl>
  • <name>Katie</name>
  • </girl>
  • <girl>
  • <name>Kim</name>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!---
  • Get the XMl Document based on an XML node. Notice that we are
  • testing two cases - an XML node and an XML document. Since
  • xmlSearch() works on both types of reference, we should be able
  • to handle both use cases transparently.
  • --->
  • <cfoutput>
  •  
  • isXmlDoc: #isXmlDoc(
  • xmlGetDoc( girls.xmlRoot.girl[ 1 ] )
  • )#
  •  
  • <br />
  • <br />
  •  
  • isXmlDoc: #isXmlDoc(
  • xmlGetDoc( girls )
  • )#
  •  
  • </cfoutput>

Notice in this demo that we are testing our xmlGetDoc() UDF with both an XML element reference and an XML document reference. Since xmlSearch() works with both types of values, our xmlGetDoc() UDF does not need to care about the subtle differences between XML documents and XML nodes. When we run the above code, we get the following output:

isXmlDoc: YES

isXmlDoc: YES

As you can see, we were able to obtain the XML document reference from both an existing XML node and an existing XML document value.

To me, it seems like a shortcoming that the xmlElemNew() function cannot create a new XML element based on any existing XML node reference. Since XML nodes can be searched, aggregated, and passed by reference, it would make more sense for the xmlElemNew() function to be more flexible. However, until the behavior of xmlElemNew() changes, at least we can manually locate the parent XML document.




Reader Comments

Nov 15, 2010 at 12:55 PM // reply »
10 Comments

Ben! what is the best way to insert the node in a xml file. some thing like at level 4


Nov 16, 2010 at 5:57 PM // reply »
10,743 Comments

@Raghuram,

Try looking at the "You might also be interested in" links as the bottom of this blog post. There's a host of entries on building XML values.

Also, today, I just posted this:

http://www.bennadel.com/blog/2055-Thoughts-About-XDOM-For-Easier-ColdFusion-XML-Manipulation.htm

Some initial thoughts on ways to manipulate XML more easily.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 16, 2012 at 8:18 PM
Best Of ColdFusion 10 Contest Entry - HTML Email Utility
Just found this, looks good! I'm trying to run it on local, it's the 64bit version and I'm experiencing horrible lag. On average the generate.cfm processes the content change in 60-90 seconds. I've ... read »
May 16, 2012 at 6:40 PM
Maintaining Sessions Across Multiple ColdFusion CFHttp Requests
I am trying to integrate this CFHTTPsession into an application that will log into zeekrewards.com to post ads and I am not having any luck. The code works perfectly for logging into other websites, ... read »
May 16, 2012 at 2:44 PM
Creating A Sometimes-Fixed-Position Element With jQuery
Thank you, very useful technique! Worked like a charm. ... read »
May 16, 2012 at 1:58 PM
Movies As A Religious Experience
Acting can, in a way, ruin the movie-goer's experience. I used to be able to get so caught up in movies and their plots, and totally engaged. But lately, I haven't been able to as much with a lot o ... read »
May 16, 2012 at 1:52 PM
The Science Of Optimal Post-Exercise Nutrition
children of this age eat very less vegetables so u can opt for salads they will like it also carrot ,cucumber,onion and as far as pulses are concerned u can boil them ,give him along with mashed rice ... read »
May 16, 2012 at 1:34 PM
Strange ColdFusion JRUN Stack Overflow Error
Hey, Recently I updated my jrun4 using the latest updater 7 and now i am having memory issues :(:(:( any help is appreciated ... read »
May 16, 2012 at 9:56 AM
ColdFusion 10 Beta, Apache Tomcat, And Symbolic Links On Mac OSX
Hi, Now that ColdFusion 10 is out I have stumbled over this as well and I cannot figure out the proper solution. We're running virtual hosts via Apache2; the ColdFusion-applications store their fil ... read »
May 15, 2012 at 6:03 PM
Movies As A Religious Experience
@Ben, I don't know whether you'd consider this a religious observation, but it seems to me, in a sense, movies multiply how many lives we get to have. Each movie is like a little extra life we get ... read »