Copying Children From One ColdFusion XML Document To Another

Posted May 14, 2007 at 8:37 AM by Ben Nadel

Tags: ColdFusion

The other day, Matthew Abbott had contacted me to ask about some advanced JDOM libraries. I have not used JDOM directly in any way so I couldn't really help him. But this got me thinking about XML in general but more specifically, about XML in ColdFusion. I just don't use it that often and certainly, I don't do anything cool with it. So, I thought I would take this opportunity to try some cool stuff.

For this experimentation, I have created two XML document objects: xmlDate and xmlGirls. The xmlDate XML document outlines the agenda for a hot date (who doesn't love pizza and a movie???). The xmlGirls XML document is an XML database of girls:

  • <!---
  • Build our XML data object to outline the activities
  • of our date. Right now, we only have information about
  • the girl by way of her foriegn key ID.
  • --->
  • <cfxml variable="xmlDate">
  •  
  • <date>
  • <girl id="4" />
  • <meal>
  • <location>Ben's Pizza</location>
  • <time>7:30 PM</time>
  • </meal>
  • <movie>
  • <name>Friends With Money</name>
  • <time>9:15 PM</time>
  • </movie>
  • </date>
  •  
  • </cfxml>
  •  
  •  
  • <!---
  • Here is our XML data object that holds the more detailed
  • information about our girls. We can use the IDs here to
  • populate foreign references to these girls.
  • --->
  • <cfxml variable="xmlGirls">
  •  
  • <girls>
  • <girl id="1">
  • <name>Kit Cat</name>
  • <hair>Brunette</hair>
  • <eyes>Blue</eyes>
  • </girl>
  • <girl id="4">
  • <name>Anna Banana</name>
  • <hair>Brunette</hair>
  • <eyes>Brown</eyes>
  • </girl>
  • <girl id="5">
  • <name>Marcie Darcey</name>
  • <hair>Blonde</hair>
  • <eyes>Brown</eyes>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!--- Dump out our date. --->
  • <cfdump
  • var="#xmlDate#"
  • label="xmlDate XML Data"
  • />
  •  
  • <!--- Dump out our girls. --->
  • <cfdump
  • var="#xmlGirls#"
  • label="xmlGirls XML Data"
  • />

CFDumping out the xmlDate XML document, we get:


 
 
 

 
ColdFusion XML Document Object Model For xmlDate  
 
 
 

CFDumping out the xmlGirls XML document, we get:


 
 
 

 
 
 
 
 

Now, our XML document about the date has the girl's ID, but that doesn't really help us out much. What we want to do is copy the properties of the girl from the xmlGirls document to the Girl node of the xmlDate document. My first thought about this was, no problem, just use the AddAll() method for the child nodes collection:

  • <!---
  • Add the girl properties to the girl node
  • of our date XML docuemnt.
  • --->
  • <cfset xmlDate.Date.Girl.XmlChildren.AddAll(
  • xmlGirls.Girl[ 2 ].XmlChildren
  • ) />

The problem is that when you run the above code, you get the following ColdFusion error:

WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it.

The issue, which I had never thought about, was that each Node of an XML document is owned by that document. You can't just use a single XML node in two different places and especially NOT in two different XML documents (just as I can't be both at work and at the movies at the same time!!!).

