Skip to main content

Tip: Default and error handling in XSLT lookup tables

How to deal with cases where a value isn't found in a lookup table

Uche Ogbuji (uche@ogbuji.net), Principal Consultant, Fourthought, Inc.
Photo of Uche Ogbuji
Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought develops 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Mr. Ogbuji is also a lead developer of the Versa RDF query language. He is a computer engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Summary:  In a previous tip Uche Ogbuji demonstrated how to build lookup tables in XSLT. One follow-up question to emerge from that article is how to handle error or default conditions in XSLT lookup tables. This tip illustrates how to do so.

View more content in this series

Date:  22 Dec 2004
Level:  Intermediate
Activity:  704 views
Comments:  

As I wrote in my earlier tip, lookup tables are a common technique for quickly finding results that would be very inefficient or impossible to determine through programming. They consist of a mapping that associates a key with a value. In that article, I demonstrated how to build lookup tables in XSLT, but I did not cover a couple of nuances of the technique: how to deal with keys not found upon lookup, or how to provide default values. You might want to review that tip before continuing with this one.

Lookup errors

In the last article, all the state abbreviations in the source listing I provided happened to be in the lookup table. But what happens if you invoke the transform with a bogus state abbreviation, such as ZZ. The following snippet is the actual lookup code from the prior article:

  <xsl:value-of select="key('state-lookup', $curr-label/address/state)/s:name"/>
  

If $curr-label were ZZ, a value not found in the state-lookup key, the result of the key function call is an empty node set, which the xsl:value-of instruction turns into an empty string. This in itself might be enough error indication for you -- or then again it may not. For example, if one of the values in the lookup table legitimately resulted in an empty string, you might not be able to distinguish the error case. The most straightforward (and unmistakable) way to signal a lookup error is to check for the empty node set result from the key function and generate an xsl:message element with the attribute terminate="yes". Listing 1 is a complete example based on the in-stylesheet lookup technique in the prior tip, updated with this error signaling technique.


Listing 1. Lookup table example that terminates on lookup error (states-lookup-error.xslt)
<?xml version="1.0"?>
<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:s="http://example.com/states.data"
  version="1.0"
>

  <xsl:output method="text"/>

  <xsl:key name="state-lookup" match="s:state" use="s:abbr"/>

  <xsl:variable name="states-top" select="document('')/*/s:states"/>

  <xsl:template match="label">
    <xsl:value-of select="name"/>
    <xsl:text> of </xsl:text>
    <xsl:apply-templates select="$states-top">
      <xsl:with-param name="curr-label" select="."/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="s:states">
    <!-- This template updated to add a lookup error signal -->
    <xsl:param name="curr-label"/>
    <xsl:variable name="look-for" select="$curr-label/address/state"/>
    <xsl:variable name="result"
      select="key('state-lookup', $look-for)/s:name"/>
    <xsl:if test="not($result)">
      <xsl:message terminate="yes">
        Lookup error on key: <xsl:value-of select="$look-for"/>
      </xsl:message>
    </xsl:if>
    <!-- Push the string value of result to the output stream -->
    <xsl:value-of select="$result"/>
  </xsl:template>

  <s:states>
    <s:state><s:abbr>CO</s:abbr><s:name>Colorado</s:name></s:state>
    <s:state><s:abbr>CT</s:abbr><s:name>Connecticut</s:name></s:state>
    <s:state><s:abbr>ID</s:abbr><s:name>Idaho</s:name></s:state>
    <s:state><s:abbr>NJ</s:abbr><s:name>New Jersey</s:name></s:state>
  </s:states>

</xsl:transform>
  

I explained the overall operation of this code in the prior tip. The only changes are to the template with match="s:states". It now uses the error signaling technique outlined above. This technique works because an XSLT key never returns an empty node set unless the lookup value was not found. Listing 2 is a mailing label file with one entry using a state abbreviation that's not in the lookup table.


