Ask Ben: Changing The Root Node In A ColdFusion XML Document

Posted April 21, 2009 at 9:20 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

I just had a quick regex. I need to replace the first and last xml nodes in an xml string. Basically was doing a quick and dirty way to change the root node of an xml document instead of creating a new root node and copying recursively all the data from one xml object to the new xml object. I can do a simple find "xxx" and replace it with "yyy" but xxx may be part of a child node somewhere like xxxBen or something. so i really want to pinpoint the start and end tags of the string, and also deal with the declaration, and leave all attributes in the root node (if they exist) intact. When you get a minute, do you mind helping?

I know you want to see the string-parsing method, rather than the new root node creation, but I will show you both methodologies as I think that they are both nice to know about. The major difference with the two is that the latter (new root node creation) requires you to parse the XML string into an actual ColdFusion XML document whereas the former only requires that you parse the string with a regular expression.

So first, let's start out be creating our XML data string and storing it in a ColdFusion content buffer:

  • <!---
  • Create an XML string that has a root node that gets
  • repeated within the body of the XML as well.
  • --->
  • <cfsavecontent variable="strXmlData">
  •  
  • <list id="my-to-do-list">
  • <item>
  • List Item A
  • </item>
  • <item>
  • List Item B
  • </item>
  • <item>
  • <list>
  • <item>
  • Sub Item A
  • </item>
  • <item>
  • Sub Item B
  • </item>
  • </list>
  • </item>
  • <item>
  • List Item D
  • </item>
  • </list>
  •  
  • </cfsavecontent>

You'll notice that the element node, "list," appears several times in the document - the root node and a nested node. I have done this to make sure that neither of these techniques replaces the nested node incorrectly.

OK, so now let's replace the root node, "list," with the new root node, "masterlist." With our first approach, we are going to use Regular Expressions to replace the first open tag and last close tag of the xml document string:

  • <!---
  • Replace the first and last nodes of the document with a
  • new node name. We are going to do this in two step. Start
  • with the first tag.
  • --->
  • <cfset strXmlData = REReplace(
  • strXmlData,
  • "<\w+",
  • "<masterlist",
  • "one"
  • ) />
  •  
  • <!---
  • Now, we want to replace the LAST close node in the document.
  • Because we want to replace the last close node, we want the
  • expression to end in the $ so that it is the end of the
  • document.
  • --->
  • <cfset strXmlData = REReplace(
  • strXmlData,
  • "(</)\w+([^>]*>\s*)$",
  • "\1masterlist\2",
  • "one"
  • ) />
  •  
  •  
  • <!--- Parse and output new XML. --->
  • <cfdump
  • var="#XmlParse( Trim( strXmlData ) )#"
  • label="New XML Document"
  • />

To keep the node attributes intact in our first REReplace(), we are only replacing the open bracket and node name of the first element (ie. <list becomes <masterlist); in doing so, we are only changing the node name and nothing else. Then, in our second replace, we replace the last close node of the document. Here, we actually have to replace the entire node as we need to use the $ to signify the end of the string data (in the regular expression). However, by using captured groups in our regular expression, we can replace everything other than the node name without having to know much about it.

When we replace the node and CFDump out the resulting XML document, we get the following:

 
 
 
 
 
 
Repalce The Root Node Of An XML Document With REReplace(). 
 
 
 

As you can see, the root node, "list," has been replaced with, "masterlist," and the XML attributes have been kept intact.

Ok, now that you see how to do this with regular expressions, let's take a look at actually changing the structure of an existing XML document. Well, sort of - before we have an actual XML document, we are going to wrap the existing XML string in a our new root node, "masterlist." Then, we're going to parse it into an XML document and transfer the original child nodes and XML attribute data into the new root node. Once this is done, we're simply going to delete the old root node.

  • <!--- Add the new XML root node around the document. --->
  • <cfsavecontent variable="strXmlData">
  •  
  • <masterlist>
  • #strXmlData#
  • </masterlist>
  •  
  • </cfsavecontent>
  •  
  • <!---
  • Now, parse the xml string into an XML document and transfer
  • the child nodes to the master list root node.
  • --->
  • <cfset xmlData = XmlParse( Trim( strXmlData ) ) />
  •  
  • <!---
  • Add all of the original children to the new root of the
  • XML document. This creates a *copy* of the original child
  • nodes, NOT a copy-by-reference!! You will lose any references
  • you had to the original nodes.
  •  
  • NOTE: This uses the undocumented AddAll() method. If you might
  • want to wrap this up in a UDF, ArrayAppendAll().
  • --->
  • <cfset xmlData.XmlRoot.XmlChildren.AddAll(
  • xmlData.masterlist.list.XmlChildren
  • ) />
  •  
  • <!--- Copy any XML attributes. --->
  • <cfset StructAppend(
  • xmlData.XmlRoot.XmlAttributes,
  • xmlData.XmlRoot.XmlChildren[ 1 ].XmlAttributes
  • ) />
  •  
  • <!--- Delete first child to get rid of old root node. --->
  • <cfset ArrayDeleteAt( xmlData.XmlRoot.XmlChildren, 1 ) />