So, how do we get around this? We have to import the girl node into our xmlDate document before we try and insert it somewhere into our xmlDate XML document object model. To help accomplish this, I have come up with a ColdFusion user defined function, XmlAppend(). This UDF takes two XML nodes from two different XML documents (one from each) and then appends the child nodes of the latter to the child nodes of the former:

  • <cffunction
  • name="XmlAppend"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Copies the children of one node to the node of another document.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="NodeA"
  • type="any"
  • required="true"
  • hint="The node whose children will be added to."
  • />
  •  
  • <cfargument
  • name="NodeB"
  • type="any"
  • required="true"
  • hint="The node whose children will be copied to another document."
  • />
  •  
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!---
  • Get the child nodes of the originating XML node.
  • This will return both tag nodes and text nodes.
  • We only want the tag nodes.
  • --->
  • <cfset LOCAL.ChildNodes = ARGUMENTS.NodeB.GetChildNodes() />
  •  
  •  
  • <!--- Loop over child nodes. --->
  • <cfloop
  • index="LOCAL.ChildIndex"
  • from="1"
  • to="#LOCAL.ChildNodes.GetLength()#"
  • step="1">
  •  
  •  
  • <!---
  • Get a short hand to the current node. Remember
  • that the child nodes NodeList starts with
  • index zero. Therefore, we must subtract one
  • from out child node index.
  • --->
  • <cfset LOCAL.ChildNode = LOCAL.ChildNodes.Item(
  • JavaCast(
  • "int",
  • (LOCAL.ChildIndex - 1)
  • )
  • ) />
  •  
  • <!---
  • Import this noded into the target XML doc. If we
  • do not do this first, then COldFusion will throw
  • an error about us using nodes that are owned by
  • another document. Importing will return a reference
  • to the newly created xml node. The TRUE argument
  • defines this import as DEEP copy.
  • --->
  • <cfset LOCAL.ChildNode = ARGUMENTS.NodeA.GetOwnerDocument().ImportNode(
  • LOCAL.ChildNode,
  • JavaCast( "boolean", true )
  • ) />
  •  
  • <!---
  • Append the imported xml node to the child nodes
  • of the target node.
  • --->
  • <cfset ARGUMENTS.NodeA.AppendChild(
  • LOCAL.ChildNode
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return the target node. --->
  • <cfreturn ARGUMENTS.NodeA />
  • </cffunction>

Once we have this nifty ColdFusion XML UDF, we can easily copy the girl properties from the xmlGirls document to the xmlDate document:

  • <!---
  • Get the ID of the girl we are going to be taking
  • out on the date. We want to get more information
  • about her in our date data object.
  • --->
  • <cfset intDateID = xmlDate.Date.Girl.XmlAttributes.ID />
  •  
  • <!---
  • Search for the matching girl in our girl xml data
  • object. When searching with XPath, search for a
  • girl with the given ID. All we need is the ID since
  • each girl has a unique ID.
  • --->
  • <cfset arrGirls = XmlSearch(
  • xmlGirls,
  • "//girl[@id=#intDateID#]"
  • ) />
  •  
  •  
  • <!---
  • Check to see if we found a matching girl in our
  • girl date object.
  • --->
  • <cfif ArrayLen( arrGirls )>
  •  
  • <!---
  • Our XPath search above has returned a matching
  • girl. Now, we want to append those returned girl's
  • properties child nodes) to the girl node of our
  • Date data object.
  • --->
  • <cfset XmlAppend(
  • xmlDate.date.girl,
  • arrGirls[ 1 ]
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- Dump out the resultant XML date document. --->
  • <cfdump
  • var="#XmlDate#"
  • label="xmlDate After Girl Node Import"
  • />

Once the node is copied, our resultant xmlDate XML document looks like this:


 
 
 

 
ColdFusion XML Document Object Model For xmlDate After XmlAppend()  
 
 
 

The girl properties of Girl ID 4 copies over quite nicely. This is some very interesting stuff. It gives me all sorts of ideas about building xml documents in pieces and then easily joining them together to make a bigger, better document (like denormalizing a database).




Reader Comments

May 14, 2007 at 9:50 AM // reply »
10 Comments

Wow! This is a pretty impressive technique.

Do you know where documentation exists that talks about these built-in functions within the XML document object? I've only ever been familiar with the functions in the CF documentation, which aren't nearly this powerful.

Thanks in advance,
Toby


May 14, 2007 at 9:54 AM // reply »
11,246 Comments

@Toby,

Take a look at this:

http://java.sun.com/j2se/1.4.2/docs/api/org/w3c/dom/Node.html

It covers the w3c.org java DOM stuff. It seems that ColdFusion is wrapping around that functionality.


May 14, 2007 at 10:03 AM // reply »
10 Comments

Rock on! Thanks for the link!


May 14, 2007 at 10:28 AM // reply »
21 Comments

On a recent project, I had the need to copy one node to another 'parent' node. I just used duplicate() on the node and it worked fine.

I was told by a co-worker that it would not work because every XML node has a 'parentNode'. Apparently in CFMX 7, Cf is smart enough to remove that reference when you duplicate(). I am not certain how this would work if I tried to move it to a completely different XML document.


May 14, 2007 at 10:37 AM // reply »
11 Comments

Ben,

Check out this XMLMerge UDF I created back in February which uses XSLT to merge.

http://www.intersuite.com/client/index.cfm/2007/2/15/XMLMerge-UDF-Uses-XSLT-to-merge-root-child-nodes

Regards,

Chris


May 14, 2007 at 11:04 AM // reply »
11,246 Comments

@Chris,

That looks pretty cool. I know nothing about XSLT (I have tried to learn it but I just can't seem to get a handle on its seemly irregular programming syntax). I like what you are doing; my only issue with it is that you have to re-parse XML, which might have a lot of over head.

Cool stuff though. I really should learn more about XSLT.


May 14, 2007 at 12:57 PM // reply »
4 Comments

Working with XML in Coldfusion is painful and slow. There are much better Java library options available. My blog entry details the problems and a solution using XOM and a StAX processor:

http://orangepips.instantspot.com/blog/index.cfm/2007/3/28/XML-StAX-Processing-with-Coldfusion


May 14, 2007 at 5:46 PM // reply »
11,246 Comments

@Matt,

That is some interesting stuff. I have not done anything with XML outside of the core ColdFusion installed libraries. I will have to take a look at that other stuff sometime.


Oct 1, 2007 at 8:11 PM // reply »
1 Comments

Just wanted to say thanks for writing this comment! I ran into this same issue while doing some XML work today, and this saved me a TON of headache. Thanks!


Feb 4, 2008 at 11:19 PM // reply »
1 Comments

Hey ben this is great and it almost solved my problem. Im trying to import a specific child to another xml document in the same parent. I tried to tweak your function to accept a new argument for a specific child but it doesnt seem to be working. Some of these functions are new to me.


Mar 6, 2008 at 2:37 PM // reply »
1 Comments

Thanks, this worked great for me.


Jul 4, 2008 at 5:28 PM // reply »
1 Comments

This worked great for manipulating a Word 2003 XML document. I'm using it to insert the contents of one document into another.


Don
Jun 16, 2009 at 6:23 PM // reply »
57 Comments

So this will work with merging a bunch of xml documents that are identical in structure?


Jun 17, 2009 at 6:14 PM // reply »
11,246 Comments

@Don,

You should be able to do that. It's an interesting problem.


Don
Jun 17, 2009 at 6:38 PM // reply »
57 Comments

Yup. Basically I have information coming in from many different sources. It is the same format so it would be nice to have an easy way to just merge the documents. Right now I have to parse each one and build a master. Many steps and very slow since they can have up to a million + entries between them all. (Sales information).

Twould be nice to just say XMLMerge(doc1,doc2) or something like that.


Jun 19, 2009 at 8:36 PM // reply »
11,246 Comments

@Don,

I've been thinking about this issue and the one thing that I realize will happen is that ColdFusion won't have the ability to hold such a large XML file in memory. Why are you trying to merge them? For transfer?


Nov 12, 2009 at 1:14 PM // reply »
1 Comments

Hi Ben,
just out of curiosity, why didn't you do the code to also copy the attributes of NodeA to NodeB if Any? Is there a specific reason? I did the code, I'm just wondering if there is anything I should know.

Thanks
Faisal


Don
Nov 12, 2009 at 1:28 PM // reply »
57 Comments

How about this one -
If you work with the Amazon API for products, you know that the nodes come out basically in reverse order for any item. It will give you the node for the product, then the parent, then for the parent it gives the grandparent and so on. All digging deeper into the xml.
<node>
<ancestor>
<node>
<ancsetor>
.... up to top level node
</ancestor>
</node>
</ancestor>
</node>
So how to flip it around so I have an xml doc with the top level node (category) on down?
Lots of work. That's how. sigh.
Actually I'm thinking of converting it to an array and then going from there. Or a structure.


Nov 15, 2009 at 8:03 PM // reply »
11,246 Comments

@Faisal,

I believe the node import bring the attributes along with it.

@Don,

Hmmm, sounds funky. I assume they are returning it that way for a practical reason?? I don't have much experience with Amazon's web services (just played around with it once or twice).


Jan 15, 2010 at 1:04 PM // reply »
6 Comments

Hi, I would like to know if this is possible?

If yoou have:

<cfxml variable="xmlDate">

<date>

<meal>
<location>Ben's Pizza</location>
<time>7:30 PM</time>
</meal>

<movie>
<name>Friends With Money</name>
<time>9:15 PM</time>
</movie>

</date>

</cfxml>

And:

<cfxml variable="xmlGirls">

<girls>
<girl id="1">
<name>Kit Cat</name>
<hair>Brunette</hair>
<eyes>Blue</eyes>
</girl>
<girl id="4">
<name>Anna Banana</name>
<hair>Brunette</hair>
<eyes>Brown</eyes>
</girl>
<girl id="5">
<name>Marcie Darcey</name>
<hair>Blonde</hair>
<eyes>Brown</eyes>
</girl>
</girls>

</cfxml>

And you want to add girls as children of the root element date...
Thanks :)


Jan 16, 2010 at 4:17 PM // reply »
11,246 Comments

@Christophe,

The code I have above is for copy one node's children to the child nodes of another node (in another document). Since you want to add the ROOT element of one XML document to the children of another node (in another document), you'd have to update the code to work with that.

I think it would work in basically the same way - you'd have to access the underlying Java methods on the root node to change it's document owner.


Don
Jan 17, 2010 at 1:36 PM // reply »
57 Comments

Okay, 2 answers long time coming.
1 - The reason to merge the documents is for reporting purposes. The full document would only be created once each day at night. The server it is done on has 12G of ram so that is not a problem. (Dual quad core wooo hooo). I finally just said to myself "self, just chop off the top level opening and closing of each document, put them together, and then put the opening and closing back on." Works well.
2 - Why does Amazon do what it does? Who knows. lol I have found many of these big companies are trying to create their own way of doing APIs etc forcing us developers to jump through hoops. I think it may be like IE marching to the beat of a different drum but being big enough to force others to follow along.
Google is doing this too in their new Analytics API. What a nightmare they have there. It is like "Geeks Gone Wild". They were told "Come up with something that will confuse the snot out of the average developer just to show how smart you are"

arg


Jan 25, 2010 at 9:40 PM // reply »
11,246 Comments

@Don,

Ha ha, Geeks gone wild :) It sounds like you might want to explore some XML transform stuff if you need to get Amazon nodes in a different order (sorry, I sort of lost the conversation a bit).