Listing 2. Source file with mailing labels
<?xml version="1.0"?>
<labels>
  <label>
    <name>Ezra Pound</name>
    <address>
      <street>45 Usura Place</street>
      <city>Hailey</city>
      <state>ZZ</state>
    </address>
  </label>
  <label>
    <name>William Williams</name>
    <address>
      <street>100 Wheelbarrow Blvd</street>
      <city>Patterson</city>
      <state>NJ</state>
    </address>
  </label>
</labels>
  

Different XSLT processors signal messages with <xsl:message terminate="yes"> in different ways, but the following session shows the result in the 4XSLT processor.

$ 4xslt listing2.xml states-lookup-error.xslt

  Ezra Pound of In stylesheet
  file://dW/articles/xslt-lookup-defaults/states-lookup-error.xslt,
  line 28, column 6:
A message instruction in the Stylesheet requested termination of processing:

        Lookup error on key: ZZ
  


Default values

Rather than signal an error in the case of lookup failure, you might wish to settle on a default value. The technique for doing this is similar to signaling an error, but rather than send a terminating message you supply the default value. You might want to encode the terminating message right into the lookup table. Listing 3 is an example that supplies the value [UNKNOWN] for any key not found in the lookup table.


Listing 3. Lookup table example with default value for lookup (states-lookup-default.xslt)
<?xml version="1.0"?>
<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:s="http://example.com/states.data"
  version="1.0"
>

  <xsl:output method="text"/>

  <xsl:key name="state-lookup" match="s:state" use="s:abbr"/>

  <xsl:variable name="states-top" select="document('')/*/s:states"/>

  <xsl:template match="label">
    <xsl:value-of select="name"/>
    <xsl:text> of </xsl:text>
    <xsl:apply-templates select="$states-top">
      <xsl:with-param name="curr-label" select="."/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="s:states">
    <!-- This template updated to add a default value signal -->
    <xsl:param name="curr-label"/>
    <xsl:variable name="look-for" select="$curr-label/address/state"/>
    <xsl:variable name="default" select="s:default"/>
    <xsl:variable name="result"
      select="key('state-lookup', $look-for)/s:name"/>
    <xsl:choose>
      <xsl:when test="$result">
        <xsl:value-of select="$result"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$default"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <s:states>
    <s:state><s:abbr>CO</s:abbr><s:name>Colorado</s:name></s:state>
    <s:state><s:abbr>CT</s:abbr><s:name>Connecticut</s:name></s:state>
    <s:state><s:abbr>ID</s:abbr><s:name>Idaho</s:name></s:state>
    <s:state><s:abbr>NJ</s:abbr><s:name>New Jersey</s:name></s:state>
    <!-- Added default value -->
    <s:default><s:name>[UNKNOWN]</s:name></s:default>
  </s:states>

</xsl:transform>
  

Here you still check for the empty result from the key function, but in this case you supply a default value. The value is provided in a special element right in the lookup table (s:states/s:default).

The following session shows the result in the 4XSLT processor:

$ 4xslt listing2.xml states-lookup-default.xslt

  Ezra Pound of [UNKNOWN]
  William Williams of New Jersey
  


Wrap-up

These additional refinements will help make XSLT lookup tables even more useful for you. You now have some idea of how much flexibility you will have when determining the behavior of such code, but you might also wonder about a better way to package lookup tables for reuse. This is not at all easy to do in basic XSLT 1.0, but in the future I will come back to this topic with a look at some facilities in EXSLT (see Resources) that can help make lookup tables even more handy.


Resources

About the author

Photo of Uche Ogbuji

Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought develops 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Mr. Ogbuji is also a lead developer of the Versa RDF query language. He is a computer engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Comments



Trademarks

static.content.url=/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=32371
ArticleTitle=Tip: Default and error handling in XSLT lookup tables
publish-date=12222004
author1-email=uche@ogbuji.net
author1-email-cc=