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>
|