Apr 17, 2010 at 4:21 AM // reply »
1 Comments

Pretty sure that Xml Objects in coldfusion are collections of arrays and structs.

There is a reference here which says you can use all the native array and struct functions to manipulate the xml

http://www.co.multnomah.or.us/cfdocs/Developing_ColdFusion_MX_Applications_with_CFML/XML7.html


Aug 13, 2010 at 10:33 AM // reply »
3 Comments

Is there ANYTHING you haven't done in ColdFusion, Mr. Nadel?


Aug 13, 2010 at 5:42 PM // reply »
11,246 Comments

@Tony,

You can definitely treat XML objects like arrays and structures when it comes to things like XML children and XML attributes. There's also all kinds of pseudo-node-wrappers that can be used as well.

@Helgi,

Ha ha, I try to cover a lot of ground :)


Sep 15, 2010 at 2:35 AM // reply »
1 Comments

F**king genius! Spent all day hitting brick wall after brick wall... Thanks mate!


Sep 23, 2010 at 10:30 PM // reply »
11,246 Comments

@Joey,

Awesome - glad this helped. Even years later, I have not found a better way to move XML nodes from one document to another; I think dipping down into Java is the only way (without doing a lot of copying).


Nov 4, 2010 at 9:44 AM // reply »
1 Comments

Today I came across this problem and your girls helped me a lot!


Nov 4, 2010 at 9:49 PM // reply »
11,246 Comments

@Christian,

Ha ha, sometimes the girls really come through!


Jan 3, 2011 at 6:42 AM // reply »
2 Comments

This solved a problem I had a while back and I've used it quite a lot since - but now found a problem. (CF8)

If the values in nodeB happen to have unicode escaped characters in them, then this UDF unescapes them.

For example:

nA:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <fai timestamp="234345454"/>

nB:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <cima>
  • <record>
  • <rlocation>Le Davier, Etrich&#xe9;</rlocation>
  • </record>
  • </cima>
  • <cfset XmlAppend(nA,xmlParse(nB)) />

result:

  • <?xml version="1.0" encoding="UTF-8"?>
  • <fai timestamp="234345454">
  • <cima>
  • <record>
  • <rlocation>Le Davier, Etrich</rlocation>
  • </record>
  • </cima>
  • </fai>


Jan 3, 2011 at 11:29 AM // reply »
2 Comments

Having struggled with this for some time, the problem is not as described above.

It is actually appending just fine.

I think I must have done something to my dev server (CF8) which causes xmlParse() not to parse entities correctly because on my production server it is working as expected.

Even though that is Railo it's probably a .jar I've replaced by mistake on my dev server or something which is causing the problem....

Oh well... Happy new year anyway.


Jan 6, 2011 at 9:29 AM // reply »
11,246 Comments

@Richard,

Is it possible that it's just outputting oddly when you debug it? Character encoding is a magical concept that my brain only understands slightly. I know there's a number of things that need to be in alignment for (moons, planets, etc.) in order for UTF-8 characters to display properly. This is doubly true when you add a database to the mix.


Gov
Oct 3, 2012 at 8:10 AM // reply »
17 Comments

Thanks Ben :-)



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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools