Ask Ben: Handling Optional XML Nodes In WhitePages.com API Response

Posted December 29, 2008 at 9:13 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

I'm a CF / Flex developer who has been reading some of your XML entries. Thanks, in advance, for always posting great solutions. I have one for you that is driving me crazy, and it's probably minor. I'm developing an app using the whitepages.com api. If I want to collect specific data in each returned record, it breaks in my loop b/c the XML is missing a node. Example:

If I search a name and it returns 3 records, the first and last have phonenumbers, but the 2nd one doesn't and throws an error. How can I test if the XMLName exists, which is wp:phonenumbers, when looping through results?

<cfset requestURL = XMLparse(URL toget XML)> gets the results in XML format.
<cfset numOfLoops = XmlSearch(requestURL,"//@wp:totalavailable") />
<cfloop from="1" to="#numOfLoops[1].XMLvalue#" index="i">
<cfoutput>
#requestURL.wp.listings.listing[i].phonenumbers#<br /><br />
</cfoutput>
</cfloop>

Right now, you are dealing with the meta data about the results. By that, I mean you are using the "totalavailable" attribute to tell you how to loop over the results. This is a fine method; but, I prefer to let the physical XML document determine how the output is used. I am not saying that this method is any different - I am just saying I am more comfortable trusting the structure rather than the attributes.

First, I am going to build up an fake, abridged fake WhitePages.com API result set based on their documentation:

  • <!--- Build a fake WhitePages.com API response. --->
  • <cfxml variable="xmlWhitePages">
  •  
  • <?xml version="1.0" encoding="UTF-8" ?>
  • <wp:wp xmlns:wp="http://api.whitepages.com/schema/">
  • <wp:result wp:type="success" />
  • <wp:meta />
  • <wp:listings>
  •  
  • <!--- Listing One. --->
  • <wp:listing>
  • <wp:people>
  • <wp:person wp:rank="primary">
  • <wp:firstname>Mike</wp:firstname>
  • <wp:lastname>Smith</wp:lastname>
  • </wp:person>
  • </wp:people>
  • <wp:phonenumbers>
  • <wp:phone wp:type="home" wp:rank="primary">
  • <wp:fullphone>(206) 973-0000</wp:fullphone>
  • <wp:areacode>206</wp:areacode>
  • <wp:exchange>973</wp:exchange>
  • <wp:linenumber>0000</wp:linenumber>
  • </wp:phone>
  • </wp:phonenumbers>
  • <wp:address />
  • <wp:geodata />
  • <wp:listingmeta />
  • </wp:listing>
  •  
  • <!--- Listing Two. --->
  • <wp:listing>
  • <wp:people>
  • <wp:person wp:rank="primary">
  • <wp:firstname>Sarah</wp:firstname>
  • <wp:lastname>Vivenzio</wp:lastname>
  • </wp:person>
  • </wp:people>
  • <wp:address />
  • <wp:geodata />
  • <wp:listingmeta />
  • </wp:listing>
  •  
  • <!--- Listing Three. --->
  • <wp:listing>
  • <wp:people>
  • <wp:person wp:rank="primary">
  • <wp:firstname>Julia</wp:firstname>
  • <wp:lastname>Niles</wp:lastname>
  • </wp:person>
  • </wp:people>
  • <wp:phonenumbers>
  • <wp:phone wp:type="home" wp:rank="primary">
  • <wp:fullphone>(555) 123-0300</wp:fullphone>
  • <wp:areacode>555</wp:areacode>
  • <wp:exchange>123</wp:exchange>
  • <wp:linenumber>0300</wp:linenumber>
  • </wp:phone>
  • </wp:phonenumbers>
  • <wp:address />
  • <wp:geodata />
  • <wp:listingmeta />
  • </wp:listing>
  • </wp:listings>
  • </wp:wp>
  •  
  • </cfxml>

In this XML document, as in yours, the second wp:listing node does not contain the wp:phonenumbers data node.

Now, when it comes to unpredictable XML documents, there are two ways to handle the variation. One way is to treat the ColdFusion XML document as a collection of pseudo, name-based sets. The other is to treat it as a query able document. I prefer the latter, but we will look at the former first.

In order to make XML documents a little bit more accessible, ColdFusion collects like-named XML siblings into pseudo-sets of nodes. This allows us to refer to XML documents as if they were collections of nested Structs. Because of this, we can use the struct method StructKeyExists() to see if a given pseudo-node set exists as the child of a given XML node:

  • <!---
  • Query for all the listing nodes (the people who have been
  • returned in our WhitePages.com API search).
  • --->
  • <cfset arrListingNodes = XmlSearch(
  • xmlWhitePages,
  • "//wp:listing"
  • ) />
  •  
  •  
  • <!---
  • Loop over our listing nodes an output the name and phone
  • numbers for each record.
  • --->
  • <cfloop
  • index="xmlListing"
  • array="#arrListingNodes#">
  •  
  • <!--- Output name (always exists). --->
  • #xmlListing[ "wp:people" ][ "wp:person" ][ "wp:firstname" ].XmlText#
  • #xmlListing[ "wp:people" ][ "wp:person" ][ "wp:lastname" ].XmlText#
  • <br />
  •  
  • <!--- Check to see if there is a phone numbers node. --->
  • <cfif StructKeyExists( xmlListing, "wp:phonenumbers" )>
  •  
  • #xmlListing[ "wp:phonenumbers" ][ "wp:phone" ][ "wp:fullphone" ].XmlText#
  •  
  • <cfelse>
  •  
  • <em>No number available</em>
  •  
  • </cfif>
  •  
  • <br />
  • <br />
  •  
  • </cfloop>

