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

Posted December 29, 2008 at 9:13 AM

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:

 Launch code in new window » Download code as text file »

  • <!--- 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:

 Launch code in new window » Download code as text file »

  • <!---
  • 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:

 Launch code in new window » Download code as text file »

  • <!---
  • 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:

 Launch code in new window » Download code as text file »

  • arrPhoneNumberNodes[ 1 ].XmlText

... rather than:

 Launch code in new window » Download code as text file »

  • 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:

 Launch code in new window » Download code as text file »

  • 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Print Page




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 »
7,512 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 »
7,512 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 Comment  |  Ask Ben

Recent Blog Comments
Mar 16, 2010 at 11:49 AM
Ask Ben: Building An AJAX, jQuery, And ColdFusion Powered Application
> I wonder if returnFormat="json" will set the mimetype in the response? Just checked; it does not. I show: Content-Type text/html; charset=UTF-8 I usually use returnFormat="json" with returnt ... read »
Mar 16, 2010 at 11:34 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
"It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;)" Heh true - didn't mean to imply you shouldn't p ... read »
Mar 16, 2010 at 11:31 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;) I tried dumping out the CGI; you act ... read »
Mar 16, 2010 at 11:24 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
FYI, you can also consider using a "one page app", ala Terry's Flex based "Builder Stats" - or just using jQuery for a rich app. ... read »
Mar 16, 2010 at 11:22 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
"Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) where cookies ... read »
Mar 16, 2010 at 11:19 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) whe ... read »
Mar 16, 2010 at 11:16 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
URLSessionFormat does a check to see if cookies are enabled. If not, it auto-adds the url token to the end. This _should_ work all the time, but I've seen it fail. Seriously though - if your exten ... read »
Mar 16, 2010 at 11:15 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, Also, I should probably clarify that this really only works IF you intend to switch from XML to HTML after the first request. I am not sure how cross-XML requests (multi-step XML wizard) ... read »