There's a few points to take away from the above code. For starters, we are using the undocumented AddAll() method to append an entire array to another array. If you are uncomfortable doing this, I would recommend just making a UDF called ArrayAppendAll() and wrapping the .AddAll() method call in that (in case the feature ever becomes unavailable in future versions of ColdFusion). Second, when we transfer the XML children to the new root node, these nodes get transferred by value, not by reference. This means that if you have any existing variable references to the original child nodes, those references will not point to the transported XML children.

That said, this method results in the exact same XML document as the regular expression replace method. The first is going to be more efficient as it's only string parsing. The latter has string parsing (into XML) and XML document manipulation - a lot more going on. But, I thought it could be beneficial to see both techniques in order to make the most informed decision.




Reader Comments

Apr 21, 2009 at 8:33 PM // reply »
8 Comments

Ben,

Without running this, to me it looks like the first method will keep the "id" attributes with a value of "my-to-do-list" in-tact for the root node. But, it looks like since that attributes is not in the new root node that your are adding in the second that it will be wiped out.

Is that the case? or am I miss-understanding what will happen when you add all the children?


Apr 21, 2009 at 9:08 PM // reply »
10,640 Comments

@Chris,

The StructAppend() in the second version takes care of the attributes. So yes, I do have to do a bit more finagling to get the attributes to copy.


Apr 21, 2009 at 11:27 PM // reply »
6 Comments

Hi Ben

This would be so easy with XSLT!

Here you go (put this after your XML cfsavecontent):

<cfsavecontent variable="xsl">
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:element name="masterlist">
<xsl:attribute name="id">
<xsl:value-of select="//@id" />
</xsl:attribute>
<xsl:copy-of select="child::*" />
</xsl:element>
</xsl:template>
</xsl:transform>
</cfsavecontent>
<cfcontent variable="#ToBinary(ToBase64(XMLTransform(strXmlData,xsl)))#" type="text/xml" />

Cheers
Matthew


Apr 22, 2009 at 10:59 AM // reply »
46 Comments

@Matthew
I see you match all the attributes, but then add an ID attribute. How do you add the attributes if you dont know what the attributes are? It may or may not be an ID attribute. Is this easy to do as well in XSLT?


Apr 22, 2009 at 11:54 AM // reply »
10,640 Comments

@Matthew (1),

Ahhh, I always forget about XSLT. I've used it a bunch of times but for some reason it never pops into my head as an answer. Thanks a lot for the reminder!

@Matthew (2),

You can copy all attributes using XSLT - you don't have to specify the given attribute. I leaned this when testing XSLT on XHTML:

http://www.bennadel.com/index.cfm?dax=blog:1455.view

I'll try to put this example in place as I need to keep hammering it into my mind!! :)


Apr 24, 2009 at 10:21 AM // reply »
10,640 Comments

@Matthew 1,

Thanks for the XSLT tip.

@Matthew Abbott,

I posted an example that copies the root node with new name and dynamic attributes. Mostly, I just wanted to practice my XSLT - it gets so rusty so fast:

http://www.bennadel.com/index.cfm?dax=blog:1573.view


Nov 8, 2010 at 10:53 AM // reply »
18 Comments

Hi Ben,

I have got a requirement to read an XML document (specifically in iTunes XML format) and present it to user in an HTML Form page where they can edit element values and properties (attributes) and can submit the form and I need to save them back in the same XML doc with updated values.
On top of that I need to offer a way so user can add more elements say actor's in the same document.

I was wondering if you can point me to right direction on what's the best way to do it or if you have any example on how to do this?

I am thinking of reading the XML and presenting in an HTML form and providing DHTML way with JScript to add or remove elements, do you think this is the right way forward?

Thanks in advance.

Philip


Nov 10, 2010 at 10:15 AM // reply »
10,640 Comments

@Philip,

Wow, that's a really interesting problem. I don't have any great advice. I guess a DHTML-style interface that allows a user to drill down through the XML would be very useful. But then, posting the information back to the server? I guess you would have to serialize the data back to XML before you post it?

I'll do something thinking on this. That might actually be a fun problem to try out :)


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
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »