XML parsing in Coldfusion has improved a lot in the last few versions but converting a complex XML object to a Coldfusion structure is still a struggle I beleive. There are a few custom tags out there for doing this but the ones that I have used or know are all only for simple structures, not nested or subnested structures. So I use this little function I and one of my other friends worked on and it works like a charm.
Update (27/12/2008) : The new code has been upload on riaforge and the usage is now very simple . It now takes the XML file and converts to Struct without doing all the Xpath’s before the function call. Here is a sample usage.
[xml]
[/xml]
The new code is here as well – Xml2Struct CFC
Anything below is now outdated :-
Update : Project can now be downloaded from here
A sample usage of the fucntion is below :-
Read the XML file into a Coldfusion variable, this can be a from a file locally or via a CFHTTP call or a webservice call.
[xml]
You need to know the root element of the XML document and pass that to the function.
Then call the function to convert it to a Structure.
[xml]
The function itself can be found here.
The only problem that I notice about this is if the root element has any default namespaces (“xmlns=’blabla’) , the XmlSearch doesnt recognise it. Apart from that, it works like a charm.
The XML file used in this sample can be found here books sample xml file .
Oh man! I remember that. This is great piece of code. Its a shame we can’t say where this was used 🙁 Anuj – I think its worth of placing this code @ riaforge.org.
I agree Radek. I am gonna polish it a bit and make it a CFC and put it up on Riaforge.
So this is additional some docs for this function. When you have more than one book in catalogue the function will create array of structs in book struct (as you can see on dump). When you have just one book the function will create struct in struct (without an array). So you must check if book is an array or struct. If its an array it means you have more than one book.
There is also another feature here which isn’t preented on the screen shot. If you have something like:
John Locke
you’ll get struct with these keys:
– author
– author_arguments
First item will be node value, second item will be struct where keys are arguments names and values are argments values.
Anuj – correct me if i’m wrong.
In above comment, instead of John Locke should be:
<author lang="en_GB">John Locke</author>
thats correct Radek. If there are any attributes to any XML element, the outupt look like it is here. https://www.anujgakhar.com/wp-content/uploads/2007/11/screenshot002.jpg
So the only rule is to know the root element of the XML document and this fucntion does the rest !
The project can be found here now http://xml2struct.riaforge.org/
I guess I’m not sure why you’d do this? Isn’t it just as easy to use the raw XML? Why go to the extra step of converting to a struct/array of structs?
I guess XML parsing is OK when you know the structure of the XML. The reason I initially worked on this was because one of the webservice calls I made was returning an unpredicatable and heavily nested XML back and it was hard to work with that. Thats how I ended up with this function. Apart from that, its a nice thing to be able to convert to a structure. 🙂
Could you give an example of how you would do a cfhttp call? I am not sure how you would define the variable then.
One more thing can you show an example of how you would display the data for instance if you didnt want to do a dump you just wanted to display the book title.
Steven, in a CFHTTP call , you would have something like :-
<cfset myXml = XmlParse(cfhttp.fileContent) />
and once you have done this
<cfset allBooks = “#ConvertXmlToStruct(responseBody[1], structnew())#”>
you can do
<cfset firstTitle = allBooks[1].title />
Can you email me off post? steven@hotelkingdom.com
Hi Anuj
Nice post.
I feel we can also do away with the requirement of knowing the root element by dynamically getting the root element as :
instead of:
mentioned in your example.
Though we can only use structKeyList(myXML) to get the root element as root element will always be a single element but I just put the listfirst to be doubly sure.
Hi Anuj
Nice post.
I feel we can also do away with the requirement of knowing the root element by dynamically getting the root element as :
<cfset xmlBody= xmlSearch(myXml,”//#listfirst(structKeyList(myXML))#”)>
instead of:
<cfset xmlBody= xmlSearch(myXml,”//catalog”) />
mentioned in your example.
Though we can only use structKeyList(myXML) to get the root element as root element will always be a single element but I just put the listfirst to be doubly sure.
@Rahul, After I posted it, I figured that we can actually find the root node dynamically by doing this.
<cfset xmlBody= xmlSearch(myXml,”./node()”)>
Thats a better way of doing it, i have an updated vrsion of this code but just didnt get the time to post it here.
ya cool, xPath is even better
ya I quite like Xpath…
Will this function work correctly in CF MX 6.1? I don’t seem to get it right, no matter what I try…
@WikWikiman,
I have tested this only on CF 7 and 8 but I cant see a reason off the top of my head why it would not work in 6.0.
can you point out the error you are getting ?
@Anuj,
I’m not at work at this moment; I’ll post some code tomorrow. Essentially, it seemed as if I was always passing in the wrong type of XML document…
Just to put things straight: shouldn’t your example in the original post be like this: ConvertXmlToStruct( XmlBody, structnew()) ? (I fail to see where CF would look to find responseBody[1]…)
@Wikiwikiman,
I know, the example should have been better. I have been trying to get some time to update this post with some better example. I will do it this week. But you are free to send me the code and i can surely point you in the right direction.
So here’s my code (CFMX 6.1):
If I now try this:
then I get this error message:
>> You have attempted to dereference a scalar variable
>> of type class java.lang.String as a structure with members.
That’s logical: a String does not have ‘XmlChildren’. So I need to pass in an XML document object (you could add a check for that in the function, by the way, using the IsXmlDoc() function). But when I try this:
then I get another error message:
>> Element XMLCHILDREN is undefined in AXML
pointing to line 17 in your function…
OUCH – second attempt…
So here’s my code (CFMX 6.1) for real, I hope:
<cffile action=”read” file=”#somexmlfile#” variable=”filecontent” />
<cfset parsed = XmlParse( filecontent ) />
If I now try this:
<cfset datainastruct = ConvertXmlToStruct( filecontent, StructNew() ) />
then I get this error message:
>> You have attempted to dereference a scalar variable
>> of type class java.lang.String as a structure with members.
That’s logical: a String does not have ‘XmlChildren’. So I need to pass in an XML document object (you could add a check for that in the function, by the way, using the IsXmlDoc() function). But when I try this:
<cfset datainastruct = ConvertXmlToStruct( parsed, StructNew() ) />
then I get another error message:
>> Element XMLCHILDREN is undefined in AXML
pointing to line 17 in your function…
You need to add another line in there.
<cffile action=”read” file=”#somexmlfile#” variable=”filecontent” />
<cfset parsed = XmlParse( filecontent ) />
<cfset responseBody = xmlSearch(parsed ,”/node()”) />
<cfset datainastruct = ConvertXmlToStruct( responseBody[1], StructNew() ) />
As I said in the post, it needs the root element of the XML, I am going to have to do all of this in the function itself ideally.
let me know how it goes.
OK, now we’re talking – even in CFMX 6.1. Thanks for the helping hand.
Allow me to make a suggestion: why not change the function so that it takes the raw string data (from a file or whatever) as input and returns the Struct as output? That way, all the special preparation is where it belongs: in the function itself. As a developer, I hand over the XML stream and I get my CF struct in return for further manipulation… Easy to remember and easy to do.
yeah I agree to that and I have just now updated the project code at http://xml2struct.riaforge.org/
The function now just takes a XML file and converts to Struct. Like it should have from the beginning.
Updated this post as well for future readers.
I’m using CFMX 6.1 and still getting the latest .cfc to work for me. The failure is on line 18 of your .cfc and the error is:
Document root element is missing. Document root element is missing.
The error occurred on line 18.
I’m not sure how to fix this.
@Bruce: I recall having seen that same message… but I can’t remember how I got it to disappear ;-(
Make sure that you are not calling the XML parsing and conversion code within a CFOUTPUT section; make sure that your XML is correct, well-formed and valid – the CF XML parser is quite strict and intolerant.
I have looked into the .cfc and find that the most recent posting has a problem. The first time the function is called it passes in a string which is correct. It is the xml text file read into a variable that is passed. But the two recursive calls to the same function inside the function both appear to pass an XML object. This is what I believe is causing the error. I have modified the code in my local copy so that in both cases it passes in an array. I moved the ParseXML outside the function for the original call. This at least gets around the error.
I now have a new question on this code. Does it transfer parameters for xml tags into the struct? It doesn’t appear to do so. For example the catalog/book example XML has an id parameter on the book tag () but I don’t find that value anywhere in the struct produced. Is this intended?
@Bruce, Thanks for pointing it out. At the moment, the function doesnt do anything with the attributes of the root XML node. Any attributes in the child nodes will be picked up. I will have to modify the code to include that functionality. I will give it a go once I get some free time.
Each iteration of the recursive call to the function changes the child nodes to become the root XML node which means none of the attributes are picked up. But maybe I misunderstand.
@Bruce, In the example books.xml try adding attributes to one of the description nodes or title nodes , they do get picked up.
So does it only pick up attributes from the bottom level nodes? Given that the code “walks” down through the hierarchy I would guess that it does not pick up the top and middle level node attributes.
http://pastebin.com/f166d90f9
I have pasted some code here
line 56-68 are new lines. What was happening is , for any repeating nodes, (in this example, “book” node) the attributes were ignored. but all child node attributes are picked up. Now this code, creates a new _attributes key in the resulting structure that takes care of this issue.
@Bruce, can you test this now ?
To overcome the namespace issue you might want to try this http://tinyurl.com/2c3fda
@Rahul, Yes I am aware of that. I wrote about this in this post.
https://www.anujgakhar.com/2007/11/21/extracting-links-using-xpath/
Hi! I keep getting an error on line 17
Premature end of file.
Here’s my code:
the file (#application.xmltemplateroot_phy##XMLData#) is in a query output b/c I have many xml files I need converted to coldfusion structures. Any help would be greatly appreciated.
Hi! It looks like my code didn’t go through:
Here’s my code:
Thanks again!
I’m not sure why when I copy and paste the error the line of code doesn’t display. I will type it out.
This is the line it errors on with Premature end of file:
cfset axml = XmlSearch(XmlParse (arguments.xmlNode, “/node()”)
Let’s see if this comes through.
Okay here the other code:
cffile action=”read” file=”#application.xmltemplateroot_phy##XMLData#” variable=”filecontent”
cfset objXml = createObject(“component”,”xml2Struct”)
cfset myXml = XmlParse(filecontent)
cfset responseBody = xmlSearch(myXml,”/node()”)
cfset datainastruct = objXml.ConvertXmlToStruct( responseBody[1], StructNew() )
cfdump var = “#objXml.ConvertXmlToStruct(responseBody[1], structnew())#”
Thanks again!
@Michaela, it looks like you are not using the latest version of the CFC, also, what version of CF are you on?
you might want to me email me offpost and I will be glad to help…more details on the contact page.
The current version up is broken. I fixed it by adding .toString() to axml.XmlChildren[i] in the recursive calls on lines 40 & 54.
@micah, although I havent tested it out yet, but thanks for posting the fix here.
I am using CF 6.1 if that makes any difference. Its probably a bad solution though since it causes the xml to be converted back and forth between its string
and XML document object representations. For some reason xmlObj.xmlChildren[1] is not of the XML document object type so that makes the obvious solution of just passing the children recursively as XML document objects not work.
Hi. This is a terrific piece of code.
But I must modify it in some way, because I’m dealing with a problem that seems impossible to solve using the code “as it is”. It is also a nice challenge to think over.
THe problem is that the XML must be a “well formed” compliant document or the code will fail with Error 500 behaviour.
So some Try-Catch traps must be included to degrate gracefully.
I don’t know where to put them, but I can provide a file that crashes the code.
If you are interested just drop me a line.
@Sergio, you can simply check for the validity of the XML before you even call the function. Its best to keep it outside the function I guess.
Do you have any idea how awesome you are????
FYI, the link is broken, I found on RIAFORGE
https://www.anujgakhar.com/wp-content/uploads/2008/02/xml2struct.cfc.txt
I should elaborate. I spent time trying to figure out hot to stop AXIS from adding XsiTypes to request elements that was causing the 3rd party web service to fail. I didn’t want to try to parse out all of this XML. So, I just formulate the SOAP request manually and use your nifty xmlToStruct function to slam the entire thing into a nice little struct.
Thanks !!!!
Travis
@Travis, thanks for the appreciation. I am glad the function was useful to you. Cheers.