Debug XSLT on the fly
Debugging tricks that keep it simple
Long before they turn to a debugger, most developers start with the equivalent of a
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.
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: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.
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
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"/> <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: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
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.
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.
- Look into EXSLT for useful and widely supported extension functions and elements for XSLT. The common module includes the
exsl:object-type()function I use in this article. The dynamic module includes the
- Check out the W3C's XSL page, which includes many useful links to XSLT-related resources, including the specifications themselves, tutorials, articles, and implementations.
- If you are not familiar with the literal result element as stylesheet form for XSLT, see the relevant section in the XSLT spec, which offers a simple example. While you are there, you might want to review xsl:message.
- Be sure to bookmark XSL Frequently Asked Questions, which features many notes that are handy for debugging.
- See my other XSLT-related tips on developerWorks: "Generating internal HTML links with XSLT" (February 2001), and "XSLT lookup tables" (February 2001).
- The style sheet processor I use in examples is 4XSLT, part of 4Suite, which I co-develop.
- Find more XML resources on the developerWorks XML zone.
- Find out how you can become an IBM Certified Developer.