Debug XSLT on the fly

Debugging tricks that keep it simple

Debuggers are very handy in programming, but they can also be complex pieces of software in their own right -- complex to set up, to learn, and to use. Sometimes you just need a quick print-out of some values that you suspect to be at the heart of a specific problem you're seeing. In this article, Uche Ogbuji shows how to do quick debugging using xsl:message and other built-in facilities of XSLT, as well as common extensions in EXSLT.

Uche Ogbuji, Principal Consultant, Fourthought, Inc.

Photo of Uche OgbujiUche 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 a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.



01 November 2002

Long before they turn to a debugger, most developers start with the equivalent of a print or printf statement in their programming language to try logging values that might shed some light on their code's errant behavior. XSLT is no exception. You can now get XSLT debuggers and even full integrated development environments (IDEs), but you probably won't always want to go through all the motions of firing up a debugger just to check a single value. This is especially true of those, like me, who use plain old XEmacs (with xslide, in my case) for editing XSLT transforms.

xsl:message is usually the best solution for this. You can simply insert a message at the right spot in the right template as a quick debugging tool. The main downside is that there is no standard specification of xsl:message output: It can take the form of a pop-up dialog box, a log file, or -- for command-line XSLT processors -- a marked display to the standard error output. Usually, this is a minor matter; a quick trip to the documentation of any tool will tell you how it renders processor messages. Despite the potential differences in presentation, the nice thing about xsl:message is that it is a standard instruction and thus portable across processors. Here, I shall present several tips to make debugging with xsl:message even more effective.

Simple messages

Listing 1 (labels.xml) is a source document I shall use throughout this discussion. It is a simple XML document that contains data for mailing address labels.

Listing 1. Mailing labels (labels.xml)
  <?xml version="1.0"?>
<labels>
 <label>
  <name>Thomas Eliot</name>
  <address>
   <street>3 Prufrock Lane</street>
   <city>Hartford</city>
   <state>CT</state>
  </address>
 </label>
 <label>
  <name>Ezra Pound</name>
  <address>
   <street>45 Usura Place</street>
   <city>Hailey</city>
   <state>ID</state>
  </address>
 </label>
 <label>
  <name>William Williams</name>
  <address>
   <street>100 Wheelbarrow Blvd</street>
   <city>Patterson</city>
   <state>NJ</state>
  </address>
 </label>
</labels>

Listing 2 (plainmsg.xslt) is a simple style sheet. In it, I use xsl:message to display the node set of addresses I get from a simple XPath expression.

Listing 2. Example that uses plain xsl:message for debugging (plainmsg.xslt)
  <?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

  <xsl:template match="labels">
    <xsl:call-template name="get-addresses"/>
  </xsl:template>

  <xsl:template name="get-addresses">
    <xsl:variable name="addresses" select="label/address"/>
    <xsl:message>
      <xsl:copy-of select="$addresses"/>
    </xsl:message>
    <!-- Actually do something useful here -->
  </xsl:template>

  <!-- Suppress all other element display -->
  <xsl:template match="*"/>

</xsl:transform>

The message displays only the address element for each label, and its descendants. The XPath expression in question, label/address, is accessed through the addresses variable. I might use such a technique to spot the common XPath error where a typo is made in an element name, and thus a returned node set would be unexpectedly empty. This can lead to odd effects further on in the processing. By using such debugging messages, you can spot the problem closer to the source. The most important thing to notice is that I use xsl:copy-of rather than xsl:value-of to put the node set into the message. The output I get using 4XSLT is:

$ 4xslt labels.xml plainmsg.xslt
STYLESHEET MESSAGE:
<address>
   <street>3 Prufrock Lane</street>
   <city>Hartford</city>
   <state>CT</state>
  </address><address>
   <street>45 Usura Place</street>
   <city>Hailey</city>
   <state>ID</state>
  </address><address>
   <street>100 Wheelbarrow Blvd</street>
   <city>Patterson</city>
   <state>NJ</state>
  </address>
END STYLESHEET MESSAGE

