Tip: Debug stylesheets with xsl:message

Echo printing in XSLT

In this tip, discover several possible ways you can use the xsl:message element to assist with understanding and debugging Extensible Stylesheet Language Transformation (XSLT) stylesheets.

Elliotte Rusty Harold (elharo@metalab.unc.edu), Adjunct Professor, Polytechnic University

Photo of Elliotte Rusty HaroldElliotte Rusty Harold is originally from New Orleans, to which he returns periodically in search of a decent bowl of gumbo. However, he resides in the Prospect Heights neighborhood of Brooklyn with his wife Beth and cats Charm (named after the quark) and Marjorie (named after his mother-in-law). He's an adjunct professor of computer science at Polytechnic University, where he teaches Java and object-oriented programming. His Cafe au Lait Web site has become one of the most popular independent Java sites on the Internet, and his spin-off site, Cafe con Leche, has become one of the most popular XML sites. His most recent book is Java I/O, 2nd edition. He's currently working on the XOM API for processing XML, the Jaxen XPath engine, and the Jester test coverage tool.



18 July 2006

Also available in Chinese

Echo printing is one of the oldest ways to debug a thorny problem. Nonetheless, it's still one of the simplest and quickest ways. When you aren't quite sure why a function doesn't behave as expected, print a few variables to the console with printf() or its equivalent to see just what's going on.

Of course, this assumes you have a console to print debugging output on. XSLT doesn't necessarily have any such thing. However XSLT does have an element corresponding to printf(): xsl:message. The xsl:message element does not change the result tree produced by the XSLT stylesheet at all. It merely outputs a message somewhere the programmer can see it. This is usually the console, but it can be a dialog box or a log file. Wherever the output goes, this is an excellent debugging aid.

The xsl:message element is optional. Processors are not required to support it. However most do, and usually they do so by printing messages on the console.

Is a template being activated?

When output isn't what you expect, the first thing to test is whether the template is really being activated. It's easy to accidentally skip a template for any of number of reasons, such as:

  • A typo in the element name in a match attribute
  • An incorrect namespace, especially trying to match an element in a default namespace with an unprefixed name in the stylesheet
  • Mode mismatch

This is just a sampling. There are many other reasons a template you think is being activated may not be. To verify that the template is reached, place an xsl:message element at the top of the template to signal when the processor does indeed reach it. For example, suppose you're trying to transform an XHTML document and the output keeps coming up as plain text with no markup. You might add messages like these in Listing 1 to your templates:

Listing 1. Buggy template rules for matching HTML
                <xsl:template match="/">
  <xsl:message>Matched root node</xsl:message>
  <xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="html" xmlns:html="http://www.w3.org/1999/xhtml">
  <xsl:message>Matched html element</xsl:message>
  <book><xsl:apply-templates select="html"/></book>
</xsl:template>

