Ask Ben: Iterating Over A ColdFusion XML Document

Posted November 15, 2007 at 7:35 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

I would appreciate your help, in providing a workable nested solution for the following type of XML structure.

<Info Main>
<Image List>
<Image>
<Image>
<Name List>
<Name>
<Name>
<Name>
<Name>
</Info Main>
<Info Main>
</Info Main>

Where the Info Main is the top structure containing collections of images and names. I have no problem in looping the Info Main top structure as per:

<cfloop index="i" from="1" to="#arrayLen(Info_Main_Array)#"> etc, but I can't seem to work out how to connect the sub-lists of images and names. Your help would be much appreciated.

For starters, let's just make sure that we're all on the same page as to what constitutes a valid XML document in ColdFusion (or anywhere). In a valid XML document, there has to be some sort of root XML node that contains all the other nodes. Also, I don't believe the XML node names can have spaces in them. So, the node "Info Main" is not a valid name. I don't think the parser would be able to tell where the name ends and a poorly formed attribute begins. So, let's take your XML document and convert into something that the ColdFusion XML parser can actually work with:

  • <!--- Create the XML document object. --->
  • <cfxml variable="xmlDoc">
  •  
  • <info>
  •  
  • <info-main id="im1">
  •  
  • <image-list id="il1">
  • <image id="i1" />
  • <image id="i2" />
  • </image-list>
  •  
  • <name-list id="nl1">
  • <name id="n1" />
  • <name id="n2" />
  • <name id="n3" />
  • <name id="n4" />
  • </name-list>
  •  
  • </info-main>
  •  
  • <info-main id="im2">
  •  
  • <image-list id="il2">
  • <image id="i3" />
  • <image id="i4" />
  • </image-list>
  •  
  • <name-list id="nl2">
  • <name id="n5" />
  • <name id="n6" />
  • <name id="n7" />
  • <name id="n8" />
  • </name-list>
  •  
  • </info-main>
  •  
  • </info>
  •  
  • </cfxml>

As you can see, I have wrapped your XML nodes into a single root node, info. I have also lower cased all the names (remember XML is very much case sensitive) and I have added hyphens in the node names that had spaces. I have also added ID attributes to each node so that as we traverse the ColdFusion XML document object model, we will easily be able to output the ID to track our progress.

When extracting data from a ColdFusion XML document object, we can get really slick and do some cool XmlSearch() stuff with XPath, but, for this, let's keep it to some simple direct XML node access. To do this, you simply have to understand how the ColdFusion XML document is structured. Each node has a set of attributes (XmlAttributes) and a set of direct child nodes (XmlChildren). The XmlAttributes object is a key-value struct. The XmlChildren is an array.

You can get to each node using these two sets mentioned above, or you can also treat the XML document as a pseudo struct/array document. For example, if we had a parent node containing a child node and we wanted to access the second child of the first parent, we could do this:

xmlDoc.data.XmlChildren[ 1 ].XmlChildren[ 2 ]

... or, we could treat it more like a struct / array beast:

xmlDoc.data.parent[ 1 ].child[ 2 ]

Both would give you the same node reference.