If I had instead used xsl:value-of --

    <xsl:message>
      <xsl:value-of select="$addresses"/>
    </xsl:message>

-- then the output would have been the considerably less useful:

$ 4xslt labels.xml plainmsg.xslt
STYLESHEET MESSAGE:
<?xml version='1.0' encoding='UTF-8'?>
   3 Prufrock Lane
   Hartford
   CT

END STYLESHEET MESSAGE

This is because xsl:value-of converts the results of its select expression to a flattened string representation of the first node in the node set. xsl:copy-of actually copies all the nodes to the message body in their entirety as intact markup, which is what you usually want.


Displaying context

In more complex scenarios, where there are many templates and several debug messages sprinkled about, it may be handy to present some of the XPath context in the message, as well. The context node itself is usually the handiest bit of information for debugging the most common errors. Listing 3 is a fragment of XSLT that illustrates this:

Listing 3. An xsl:message instruction formatted to display the serialized context node as well
    <xsl:message>
      <xsl:text>START MESSAGE HEADER
</xsl:text>
      <xsl:text>CONTEXT NODE:
</xsl:text>
      <xsl:copy-of select="."/>
      <xsl:text>
STOP MESSAGE HEADER
</xsl:text>
      <xsl:text>START MESSAGE BODY
</xsl:text>
      <xsl:copy-of select="$addresses"/>
      <xsl:text>
STOP MESSAGE BODY</xsl:text>
    </xsl:message>

This formats the message into a header and body, and displays the context node in the header (<xsl:copy-of select="."/>). Notice the " " entities that I sprinkle about: These are line feed entities, and are a safe way to force carriage returns in XSLT (as long as they are not stripped as white space, which is why I made sure they are in the xsl:text tag).


Introspector gadget

If your XSLT processor supports the dyn module of the EXSLT standard XSLT extensions, there is a lot more you can do while debugging. The dyn module provides tools for dynamically evaluating XPath expressions at run time. You can exploit this facility to add some neat debugging tricks by examining the style sheet itself at run time. This is similar to what is referred to as introspection in some languages. As an example, I shall add a little utility template for displaying the value of all global variables. Listing 4 is a full style sheet (global-vars.xslt) that uses it:

Listing 4. A script that demonstrates the use of dynamic evaluation to display global variables (global-vars.xslt)
  <?xml version="1.0" encoding="utf-8"?>
<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0"
>

  <xsl:variable name="spam" select="'eggs'"/>
  <xsl:variable name="first-label" select="/labels/label[1]"/>
  <xsl:param name="monty" select="'python'"/>

  <xsl:template match="labels">
    <xsl:call-template name="get-addresses"/>
  </xsl:template>

  <xsl:template name="get-addresses">
    <xsl:variable name="addresses" select="label/address"/>
    <xsl:message>
      <xsl:call-template name="dump-globals"/>
    </xsl:message>
  </xsl:template>

  <!-- Suppress all other element display -->
  <xsl:template match="*"/>


  <!-- Cut and paste this template into your own scripts -->
  <xsl:template name="dump-globals"
    xmlns:exsl="http://exslt.org/common"
    xmlns:dyn="http://exslt.org/dynamic"
    >
    <!-- Get the current transform document element -->
    <!-- A -->
    <xsl:variable name="ctde" select="document('')/xsl:*"/>
    <xsl:text>GLOBAL VARIABLES:
</xsl:text>
    <!-- B -->
    <xsl:for-each
      select="$ctde/xsl:variable|$ctde/xsl:param">
      <!-- C -->
      <xsl:variable name="value" select="dyn:evaluate(concat('$', @name))"/>
      <xsl:text>VARIABLE NAME:</xsl:text>
      <xsl:value-of select="@name"/><xsl:text>
</xsl:text>
      <xsl:text>VARIABLE VALUE:</xsl:text>
      <xsl:choose>
        <!-- D -->
        <xsl:when test="exsl:object-type($value) = 'node-set'">
          <xsl:copy-of select="$value"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$value"/>          
        </xsl:otherwise>
      </xsl:choose>
      <xsl:text>