When you run the stylesheet, if you see the Matched root node message, you know you got that far. If you don't see the Matched html element message, you know you didn't get that far. This gives you a big clue where to look for the bug. In all likelihood, either the select attribute of the xsl:apply-templates is wrong or the match attribute of the htmltemplate is wrong. (In this example, it's the latter. The second template should try to match html:html.) Either way, this cuts down on where you have to look and lets you focus your attention on the most likely locations of the bug.

A template that contains xsl:if or xsl:choose statements has several branches. You can place xsl:message elements in the individual branches to find out which (if any) are activated. For example, Listing 2 shows some debugging code I inserted into the DocBook XSL stylesheets to figure out why my transforms weren't working:

Listing 2. A DocBook XSL template with additional debugging instructions
  <xsl:choose>
    <xsl:when test="caption">
      <xsl:message>CAPTION!</xsl:message>
      <fo:table-and-caption id="{$id}" 
                            xsl:use-attribute-sets="table.properties">
        <xsl:apply-templates select="caption" mode="htmlTable"/>
        <fo:table xsl:use-attribute-sets="table.table.properties">
          <xsl:choose>
            <xsl:when test="$fop.extensions != 0 or
                            $passivetex.extensions != 0">
              <xsl:message>EXTENSIONS!</xsl:message>
              <xsl:attribute name="table-layout">fixed</xsl:attribute>
            </xsl:when>
          </xsl:choose>
          <xsl:attribute name="width">
            <xsl:choose>
              <xsl:when test="@width">
                <xsl:message>WIDTH ATTRIBUTE!</xsl:message>
                <xsl:value-of select="@width"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:message>NO WIDTH ATTRIBUTE!</xsl:message>100%
              </xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <xsl:call-template name="make-html-table-columns">
            <xsl:with-param name="count" select="$numcols"/>
          </xsl:call-template>
          <xsl:apply-templates select="thead" mode="htmlTable"/>
          <xsl:apply-templates select="tfoot" mode="htmlTable"/>
          <xsl:choose>
            <xsl:when test="tbody">
              <xsl:message>TBODY!</xsl:message>
              <xsl:apply-templates select="tbody" mode="htmlTable"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:message>NO TBODY!</xsl:message>
              <fo:table-body>
                <xsl:apply-templates select="tr" mode="htmlTable"/>
              </fo:table-body>
            </xsl:otherwise>
          </xsl:choose>
        </fo:table>
      </fo:table-and-caption>
      <xsl:copy-of select="$footnotes"/>
    </xsl:when>
    <xsl:otherwise>
       <xsl:message>NO CAPTION!</xsl:message>
      <fo:block id="{$id}"
                xsl:use-attribute-sets="informaltable.properties">
        <fo:table table-layout="auto"
                  xsl:use-attribute-sets="table.table.properties">
          <xsl:attribute name="width">
            <xsl:choose>
              <xsl:when test="@width">
                <xsl:message>WIDTH ATTRIBUTE!</xsl:message>
                <xsl:value-of select="@width"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:message>NO WIDTH ATTRIBUTE!</xsl:message>
                100%
              </xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <xsl:call-template name="make-html-table-columns">
            <xsl:with-param name="count" select="$numcols"/>
          </xsl:call-template>
          <!--<xsl:apply-templates mode="htmlTable"/>-->
          <xsl:choose>
            <xsl:when test="tbody">
              <xsl:message>TBODY!</xsl:message>
              <xsl:apply-templates select="tbody" mode="htmlTable"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:message>NO TBODY!</xsl:message>
              <fo:table-body>
                <xsl:apply-templates select="tr" mode="htmlTable"/>
              </fo:table-body>
            </xsl:otherwise>
          </xsl:choose>
        </fo:table>
      </fo:block>
      <xsl:copy-of select="$footnotes"/>
    </xsl:otherwise>
  </xsl:choose>

XSLT templates can be relatively complex. Be careful with your messages. More than once I've tripped myself up because I put a message claiming something like NO TBODY! in the wrong block. Consequently, the messages I saw were actively misleading. It's worth carefully cleaning up the indentation and white space before trying to debug a thorny problem. Half the time, cleaning up the indentation alone reveals the bug.


Inspecting nodes and node sets

Sometimes it's better to calculate the value to output rather than type it as a literal. This way there's less chance of accidentally misleading yourself. Fortunately, the content of the xsl:message element is really a full-blown XSLT template. This means it can contain quite a bit more than a simple string message. For example in Listing 3, it can output the complete ancestor tree of the context node that caused the template to be activated:

Listing 3. A template that lists all the ancestors of the context node
                <xsl:template match="foo">
  <xsl:message>
    <xsl:for-each select="ancestor-or-self::*">
      <xsl:value-of select="name(.)"/> /
    </xsl:for-each>
  </xsl:message>
  ...
</xsl:template>

It can print all the attributes of the current element as in Listing 4:

Listing 4. A template that lists all the attributes of the context node
                <xsl:template match="foo">
  <xsl:message>
    <xsl:for-each select="attribute::*">
      <xsl:value-of select="name(.)"/>="<xsl:value-of select="."/>"
    </xsl:for-each>
  </xsl:message>
  ...
</xsl:template>

It can generate essentially any information you need to help you debug the stylesheet. However, if the template is not activated for the reason you think it is, then complicated templates may fail. Sometimes it's best to start with a simple expression that merely identifies the context node:

 <xsl:message>
    <xsl:value-of select="name(.)"/>: <xsl:value-of select="."/>
 </xsl:message>

This message seems trivial. After all, you know what node is matched, right? But it's really not quite that obvious. One of the purposes of this sort of debugging is to find out where your mental model of the code diverges from reality. When you have a bug, chances are good that some piece of the code is not doing at all what you think it is. Therefore, it's worth verifying everything you "know" to be true. When you find something you know that turns out to be false, there's the bug. In the words of Artemus Ward, "It ain't so much the things we don't know that get us in trouble. It's the things we know that ain't so."


Testing conditions

Sometimes the problem only occurs under some special conditions. You can apply xsl:if to generate output only when some other condition is true. It is especially important to limit the output you have to consider for large documents. For instance, suppose you're debugging a stylesheet that seems to have trouble when a paragraph contains an inline image. You don't want to print a message for every paragraph in the whole book, just those that contain inline images. For example, this template in Listing 5 prints a message only when a para element contains an image element:

Listing 5. A template that outputs a message when a paragraph contains an image
<xsl:template match="para">
  <xsl:if test="image">
    <xsl:message>
      Para: <xsl:value-of select="."/>"
    </xsl:message>
  </xsl:if>
  ...
</xsl:template>

You can test the value of individual elements to restrict the messages to as little as a single case where a bug appears. For example in Listing 6, this message is output only when the paragraph contains a certain word:

Listing 6. A template that outputs a message when a paragraph contains the string "BCEL"
<xsl:template match="para">
    <xsl:if test="contains(., 'BCEL')">
      <xsl:message>
        Para: <xsl:value-of select="."/>"
      </xsl:message>
    </xsl:if>
  ...
</xsl:template>

Are you using the processor you think you are?

To really override the built-in processor in Java 1.4 and later, you need to put the Xalan JAR files in jre/lib/endorsed. The -classpath and even -Xbootclasspath/p: command line arguments will not work.

Some of the toughest problems to diagnose are those caused by bugs in XSLT processors. This is especially tricky if you aren't using the processor you think you are. This is extremely common when working with the Java™ language because classpath issues mean you often aren't using the version or brand of processor that you think you are. For instance, Sun's JDK 1.4.0 bundles the comparatively buggy Xalan 2.2d10. Even when you add the much more correct and robust Xalan 2.7 to your classpath, you might still use the older bundled version.

The quickest way to find out which processor you're using is to print its name from an xsl:message element. The xsl:vendor function system property contains the name of the processor you're using.

    <xsl:message>
      Processor: <xsl:value-of select="system-property('xsl:vendor')"/>
    </xsl:message>

This usually returns a string such as Apache Software Foundation (Xalan XSLTC) or SAXON from Michael Kay of ICL. If you discover you're using Xalan, and you thought you were using Saxon, that might explain a few things. However, normally what you really care about is the version of the library. In Xalan this is available through a simple extension function that calls the static org.apache.xalan.Version.getVersion() method as demonstrated in Listing 7:

Listing 7. A stylesheet that prints the Xalan version
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:pre="xalan://org.apache.xalan.Version"
>
  
  <xsl:template match="/">
    <xsl:message>
      Processor: <xsl:value-of select="system-property('xsl:vendor')"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="pre:getVersion()"/>
    </xsl:message>
  </xsl:template>

</xsl:stylesheet>

Here's what I get when transforming with the JDK 1.5.0_06:

$ java org.apache.xalan.xslt.Process -IN test.xml -XSL test.xsl 

      Processor: Apache Software Foundation (Xalan XSLTC) Xalan Java 2.4.1

And here's the output after adding the latest version of Xalan to jre/lib/endorsed:

$ java org.apache.xalan.xslt.Process -IN test.xml -XSL test.xsl 

file:///Users/elharo/Documents/articles/tips/test.xsl; Line #8; Column #18; 
      Processor: Apache Software Foundation Xalan Java 2.7.0

XSLT 2

XSLT 2 defines standard xsl:product-name and xsl:product-version system properties that return the name and version number of the product. These aren't recognized by any XSLT 1 processors I know of, but if you're using XSLT 2, they're an even more effective way to check which processor you're using.

Also of note, apparently sometime between version 2.4.1 and 2.7.0, Xalan started outputting the URL, line, and column number for each message.

Of course, this all only works if you're using Xalan. If you think you're using Xalan and you're really using something else, then the likely failure will alert you to that fact. However, most other processors have some similar ability. The latest versions of Saxon actually do include the version number in the xsl:vendor string, which is very helpful.


Stopping the transformation

On rare occasions, you may discover that a transform is so messed up you can't continue. Perhaps the input is just too far apart from what you expected, and you want to give up and stop the processing. In this case, an xsl:message element with a terminate="yes" attribute will stop the transformation. For example in Listing 8, this template abandons processing if the input contains any elements not in the XHTML namespace:

Listing 8. A template that stops processing if the document contains a non-XHTML element
<xsl:template match="/">
  <xsl:for-each select="descendant::*">
    <xsl:if test="namespace-uri() != 'http://www.w3.org/1999/xhtml'">
      <xsl:message terminate="yes">
        Non-XHTML element encountered: <xsl:value-of select="name()"/> 
      </xsl:message>
    </xsl:if>
  </xsl:for-each>
  <xsl:apply-templates select="*"/>
</xsl:template>

Ideally, result tree construction happens before any output is generated, so a processor might output nothing if such a message is encountered. This does vary from one processor to another. Some, including xsltproc, still generate some output before they terminate. If you include this message in your stylesheet, make sure any process that depends on the output recognizes when the result was stopped prematurely.


In conclusion

Functional programming languages, such as XSLT, are somewhat trickier to debug than imperative languages, such as C. Sometimes the simplest approach is to fall back on classic echo printing where you dump anything that might be relevant to the console at the time it's used. The xsl:message element enables stylesheets to log messages to the console or somewhere else that the programmer can see them. Messages can be simple signals, complex combinations of node information, or conditional data. The xsl:message element lets you compare what you think the processor sees in your document to what it really sees. The difference between expectation and reality is where the bug lies.

Resources

Learn

  • Chapter 15 of the XML 1.1 Bible (Elliotte Rusty Harold; Wiley, 2003): Read this complete introduction to XSLT.
  • "Principles of XML design" (developerWorks, March 2004 through April 2005): See Uche Ogbuji's series to make sure your XML design does not get in the way of normal content processing.
  • IBM XML 1.1 certification: Find out how you can become an IBM Certified Developer in XML 1.1 and related technologies.
  • XML: See developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks. For a complete list of XML tips to date, check out the tips summary page.
  • developerWorks technical events and webcasts: Stay current with technology in these sessions.

Get products and technologies

  • Xalan: XSLT processor's current version is superior to the one bundled with the JDK, especially JDK 1.4.
  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.

Discuss

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=146056
ArticleTitle=Tip: Debug stylesheets with xsl:message
publish-date=07182006