Accessing XML Nodes Having Names That Contain Dashes In ColdFusion
ColdFusion has always made working with XML documents very easy. By implicitly wrapping the underlying XML DOM nodes in pseudo node collections, we've always been able to easily access XML nodes using struct (dot) and array (bracket) notation. For example, accessing the name of the third friend in an XML document could look as straightforward as something like this:
friends.friend[ 3 ].name.xmlText
As you can see, ColdFusion allows us to think about XML node collections as both named and ordered collections of like-nodes.
Most of the time, this is exactly what we need and it works perfectly. But, as Simon Hume brought up on Twitter today, when your XML node names contain a dash, you have to be a bit more careful in the way you define your node paths. Going back to the previous example, if we had the node, "friend-name", rather than, "name," we might be tempted to try following node path:
friends.friend[ 3 ].friend-name.xmlText
When ColdFusion sees this, however, it will throw the following error:
Element FRIEND is undefined in a Java object of type class coldfusion.xml.XmlNodeMap.
This is happening because ColdFusion doesn't see the dash as part of the node name; rather, ColdFusion sees this dash as a mathematical operator in the following equation:
((friends.friend[ 3 ].friend) - (name.xmlText))
To get ColdFusion to see the dash as part of the node name, we have to "escape" it, for lack of a better term. To do so, we either have to use array notation and define the node name as a quoted string; or, we have to use xmlSearch() where we can deal directly with the underlying document object model. The following code demonstrates both of these approaches:
<!---
Create an XML document that has some node with names that
contain dashes.
--->
<cfxml variable="friends">
<friends>
<friend>
<given-name>Tricia</given-name>
<private-name>Pookie-bear</private-name>
</friend>
<friend>
<given-name>Joanna</given-name>
<private-name>Sugar-butt</private-name>
</friend>
</friends>
</cfxml>
<!---
Use ColdFusion's XML node wrappers to access the pseudo-node
collection based on names. Notice here that we are wrapping the
node name in quotes and using array-notation in the node path.
--->
<cfset tricia = friends.friends.friend[ 1 ][ "private-name" ].xmlText />
<!---
In this version, we'll use xmlSearch(). Since xmlSearch() is
using the native XML DOM structure, we don't need to "escape"
the dashed name in any way.
--->
<cfset joanna = xmlSearch(
friends,
"/friends/friend[ 2 ]/private-name/"
) />
<!---
Output the friend's private names as extracted from the XML
document object model.
NOTE: Because xmlSearch() returns an array, "joanna" is the XML
node array at this point, not the actual text value.
--->
<cfoutput>
Tricia: #tricia#
<br />
<br />
Joanna: #joanna[ 1 ].xmlText#
</cfoutput>
As you can see in the above code, our ColdFusion XML document has the dash-containing node, "private-name." In the first approach, we use array notation and quote the node name. In the second approach, we use xmlSearch() in which no "escaping" is necessary. Running the above code, we get the following page output:
Tricia: Pookie-bear
Joanna: Sugar-butt
As you can see, both approaches work fine when dealing with XML nodes that contain dashed-names. The technique you end up using is going to depend on the situation. Using ColdFusion's pseudo node wrappers is typically easier when we know that the structure of the XML document will be fairly static. But, if the XML document can be more dynamic, xmlSearch() gives us a lot of flexibility and power in how we traverse the underlying node tree.
Want to use code from this post? Check out the license.
Reader Comments
You could also use native XML traversal to get around this:
xmlRoot.xmlChildren[ 1 ].xmlChildren[ 2 ].xmlText
... but this is pretty much unreadable. As such, I left it out of the convesation; but, I figured I'd throw it here into the comments as an after-thought.
I ran into this issue with an XML attribute, here is how I worked around it...
<cfset example = XmlRoot.XmlAttributes["page-size"]>
...where "page-size" is an XML attribute of the root node. I figure this may be somewhat easier to follow as there is no extraneous array notation, or XML searching involved.
@Justin,
Ah, great points - the array notation works both for element nodes as well as for attribute nodes. Excellent tip.