That's not the best example since you can't see the real XML strcuture, so let's get back to your example. Using a combination of the two access methods, we can not iterate over the known structure of our test ColdFusion XML document:

  • <!--- Loop over the info-main nodes. --->
  • <cfloop
  • index="intMainIndex"
  • from="1"
  • to="#ArrayLen( xmlDoc.info.XmlChildren )#"
  • step="1">
  •  
  • <!--- Get a short hand for the current info-main node. --->
  • <cfset xmlInfoMain = xmlDoc.info[ "info-main" ][ intMainIndex ] />
  •  
  • info-main[ id = #xmlInfoMain.XmlAttributes.id# ]<br />
  •  
  •  
  • <!--- Get the image-list children. --->
  • <cfset arrImageList = xmlInfoMain[ "image-list" ] />
  •  
  • <!--- Loop over the image list. --->
  • <cfloop
  • index="intImageListIndex"
  • from="1"
  • to="#ArrayLen( arrImageList )#"
  • step="1">
  •  
  • <!--- Get a short hand for the current image-list node. --->
  • <cfset xmlImaegList = arrImageList[ intImageListIndex ] />
  •  
  • . . image-list[ id = #xmlImaegList.XmlAttributes.id# ]<br />
  •  
  •  
  • <!--- Loop over image nodes. --->
  • <cfloop
  • index="intImageIndex"
  • from="1"
  • to="#ArrayLen( xmlImaegList.image )#"
  • step="1">
  •  
  • . . . . image[ id = #xmlImaegList.image[ intImageIndex ].XmlAttributes.id# ]<br />
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  •  
  • <!--- Get the name-list children. --->
  • <cfset arrNameList = xmlInfoMain[ "name-list" ] />
  •  
  • <!--- Loop over the name list. --->
  • <cfloop
  • index="intNameListIndex"
  • from="1"
  • to="#ArrayLen( arrNameList )#"
  • step="1">
  •  
  • <!--- Get a short hand for the current name-list node. --->
  • <cfset xmlNameList = arrNameList[ intNameListIndex ] />
  •  
  • . . name-list[ id = #xmlNameList.XmlAttributes.id# ]<br />
  •  
  •  
  • <!--- Loop over name nodes. --->
  • <cfloop
  • index="intNameIndex"
  • from="1"
  • to="#ArrayLen( xmlNameList.name )#"
  • step="1">
  •  
  • . . . . name[ id = #xmlNameList.name[ intNameIndex ].XmlAttributes.id# ]<br />
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  • <br />
  •  
  • </cfloop>

Running the above code, we get the following output:

info-main[ id = im1 ]
. . image-list[ id = il1 ]
. . . . image[ id = i1 ]
. . . . image[ id = i2 ]
. . name-list[ id = nl1 ]
. . . . name[ id = n1 ]
. . . . name[ id = n2 ]
. . . . name[ id = n3 ]
. . . . name[ id = n4 ]

info-main[ id = im2 ]
. . image-list[ id = il2 ]
. . . . image[ id = i3 ]
. . . . image[ id = i4 ]
. . name-list[ id = nl2 ]
. . . . name[ id = n5 ]
. . . . name[ id = n6 ]
. . . . name[ id = n7 ]
. . . . name[ id = n8 ]

I hope this example helps you out in some way.




Reader Comments

Rob
Oct 8, 2008 at 8:44 PM // reply »
7 Comments

Hi Ben,

Can you help me Iterate over this advanced XML object? http://www.nycwebdesign.net/xmlFEED.cfm

That is the link i'm getting...
I have this so far:
<cfscript>
XMLContent = trim(objGet.FileContent);
XMLContent = XMLParse(XMLContent);
</cfscript>

I basically need to output this XML and set the following vars:
Term1MonthlyRate
Term1QuarterlyRate
Term1Semi-AnnualRate
Term1AnnualRate
Term1AppLink

Term2MonthlyRate
Term2QuarterlyRate
Term2Semi-AnnualRate
Term2AnnualRate
Term2AppLink

Thanks!


Oct 9, 2008 at 8:40 AM // reply »
10,640 Comments

@Rob,

Your link is giving me a ColdFusion error.


Rob
Oct 9, 2008 at 10:36 AM // reply »
7 Comments

Hi Ben...try it again. Thanks. I am curious to see how you go about iterating through that XML feed.

Thanks.


Oct 9, 2008 at 1:20 PM // reply »
10,640 Comments

@Rob,

Right now, there data on that page is a CFDump. I would need a page that points to the XML data itself in order to be able to parse and traverse it.


Jun 9, 2009 at 4:33 PM // reply »
43 Comments

Thanks, Ben! This really helped me debug a report I was working on earlier today. I was asked to run a deep text search on all the files on our site in search of some outdated URLs. Then I was to save that report to Excel. My first thought was to run the search with regular expressions and Dreamweaver. The search went through fine. It was getting to save the report in a user-friendly format where I ran into problems.

The only option for saving the search results was a Dreamweaver-specific XML format. (Not even a tab-delimited format? Come on!) That left with me little choice but to figure out how to turn this verbose XML document into something more usable. Enter CF support of XML and my being about six years behind in this area.

I looked through the guides to see how it was supposed to be done. My first attempt seemed rather logical. Using CFFile, I would load the contents of the Dreamweaver XML file to a variable, and then use CFXml to create an XML document object.

No matter what I tried, I kept getting this annoying "Content not allowed in prolog" error. I found a couple of posts on this topic including one on this site. I made sure there were no spaces before the opening XML tag. I even used regular expressions to try and eliminate any potential Byte-Order-Markers (BOMs) at the beginning. Nothing worked.

I'll tell you what did work. The sample XML here. It got parsed with no problems whatsoever. Seeing how I was able to get the CF code working using the sample in this blog post, I got desperate and decided to copy-and-paste the contents of the XML file into my CFXml block. Well what do you know? That worked too!?!??

For some reason I might never know, I could never get CFXml to create the XML object if I read in the XML code using CFFile. If the XML was hard-coded into the page, however, all was dandy. So I was able to create the report, albeit several hours later. But now I know, and knowing is half the yadda yadda yadda.

On a side note: I learned just a few minutes ago that Eclipse's search results are copy-and-paste-able. Maybe next time I'll go with Eclipse :-)


Sep 22, 2010 at 5:04 AM // reply »
4 Comments

Hi Ben,

Thanks for this helpful and clear explanation. Exactly what I was looking for.

Cheers!


Sep 22, 2010 at 2:07 PM // reply »
10,640 Comments

@Michiel,

Awesome - glad this helped. ColdFusion's XML documents are pretty awesome.


Oct 5, 2011 at 10:33 AM // reply »
1 Comments

How would you parse over this?
<body>
<orderCompleteNotificationRequest>
<ResultSuccess="1"/>
<participantID>134D375F-0577-8326-0B33-1C2717549658</participantID>
<externalParticipantID>rortisi@aiag.org</externalParticipantID>
<offerID>b88a9206-0701-45ba-93c4-13de3db8dc04</offerID>
<transactionNumber>TEST TRANSACTION NUMBER</transactionNumber>
<completionStatus>COMPLETE</completionStatus>
<pointCategoryResults>
<pointCategoryResult Title="Placeholder" PointCategoryID="174B7C2A-2007-0520-2718-5F6186597734" Percentage="98" MinBound="0" MaxBound="50" ScoreValue="49"/>
</pointCategoryResults>
</orderCompleteNotificationRequest>
</body>

I can get/pull out everything until I get to the <pointCategoryResults> node. I need to be able to read the <pointCategoryResult> elements of Title and Percentage.


Dec 9, 2011 at 10:29 AM // reply »
11 Comments

Hi Ben
I think i'm having a similar problem to @Rob

I think the problem is being caused because of namespaces.

I have put my content on http://www.fusebox.co.za/terminal_query.cfm where you will see the raw XML as well as the dump.

The XML is returning 2 transactions which i need to get into a digestable format.

Would you mind taking a look?
I'm sure im not the only one going grey because of this.

Thank you so much for sharing your knowledge!


Dec 9, 2011 at 7:20 PM // reply »
11 Comments

Hi all
Just thought I'd share the solution to my question above:

<cfset xmlp = xmlparse(cfhttp.filecontent)>
<cfset xmlitems = xmlp['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ns1:queryResponse']['returnTranData']>
<cfset availableBalance = xmlp['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ns1:queryResponse']['availableBalance']>
<cfoutput>Available balance: #availableBalance#</cfoutput><Br /><Br />
<cfloop from="1" to="#arraylen(xmlitems['item'])#" index="i">
<cfoutput>
TransactionID: #xmlitems['item'][i].TransactionID.xmltext#<br />
TranType: #xmlitems['item'][i].TranType.xmltext#<br />
</cfoutput>
<br />
</cfloop>


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 »