Notice that once we have our collection of listing nodes, we are using struct/array name-based notation to navigate down do the nodes we want to access. Now, just as we would with a standard ColdFusion struct, we are testing for the existence of the wp:phonenumbers node by seeing if the pseudo-node set key exists in the parent (the current listing node). If the node exists, then we are outputing its text value.

When we run the above code, we get the following output:

Mike Smith
(206) 973-0000

Sarah Vivenzio
No number available

Julia Niles
(555) 123-0300

This works just fine. But, for some reason, I feel more comfortable viewing a ColdFusion XML document as a query able document rather than a collection nested structures. That is not to say that I totally disregard the above method; rather, I combine the above method with a more XmlSearch()-based method. I use XmlSearch() to locate optional child nodes. I find the code to be longer, but a bit more manageable:

  • <!---
  • Query for all the listing nodes (the people who have been
  • returned in our WhitePages.com API search).
  • --->
  • <cfset arrListingNodes = XmlSearch(
  • xmlWhitePages,
  • "//wp:listing"
  • ) />
  •  
  •  
  • <!---
  • Loop over our listing nodes an output the name and phone
  • numbers for each record.
  • --->
  • <cfloop
  • index="xmlListing"
  • array="#arrListingNodes#">
  •  
  • <!---
  • Now that we are in the context of each listing node
  • (as defined by the xmlListing reference), we are going
  • to perform node-relative XML searches to find the points
  • of data that we want to output.
  • --->
  •  
  • <!--- Search for the person node. --->
  • <cfset arrPersonNodes = XmlSearch(
  • xmlListing,
  • "./wp:people/wp:person/"
  • ) />
  •  
  • <!--- Search for phone numbers node. --->
  • <cfset arrPhoneNumberNodes = XmlSearch(
  • xmlListing,
  • "./wp:phonenumbers/wp:phone/wp:fullphone/"
  • ) />
  •  
  •  
  • <!--- Check to see if a name was found. --->
  • <cfif ArrayLen( arrPersonNodes )>
  •  
  • <!--- Output first and last names. --->
  • #arrPersonNodes[ 1 ][ "wp:firstname" ].XmlText#
  • #arrPersonNodes[ 1 ][ "wp:lastname" ].XmlText#
  • <br />
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if phone numbers was found. --->
  • <cfif ArrayLen( arrPhoneNumberNodes )>
  •  
  • #arrPhoneNumberNodes[ 1 ].XmlText#
  •  
  • <cfelse>
  •  
  • <em>No number available</em>
  •  
  • </cfif>
  •  
  • <br />
  • <br />
  •  
  • </cfloop>

When we run this code, we get the exact same output. Again, I am not saying that this method is better than the first; it just so happens that I am personally a little more comfortable with it. Something about using XmlSearch() on a ColdFusion XML document makes me a little bit more comfortable than using StructKeyExists().

Now, notice that in all of my XML output, I am explicitly using the XmlText property of the referenced XML nodes:

  • arrPhoneNumberNodes[ 1 ].XmlText

... rather than:

  • arrPhoneNumberNodes[ 1 ]

From the user's point of view, it won't make much of a difference - both output the text of the XML node. However, when you output the XML node without referencing its XmlText property, what you are actually doing is asking ColdFusion to automagically convert the given XML node to a string:

  • ToString( arrPhoneNumberNodes[ 1 ] )

When you do this, ColdFusion actually returns the following:

<?xml version="1.0" encoding="UTF-8"?>
<wp:fullphone xmlns:wp="http://api.whitepages.com/schema/"
>(555) 123-0300</wp:fullphone>

As you can see, it doesn't just output the node value - it takes the node, treats it as a complete XML document, and generates the string-based equivalent. And, since you certainly don't want that hanging out in your source code, make sure to use the .XmlText property whenever outputting data.



Reader Comments

Dec 29, 2008 at 12:15 PM // reply »
7 Comments

While looping, you could also use the xmlChildPos function. i.e.
<cfif xmlChildPos(someArray[i],"someNode", 1) GT 0></cfif>


Dec 29, 2008 at 12:24 PM // reply »
10,640 Comments

@CV,

Good point. I don't believe I have ever used the XmlChildPos() method. I looked at it once when I was trying to get the sibling index based on on a given node reference; but it turns out, that's not what it does at all :)


Dec 29, 2008 at 2:06 PM // reply »
1 Comments

Hey Ben,

Great job and THANKS! I was using XMLSearch, which I prefer, but my problem was not using array option in cfloop; that's what was giving me the error.

I also agree with using the XML structure instead of the meta data, especially since some nodes do not even show up as we saw in this scenario.


Dec 29, 2008 at 2:13 PM // reply »
10,640 Comments

@Mark,

Glad to help my man. And, please don't get me wrong - no one technique is any better than another. My strategy will definitely change depending on source of the XML data. If it is something that I create and use internally and it's small, I will definitely use the named-pseudo-sets.

Just trying to demonstrate different options.


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 »