Ask Ben: Selecting XML Attributes Given Other XML Attributes

Posted September 19, 2007 at 6:39 PM by Ben Nadel

Tags: ColdFusion, Ask Ben

Earlier today, someone asked me about searching XML documents in such a way that he only wanted to select a tag attribute if the parent tag had another attribute set to a given value. This is actually a simple task with the use of a Predicate. As a review from my ColdFusion XPath and XmlSearch() presentation, predicates are XPath constructs contained in square brackets that filter the results of the returned nodes. Predicates need to result in a boolean-true in order for the selected node to be returned in the final node set.

In our case, there are two ways we can look at the problem that have slightly different XPath values. We want to:

  • Select an attribute node that is part of a element node that has an attribute of a given value.
  • Select an attribute node that has a sibling attribute node of a given value.

While these might sound like the same thing, and do, in fact, result in the same node set, they require different XPath values. Both of these situations are demonstrated in this example:

  • <!--- Define our ColdFusion XML document object. --->
  • <cfxml variable="xmlGirls">
  •  
  • <girls>
  • <girl
  • name="Samantha"
  • age="27"
  • hair="Blonde"
  • />
  • <girl
  • name="Kim"
  • age="32"
  • hair="Brunette"
  • />
  • <girl
  • name="Cindi"
  • age="25"
  • hair="Black"
  • />
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!---
  • Get the Name attribute nodes of all the girls
  • who are brunetted. We are going to be doing this
  • by only looking in girl nodes that have a hair
  • attribute that is brunette.
  • --->
  • <cfset arrNodes1 = XmlSearch(
  • xmlGirls,
  • "//girl[ @hair = 'Brunette' ]/@name"
  • ) />
  •  
  • <!---
  • Get the Name attribute nodes of all the girls
  • who are brunetted. We are going to be doing this
  • by getting all name nodes who have a sibling
  • attribute node, hair, that is Brunette.
  • --->
  • <cfset arrNodes2 = XmlSearch(
  • xmlGirls,
  • "//girl/@name[ ../@hair = 'Brunette' ]"
  • ) />
  •  
  •  
  • <!--- Output the matching nodes. --->
  • <cfdump
  • var="#arrNodes1#"
  • label="Names of Burnette Girls - Method ##1"
  • />
  •  
  • <!--- Output the matching nodes. --->
  • <cfdump
  • var="#arrNodes2#"
  • label="Names of Burnette Girls - Method ##2"
  • />

Notice that in our first ColdFusion XmlSearch() call, our XPath value is first limiting on the Girl node, using a predicate that requires the Hair attribute to be Brunette. Then in our second ColdFusion XmlSearch() call, our predicate is requiring a sibling Hair attribute with no explicit mention of the parent tag (other than by relative relationship).

Running the above tag, we get the two CFDump outputs:


 
 
 

 
ColdFusion XmlSearch() That Uses XPath  
 
 
 

 
 
 

 
ColdFusion XmlSearch() That Uses XPath  
 
 
 

As you can see, both return the proper Name attribute, Kim. Now, is there a difference between the two different XPath values used? From a readability standpoint, I think the first one is better. From a performance standpoint, I am going to assume that the first one is also a better choice. Just as with a SQL WHERE clause, I think in an XPath statement, you are gonna get better performance by putting the most limiting statements first; filtering on the Girl node will result in less Name attribute node evaluations and therefore might perform better.

Hope that helps a bit.




Reader Comments

Sep 21, 2007 at 12:17 PM // reply »
16 Comments

Ben,

I was looking at doing this exact same thing. How would it work if I wanted to test/search on two variables say Brunette and age = 32


Sep 21, 2007 at 12:36 PM // reply »
11,241 Comments

@Alan,

XPath supports some AND/OR logic in the predicates:

//girl[ @hair = 'Brunette' and @age = '32' ]/@name

Notice that the "and" is lowercase; this is required. An uppercase AND will not work properly.


Dec 2, 2008 at 9:41 PM // reply »
1 Comments

Thanks for this example. It was exactly what I was looking for. Sometimes it's impossible to use XPath without a resources of many solid examples.


Dec 3, 2008 at 8:01 AM // reply »
11,241 Comments

@John,

Awesome! Glad to help. If you run into any more issues with XPath, please drop me a line and I'd be happy to whip up a good demo or tutorial.


Jan 28, 2009 at 4:19 PM // reply »
14 Comments

