Previous Page Next Page

Recipe 15.1. Using xsl:message Effectively

Problem

You want to inspect your stylesheet to determine why it does not do what you expect.

A common bug is to run a stylesheet on a document and see no output at all or only text output with no elements. In my experience, this almost always indicates a namespace issue. It is very easy to forget to include namespace prefixes in xsl:template match attributes or to have a mismatch in the namespace URI in the document versus the one in the stylesheet. Remember, it is the namespace URI and not the prefix that counts. A deviation by even one letter will cause a problem. On long URIs, I usually copy and paste the namespace declaration from the document to the stylesheet to make sure they match.


Solution

XSLT 1.0

The simplest tool in your debugging arsenal is xsl:message, which is often used to figure out if you are executing a particular template:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<!-- ... -->
   
<xsl:template match="someElement[someChild = 'someValue']">
  <xsl:message>Matched someElement[someChild = 'someValue']</xsl:message>
   
  <!-- ... -->
   
</xsl:template>
   
</xsl:stylesheet>

The technique is even more useful when you display relevant data. Be sure to surround the output with known, descriptive text so you can disambiguate the output from other occurrences of xsl:message and detect when a message was executed (but the results were empty):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<xsl:template match="someElement[someChild = 'someValue']">
  <xsl:param name="myParam"/>
  <!-- This is not an effective debugging technique. If you run a test and see
       nothing, it might be because the template was never matched or it might be
       because it was matched with $myParam empty -->
  <xsl:message><xsl:value-of select="$myParam"/></xsl:message>
</xsl:template>
   
<xsl:template match="someElement[someChild = 'someOtherValue']">
  <xsl:param name="myParam"/>
  <!-- This is better -->
  <xsl:message>Matched someElement[someChild = 'someOtherValue']</xsl:message>
  <xsl:message>$myParam=[<xsl:value-of select="$myParam"/>]</xsl:message>
</xsl:template>
   
</xsl:stylesheet>

Use a debugging parameter to preserve xsl:message-based instrumentation in your stylesheet. Place the parameter in your own namespace if you want to distribute the code to others without interfering with their own debug instrumentation:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                         xmlns:dbg="http:www.ora.com/
XSLTCookbook/ns/debug">
   
<xsl:param name="dbg:debugOn" select="false( )"/>
   
<xsl:template match="someElement[someChild = 'someValue']">
  <xsl:param name="myParam"/>
  <xsl:if test="$dbg:debugOn">
    <xsl:message>Matched someElement[someChild = 'someValue']</xsl:message>
    <xsl:message>$myParam=[<xsl:value-of select="$myParam"/>]</xsl:message>
  </xsl:if>
</xsl:template>
   
</xsl:stylesheet>

XSLT 2.0

There are two handy enhancements in 2.0. First, xsl:message now has a select attribute you can use instead of the sequence constructor syntax. This is handy for small messages. Second, the terminate attribute can now be an attribute value template. This greatly simplifies global changes to the termination behavior and the creation of assert style messages.

<!-- Output the value of $foo and terminate if it is negative -->
<xsl:message select=" 'foo=', $foo " terminate="{ if ($foo lt 0) then 'yes' else 'no'}/>

Depending on your XSLT processor, you may be able to take advantage of the use-when attribute that is standard on all XSLT 2.0 instructions. The trick is to test for a custom debug system property. In the Saxon implementation of system-property(), if the argument is a name in no namespace that is, if the name is unprefixed then the name is taken to refer to a Java system property, and the value of that property is returned if it exists. One can then have messages that are conditionally compiled.

<xsl:message use-when="system-property('debug_on') = 'yes' ">
        <xsl:text>Debug mode is on!</xsl:text>
</xsl:message>

You set debug-on when you launch Saxon with java:

java -Ddebug_on=yes -jar c:\saxon\saxon8.jar test.xml test.xslt

Discussion

XSLT 1.0

Before debuggers, there were print statements. Although some interactive XSLT debuggers are now available, none that I know of are free.

In addition to the previous usage of xsl:message, you might consider using assertions to test preconditions, postconditions, or invariants that must be true at some point in the stylesheet:

<xsl:if test="debugOn>
  <xsl:if test="insert some invariant test">
    <xsl:message terminate="yes">
       Message describing the violation or failure.
    </xsl:message>
  </xsl:if>
</xsl:if>

Assertion style tests typically use terminate="yes" because they are fatal errors by definition.

An important consideration when debugging with xsl:message is that the output's destination varies, depending on the environment in which the XSLT script is executed. For example, you may not be able to see the output at all if the transformation runs in a browser client. It is usually advisable to test stylesheets using a command-line processor before moving to the target environment.

When the output is XML or HTML, an alternative to xsl:message is to emit debugging comments into the result document using xsl:comment. In particular, you can begin each template in your stylesheet with an xsl:comment to trace back from the output to the templates that generated it:

<xsl:template match="*">
     <xsl:comment>Generated by the wild card match</xsl:comment> 
 ...
</xsl:template>
   
...
   
<xsl:template match="*" mode="foo">
     <xsl:comment>Generated by the mode=foo wild card match</xsl:comment> 
 ...
</xsl:template>


Previous Page Next Page