</xsl:text>
    </xsl:for-each>
    <xsl:text>END GLOBAL VARIABLES
</xsl:text>
  </xsl:template>

</xsl:transform>

Here is the output that 4XSLT produces from this transform, applied against the labels.xml document:

$ 4xslt labels.xml global-vars.xslt 
STYLESHEET MESSAGE:
GLOBAL VARIABLES:
VARIABLE NAME:spam
VARIABLE VALUE:eggs
VARIABLE NAME:first-label
VARIABLE VALUE:<label>
  <name>Thomas Eliot</name>
  <address>
   <street>3 Prufrock Lane</street>
   <city>Hartford</city>
   <state>CT</state>
  </address>
 </label>
VARIABLE NAME:monty
VARIABLE VALUE:python
END GLOBAL VARIABLES

END STYLESHEET MESSAGE

As you can see, the result is to display all the global variable names and values. The code that performs this trick is the dump-globals template, which I have designed to be fully encapsulated, including the extension namespaces it needs. You can copy this template directly into your own XSLT code to use it, or you can copy it into a standalone XSLT file for import. Just invoke <xsl:call-template name="dump-globals"/> to use it. It will dump the global variable listing into the output. In Listing 4, I invoke it from the body of an xsl:message, so that it appears as the message content. I inserted comments marked by letters in order to more clearly explain the workings of the dump-globals template. The following paragraphs explain each lettered section.

A: I use the standard document('') form to retrieve the current style sheet as a source document. I assign the document element of this style sheet to the ctde variable, to have ready access to the top-level XSLT elements. Notice that I am careful to use xsl:* as the name test for the document element. This is because both xsl:stylesheet and xsl:transform are legal document element names, and I can't be sure which will be used in any particular script. This template won't work for style sheets that use the literal result element as root form allowed by XSLT, but you can't have truly global variables in such transforms, anyway, so you will not be likely to use the dump-globals template.

B: The xsl:for-each loops over top-level variable and parameter elements.

C: Each variable or parameter element has a name attribute. Since I have the variable name, all I have to do is evaluate a variable reference to get its value. I do this by creating an XPath expression on the fly, taking the current variable name and prepending dollar sign, $. Now I need to evaluate the resulting string as an XPath expression. This is where the dyn:evaluate function comes in. It takes a string and executes it as an XPath expression, returning the result. In this way I get the value from each global variable or parameter, and assign it to $value for later use.

D: After displaying the variable name, this code displays the value. As I mentioned above, it is generally most useful to use xsl:copy-of when displaying node sets for informational purposes. When displaying numbers, strings, and booleans, however, xsl:value-of is what you want. Since I do not know ahead of time whether each variable value will be a node set or other type of object, I need a way to determine this at run time. Luckily, EXSLT provides another useful function, exsl:object-type in the common module. This function returns a string that indicates the type of the XPath object that results from evaluating its argument.


Conclusion

You can take these basic ideas much further. Dynamic XPath expressions and the document('') form open up a lot of possibilities for processing tricks, even beyond debugging. Also, EXSLT has many other useful modules and functions you should experiment with. If your favorite XSLT processor does not support EXSLT, be sure to request such support from your vendor. EXSLT support is growing, and Saxon, Xalan, libxslt, jd.xslt, and 4XSLT (part of 4Suite) all support some or all of the EXSLT modules.

Don't forget that xsl:message supports the terminate attribute, which, if set to yes aborts the style sheet. This provides for a simple way of supporting assertions in XSLT -- that is, you can place checks for unexpected situations into the style sheet and use a terminating message to abort if such situations occur.

In this article I have demonstrated how to use xsl:message for debugging, some general tips and techniques that are handy for debugging tasks, and some of the new possibilities opened up by extending transforms with the EXSLT functions. I imagine all XML developers get to the point at some time or another where they have to haul out an XSLT debugger with all the bells and whistles, but I hope I've helped to reduce the number of times that is necessary.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=12187
ArticleTitle=Debug XSLT on the fly
publish-date=11012002