Saved me AGAIN! 2 in one day...
it's getting to the point that I go to bennadel.com first, then the cfdocs.
(in most cases google finds Ben's stuff first anyway!)


Jan 28, 2009 at 4:25 PM // reply »
11,241 Comments

@Michael,

Ha ha, that's awesome. Always glad to help!


Jul 21, 2009 at 7:28 AM // reply »
1 Comments

thanks, that was very helpful


Mar 11, 2010 at 5:05 AM // reply »
2 Comments

Seems like I'm missing something. If I take this code and put it in a .cfm file and try to run it I get this error:
Detail Premature end of file.
ErrNumber 0
ExceptionMessage Premature end of file.
Message An error occured while Parsing an XML document.


Mar 19, 2010 at 9:10 AM // reply »
11,241 Comments

@Tom,

That's odd. Looks like a malformed XML problem. Make sure you aren't missing any of the ">" closing brackets or something.


Mar 22, 2010 at 3:08 AM // reply »
2 Comments

Thanks for the response. I finally discovered that I was getting this error because I had cfsetting enablecfoutputonly="yes" in Application.cfc, and was neither setting it to false elsewhere nor bracketing cfxml with cfoutput.


Mar 22, 2010 at 6:46 PM // reply »
11,241 Comments

@Tom,

Ahh, so your XML content was empty. That'll get you every time!


Aug 6, 2010 at 12:31 AM // reply »
2 Comments

This is truly amazing stuff for me Ben. I knew there was a way to build these types of specific-data arrays without having to loop over multiple nodes, etc.
<br /><br />
I am having a bit of difficulty accessing the XmlValue of the returned attributes, however. In other words, if I wanted an array of just the value of the name attribute for each girl, I would guess aGirlNames=XmlSearch(xmlGirls,"/girls/girl/@name");
<br /><br />
My problem is that I end up with an array where each of the three elements is another array. How do I get at each of the names in XmlValue?
<br /><br />
Thanks again for sharing all of the solid development tips over the years!


Aug 6, 2010 at 11:10 AM // reply »
16 Comments

Shawn --- you can get it by doing this after you've gotten the array:

<cfset aGirlName = #aGirlName[1][1]#>

BTW -- I have found that you may also be getting the header of the XML too along with the value (<?xml version=1.0 encoding=UTF-8?). So numbers won't necessrily work properly unless you strip out the extra text.

You can check on that as needed once you've gotten your value out.


Aug 6, 2010 at 12:52 PM // reply »
2 Comments

Hi Alan,
Thanks for replying, that gets me a bit closer to what I am looking to do. After re-reading my question, I realize I probably wasn't real clear on my need.

<cfset aGirlName = #aGirlName[1][1]#> will only give me name/value pairs of XmlName, XmlType, and XmlValue, and for only one girl. Even if I dump out #aGirlName[1][1].XmlValue#, I will still only be dealing with one girl.

I was hoping to use an XPath expression to return an array with just the values of #aGirlName[i][j].XmlValue#.

I am looking to avoid looping over the resulting array, since it effectively cancels out everything I gain by using the XPath expression in the first place. I was using a nested loop to do this before, and was hoping I could replace my original process with an XPath expression.

I hope that makes more sense.

BTW: Sorry for the <br /> tags in my previous post.


Aug 8, 2010 at 6:07 PM // reply »
11,241 Comments

@Shawn,

I know exactly what you are trying to do. The problem is that in XML, "attributes" are really just different types of "nodes" (as opposed to element nodes based on tag names); in other words, they aren't just simple text values - they are node structures in which the property "xmlValue" is the textual attribute value.

If you want to *only* get back the value, you'd have to wrap this access in some sort of UDF (user defined function) that maps the node array to a simple value array.


Feb 17, 2011 at 11:44 PM // reply »
17 Comments

Hey Ben,

Could you give us an example of how to return nodes according to a date range?

I tried posting sample XML within <code></code> tags, but got an error:

Your comment contains restricted HTML elements (A).

For a live-scoring app, I need to get the first node where the MatchDate attribute "startDateLocal" is on or after the current date.

I'm struggling with namespace errors using such code as:

  • <cfset current matchNodes = xmlSearch(scheduleXml,"/SportData/Sport/Schedules/Match/MatchDate[(@StartDateLocal) >= '#now()#']")>

and I've concluded that I simply don't know what I'm doing.

Any help would be hugely appreciated.


Feb 17, 2011 at 11:47 PM // reply »
17 Comments

Correction to that code provided:

  • <cfset matchNodes = xmlSearch(scheduleXml,"/SportData/Sport/Schedules/Match/MatchDate[(@StartDateLocal) >= '#now()#']")>


Feb 18, 2011 at 9:22 AM // reply »
11,241 Comments

@Paul,

I am not sure that the version of XPath in ColdFusion knows how to support date/time stamps. What I might recommend, without having tested it, is to store the dates in numeric format in your XML. To get this, simply multiple the date by 1:

<tag datetime="#dateOfMatch * 1#">

... This will give you something like:

<tag datetime="12342.43232">

... Then, you can probably use XPath to do a numeric comparison of the "Date time" stamp using numbers.

Of course, assuming you're getting your XML from somewhere else, this probably isn't an option (and might not even work anyway).

You might just have to fall back on doing this manually - ie. getting all the nodes and then filtering the node array once you get it back in ColdFusion.


Apr 21, 2011 at 9:16 PM // reply »
1 Comments

Hi Ben,

Do you know if I can use concat function in XmlSearch?
here's sample xml
...
<iattrval attr="5" id="40" value="First" />
<iattrval attr="10" id="110" value="Blue" />
<iattrval attr="11" id="116" value="25" />
...

here's what I am trying
//iattrval[@attr=11]/concat(@id,'_',@value)

to get following result
...
116_25
...

Now, if I execute this in stylus studio xpath query editor I do get the desired result but it fails in ColdFusion XmlSearch function with following error message

An error occured while Searching an XML document.
Unknown nodetype: concat

Any help on this is greatly appreciated as always.


Jun 15, 2011 at 8:23 AM // reply »
1 Comments

The example was awesome. Struggled for almost a day to get the similar stuff done, was about to give up then by luck got a reference to this page.

Thanks for sharing


Feb 16, 2013 at 1:21 PM // reply »
1 Comments

Thanks for clear explanation! This was very useful when I tried to handle XMLs in a Jelly script.



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 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools