Frequently, crashes are followed with a message like “ID 02.” “ID” is an abbreviation for idiosyncrasy and the number that follows indicates how many more months of testing the product should have had.
Guy Kawasaki
Many XSLT scripts you write will be so-called one-offs that transform well-defined input. Here testing does little more than execute the transformation against the input and inspect the output. However, even in this simple case, how do you best deal with a stylesheet that does not do what you expect? Usually, simple inspection of the code reveals the offending lines. However, debugging by code inspection is often not effective for developers new to XSLT—including those who are seasoned in manipulating XML in more procedural languages. This chapter demonstrates basic debugging techniques that offer quicker solutions to common coding mistakes and enhance your understanding of XSLT.
Many examples in this book emphasize the creation of reusable XSLT. Authors of reusable code must subject that code to more rigorous testing. By definition, reusable code is often deployed in contexts of which the author cannot have full knowledge. You should ensure that the code performs as advertised for typical inputs and boundary conditions. Reusable code should also behave predictably in the face of illegal inputs.
Developers are more likely to test when it is easy. Interpreted languages such as XSLT are typically easier to test because there is no compile and link cycle. XSLT has a further advantage in that it is homoiconic—the syntax of the language and its data are identical. This feature allows easy embedding of test data into the stylesheet, thus creating completely self-contained tests.
All recipes in this chapter are based purely on facilities in XSLT. However, nothing beats a native debugger. The following is a list of commercial products in this space. I have not tried all the products, so do not interpret the list as an endorsement. Many products do much more than debug XSLT:
Active State’s Visual XSLT 2.0 (http://www.activestate.com/Products/Visual_XSLT/)
Altova’s XML Spy Version 2005 (http://www.xmlspy.com/products_ide.html)
Emacs Based XSLT Debugger: XSLT-process (http://xslt-process.sourceforge.net/index.php)
Exchanger (http://www.exchangerxml.com/editor/index.htm)
MarrowSoft’s Xselerator 2.6 (http://www.marrowsoft.com/)
Oxygen (http://www.oxygenxml.com/)
Stylus Studio 6 (http://www.stylusstudio.com/)
WebSphere Studio Application Developer (http://www-306.ibm.com/software/awdtools/studioappdev/)
Treebeard (http://treebeard.sourceforge.net/) is an open source project that is written in Java and works with Xalan, Saxon, jd, and Oracle XSLT processors. It is more of a visual XSLT development environment than a full-fledged debugger, but it can help debug XPath expressions.
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.
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>
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:saxonsaxon8.jar test.xml test.xslt
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>
You should first consider the trace options available in your XSLT
processor. Saxon has a -t
option that displays
timing information about various processing stages and a
-T
option that causes the output of trace
information. Xalan has -TT
, which traces the
templates as they are called; -TG
, which traces
each generation event; -TS
, which traces each
selection event; and -TTC
, which traces template
children as they are called.
If your processor does not support trace output or you need higher
degrees of control over the output, you can consider a solution based
on xsl:message
. With
xsl:message
, it is easy to generate debug output
that lets you trace the flow of control through the stylesheet. It is
also useful to trace the flow of the stylesheet through the document.
Here is a utility you can import into any stylesheet for this
purpose:
<!-- xtrace.xslt --> <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="debugOn" select="false()"/> <xsl:template match="node()" mode="dbg:trace" name="dbg:xtrace"> <xsl:param name="tag" select=" 'xtrace' "/> <xsl:if test="$debugOn"> <xsl:message> <xsl:value-of select="$tag"/>: <xsl:call-template name="dbg:expand-path"/> </xsl:message> </xsl:if> </xsl:template> <!--Expand the xpath to the current node --> <xsl:template name="dbg:expand-path"> <xsl:apply-templates select="." mode="dbg:expand-path"/> </xsl:template> <!-- Root --> <xsl:template match="/" mode="dbg:expand-path"> <xsl:text>/</xsl:text> </xsl:template> <!--Top-level node --> <xsl:template match="/*" mode="dbg:expand-path"> <xsl:text>/</xsl:text><xsl:value-of select="name()"/> </xsl:template> <!--Nodes with node parents --> <xsl:template match="*/*" mode="dbg:expand-path"> <xsl:apply-templates select=".." mode="dbg:expand-path"/>/<xsl:value-of select="name()"/>[<xsl:number/>]<xsl:text/> </xsl:template> <!--Attribute nodes --> <xsl:template match="@*" mode="dbg:expand-path"> <xsl:apply-templates select=".." mode="dbg:expand-path"/>/@<xsl:value-of select="name()"/> </xsl:template> <!-- Text nodes (normalized for clarity) --> <xsl:template match="text()" mode="dbg:expand-path">normalized-text(<xsl:value-of select="normalize-space(.)"/>)</xsl:template> </xsl:stylesheet>
When you place calls to dbg:xtrace
in your
stylesheet, you will generate a message containing the path to the
current node. For example, this code instruments an identity
stylesheet with xtrace
:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dbg="http://www.ora.com/ XSLTCookbook/ns/debug"> <xsl:include href="xtrace.xslt"/> <xsl:template match="/ | node() | @* | comment() | processing-instruction()"> <xsl:call-template name="dbg:trace"/> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Using this test input:
<test foo="1"> <someElement n="1"/> <someElement n="2"> <someChild>someValue</someChild> </someElement> <someElement n="3"> <someChild>someOtherValue</someChild> </someElement> <someElement n="4"> <someChild>someValue</someChild> </someElement> </test>
you produce the following debug output:
xtrace: / xtrace: /test xtrace: /test/@foo xtrace: normalized-text() xtrace: /test/someElement[1] xtrace: /test/someElement[1]/@n xtrace: normalized-text() xtrace: /test/someElement[2] xtrace: /test/someElement[2]/@n xtrace: normalized-text() xtrace: /test/someElement[2]/someChild[1] xtrace: normalized-text(someValue) xtrace: normalized-text() xtrace: normalized-text() xtrace: /test/someElement[3] xtrace: /test/someElement[3]/@n xtrace: normalized-text() xtrace: /test/someElement[3]/someChild[1] xtrace: normalized-text(someOtherValue) xtrace: normalized-text() xtrace: normalized-text() xtrace: /test/someElement[4] xtrace: /test/someElement[4]/@n xtrace: normalized-text() xtrace: /test/someElement[4]/someChild[1] xtrace: normalized-text(someValue) xtrace: normalized-text() xtrace: normalized-text()
You can turn the calls to dbg:trace
off and on
using the use-when
attribute in the same manner as
we did with xsl:message
in Recipe 15.1, provided that your processor supports testing
system properties.
Saxon version 8.4 supports additional tracing options:
Switches on tracing of the binding of calls to external Java methods. This is useful when analyzing why Saxon fails to find a Java method to match an extension function call in the stylesheet, or why it chooses one method over another when several are available.
Run the stylesheet using the specified
TraceListener
. The classname names a user-defined
class, which must implement
net.sf.saxon.trace.TraceListener
Run the stylesheet using the TraceListener
TimedTraceListener
. This creates an output file
giving timings for each instruction executed. This output file can
subsequently be analyzed to give an execution time profile for the
stylesheet.
See the Saxon documentation for more details.
To get the biggest bang for your buck, combine this tracing technique
with debugging output that indicates where you are in the stylesheet.
You can do that with a separate message, but
xtrace
has a parameter named
tag
, which, if set, will be output instead of the
default tag.
This example outputs text nodes as normalized so the trace output does not span multiple lines. You can easily remove this filtering if you prefer to see the actual text.
When you use document tracing, think carefully about where to place the calls to trace. The most useful place is at the point or points where the processing of the current node effectively takes place. Consider the following postorder traversal stylesheet borrowed from Recipe 5.5 and instrumented with trace:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dbg="http://www.ora.com/XSLTCookbook/ns/debug"> <xsl:include href="xtrace.xslt"/> <xsl:output method="text"/> <xsl:strip-space elements="*"/> <xsl:template match="/employee" priority="10"> <xsl:apply-templates/> <xsl:call-template name="dbg:trace"/> <xsl:value-of select="@name"/> <xsl:text> is the head of the company. </xsl:text> <xsl:call-template name="reportsTo"/> <xsl:call-template name="HimHer"/> <xsl:text>. </xsl:text> <xsl:text>

</xsl:text> </xsl:template> <xsl:template match="employee[employee]"> <xsl:apply-templates/> <xsl:call-template name="dbg:trace"/> <xsl:value-of select="@name"/> <xsl:text> is a manager. </xsl:text> <xsl:call-template name="reportsTo"/> <xsl:call-template name="HimHer"/> <xsl:text>. </xsl:text> <xsl:text>

</xsl:text> </xsl:template> <xsl:template match="employee"> <xsl:call-template name="dbg:trace"/> <xsl:text>Nobody reports to </xsl:text> <xsl:value-of select="@name"/> <xsl:text>. 
</xsl:text> </xsl:template> <!-- Remainder elided ... --> </xsl:stylesheet>
Notice how you call trace when you act on the current node, not as the first statement of each template. This placement results in the following trace, which accurately reflects the postorder traversal:
xtrace: /employee/employee[1]/employee[1] xtrace: /employee/employee[1]/employee[2]/employee[1] xtrace: /employee/employee[1]/employee[2] xtrace: /employee/employee[1] xtrace: /employee/employee[2]/employee[1] xtrace: /employee/employee[2]/employee[2]/employee[1] xtrace: /employee/employee[2]/employee[2]/employee[2] xtrace: /employee/employee[2]/employee[2]/employee[3] xtrace: /employee/employee[2]/employee[2] xtrace: /employee/employee[2] xtrace: /employee/employee[3]/employee[1]/employee[1] xtrace: /employee/employee[3]/employee[1]/employee[2] xtrace: /employee/employee[3]/employee[1]/employee[3] xtrace: /employee/employee[3]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[1]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[2]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[2]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[3]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[3] xtrace: /employee/employee[3]/employee[2] xtrace: /employee/employee[3] xtrace: /employee
Had you simply placed the trace at the first line, you would have output the following misleading trace that reflects a preorder traversal:
xtrace: /employee xtrace: /employee/employee[1] xtrace: /employee/employee[1]/employee[1] xtrace: /employee/employee[1]/employee[2] xtrace: /employee/employee[1]/employee[2]/employee[1] xtrace: /employee/employee[1]/employee[2] xtrace: /employee/employee[1] xtrace: /employee/employee[2] xtrace: /employee/employee[2]/employee[1] xtrace: /employee/employee[2]/employee[2] xtrace: /employee/employee[2]/employee[2]/employee[1] xtrace: /employee/employee[2]/employee[2]/employee[2] xtrace: /employee/employee[2]/employee[2]/employee[3] xtrace: /employee/employee[2]/employee[2] xtrace: /employee/employee[2] xtrace: /employee/employee[3] xtrace: /employee/employee[3]/employee[1] xtrace: /employee/employee[3]/employee[1]/employee[1] xtrace: /employee/employee[3]/employee[1]/employee[2] xtrace: /employee/employee[3]/employee[1]/employee[3] xtrace: /employee/employee[3]/employee[1] xtrace: /employee/employee[3]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[1]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[2]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[2]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[2] xtrace: /employee/employee[3]/employee[2]/employee[3] xtrace: /employee/employee[3]/employee[2]/employee[3]/employee[1] xtrace: /employee/employee[3]/employee[2]/employee[3] xtrace: /employee/employee[3]/employee[2] xtrace: /employee/employee[3]
catchXSL! (http://www.xslprofiler.org/overview.html) is a freely downloadable tool that profiles XSL transformations in a processor-dependent manner. In the course of the transformation, every XSLT instruction is recorded and logged as a style event provided with a timestamp. The resulting statistics give information about the transformation proceedings and deliver useful hints for stylesheet improvements. A detailed listing of the style events gives information about each step and its duration. A template-oriented listing shows the time spent in each template and may thus indicate time-consuming “hot spots” in the stylesheet.
You want to transform your s tylesheet into another stylesheet that is instrumented with debug traces.
Oliver Becker developed a handy stylesheet transformation that takes any input stylesheet and produces an output stylesheet with trace instrumentation:
<!-- Trace utility, modifies a stylesheet to produce trace messages Version 0.2 GPL (c) Oliver Becker, 2002-02-13 [email protected] --> <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:trace="http://www.obqo.de/XSL/Trace" xmlns:alias="http://www.w3.org/TransformAlias" exclude-result-prefixes="alias"> <xsl:namespace-alias stylesheet-prefix="alias" result-prefix="xsl" /> <!-- <xsl:output indent="yes" /> --> <!-- XSLT root element --> <xsl:template match="xsl:stylesheet | xsl:transform"> <xsl:copy> <!-- We need the trace namespace for names and modes --> <xsl:copy-of select="document('')/*/namespace::trace" /> <!-- ditto: perhaps a namespace was used only as attribute value --> <xsl:copy-of select="namespace::*|@*" /> <xsl:apply-templates /> <!-- append utility templates --> <xsl:copy-of select="document('')/*/xsl:template [@mode='trace:getCurrent' or @name='trace:getPath']" /> <!-- compute the lowest priority and add a default template with a lower priority for element nodes --> <xsl:variable name="priority" select="xsl:template/@priority [not(. > current()/xsl:template/@priority)]" /> <xsl:variable name="newpri"> <xsl:choose> <xsl:when test="$priority < -1"> <xsl:value-of select="$priority - 1" /> </xsl:when> <!-- in case there's only a greater or no priority at all --> <xsl:otherwise>-2</xsl:otherwise> </xsl:choose> </xsl:variable> <!-- copy the contents only --> <alias:template match="*" priority="{$newpri}"> <xsl:copy-of select="document('')/*/xsl:template [@name='trace:defaultRule']/node()" /> </alias:template> </xsl:copy> </xsl:template> <!-- XSLT templates --> <xsl:template match="xsl:template"> <xsl:copy> <xsl:copy-of select="@*" /> <!-- first: copy parameters --> <xsl:apply-templates select="xsl:param" /> <alias:param name="trace:callstack" /> <xsl:choose> <xsl:when test="@name"> <alias:variable name="trace:current" select="concat($trace:callstack,'/{@name}')" /> </xsl:when> <xsl:otherwise> <alias:variable name="trace:current" select="concat($trace:callstack, '/{count(preceding-sibling::xsl:template)+1}')" /> </xsl:otherwise> </xsl:choose> <!-- emit a message --> <alias:message> <alias:call-template name="trace:getPath" /> <alias:text>
 stack: </alias:text> <alias:value-of select="$trace:current" /> <xsl:if test="@match or @mode"> <alias:text> (</alias:text> <xsl:if test="@match"> <alias:text>match="<xsl:value-of select="@match" />"</alias:text> <xsl:if test="@mode"> <alias:text><xsl:text> </xsl:text></alias:text> </xsl:if> </xsl:if> <xsl:if test="@mode"> <alias:text>mode="<xsl:value-of select="@mode" />"</alias:text> </xsl:if> <alias:text>)</alias:text> </xsl:if> <xsl:apply-templates select="xsl:param" mode="traceParams" /> </alias:message> <!-- process children except parameters --> <xsl:apply-templates select="node()[not(self::xsl:param)]" /> </xsl:copy> </xsl:template> <!-- add the callstack parameter for apply-templates and call-template --> <xsl:template match="xsl:apply-templates | xsl:call-template"> <xsl:copy> <xsl:copy-of select="@*" /> <alias:with-param name="trace:callstack" select="$trace:current" /> <xsl:apply-templates /> </xsl:copy> </xsl:template> <!-- output parameter values --> <xsl:template match="xsl:param" mode="traceParams"> <alias:text>
 param: name="<xsl:value-of select="@name" />" value="</alias:text> <alias:value-of select="${@name}" />" <alias:text /> <!-- <alias:copy-of select="${@name}" />" <alias:text /> --> </xsl:template> <!-- output variable values --> <xsl:template match="xsl:variable"> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates /> </xsl:copy> <xsl:if test="ancestor::xsl:template"> <alias:message> variable: name="<xsl:value-of select="@name" />" value="<alias:text /> <alias:value-of select="${@name}" />" </alias:message> </xsl:if> </xsl:template> <!-- copy every unprocessed node --> <xsl:template match="*|@*"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates /> </xsl:copy> </xsl:template> <!-- *************************************************************** --> <!-- The following templates will be copied into the modified --> <!-- stylesheet --> <!-- *************************************************************** --> <!-- | trace:getPath | compute the absolute path of the context node +--> <xsl:template name="trace:getPath"> <xsl:text>node: </xsl:text> <xsl:for-each select="ancestor::*"> <xsl:value-of select="concat('/', name(), '[', count(preceding-sibling::*[name()=name(current())])+1, ']')" /> </xsl:for-each> <xsl:apply-templates select="." mode="trace:getCurrent" /> </xsl:template> <!-- | trace:getCurrent | compute the last step of the location path, depending on the | node type +--> <xsl:template match="*" mode="trace:getCurrent"> <xsl:value-of select="concat('/', name(), '[', count(preceding-sibling::*[name()=name(current())])+1, ']')" /> </xsl:template> <xsl:template match="@*" mode="trace:getCurrent"> <xsl:value-of select="concat('/@', name())" /> </xsl:template> <xsl:template match="text()" mode="trace:getCurrent"> <xsl:value-of select="concat('/text()[', count(preceding-sibling::text())+1, ']')" /> </xsl:template> <xsl:template match="comment()" mode="trace:getCurrent"> <xsl:value-of select="concat('/comment()[', count(preceding-sibling::comment())+1, ']')" /> </xsl:template> <xsl:template match="processing-instruction()" mode="trace:getCurrent"> <xsl:value-of select="concat('/processing-instruction()[', count(preceding-sibling::processing-instruction())+1, ']')" /> </xsl:template> <!-- | trace:defaultRule | default rule with parameter passing +--> <xsl:template name="trace:defaultRule"> <xsl:param name="trace:callstack" /> <xsl:message> <xsl:call-template name="trace:getPath" /> <xsl:text>
 default rule applied</xsl:text> </xsl:message> <xsl:apply-templates> <xsl:with-param name="trace:callstack" select="$trace:callstack" /> </xsl:apply-templates> </xsl:template> </xsl:transform>
Here is a sample of the debug output produced when this transformation was applied to postorder.orgchart.xslt from Recipe 5.5:
node: /employee[1] stack: /1 (match="/employee") node: /employee[1]/employee[1] stack: /1/2 (match="employee[employee]") node: /employee[1]/employee[1]/employee[1] stack: /1/2/3 (match="employee") node: /employee[1]/employee[1]/employee[2] stack: /1/2/2 (match="employee[employee]") node: /employee[1]/employee[1]/employee[2]/employee[1] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[1]/employee[2] stack: /1/2/2/reportsTo node: /employee[1]/employee[1]/employee[2] stack: /1/2/2/HimHer node: /employee[1]/employee[1] stack: /1/2/reportsTo node: /employee[1]/employee[1] stack: /1/2/HimHer node: /employee[1]/employee[2] stack: /1/2 (match="employee[employee]") node: /employee[1]/employee[2]/employee[1] stack: /1/2/3 (match="employee") node: /employee[1]/employee[2]/employee[2] stack: /1/2/2 (match="employee[employee]") node: /employee[1]/employee[2]/employee[2]/employee[1] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[2]/employee[2]/employee[2] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[2]/employee[2]/employee[3] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[2]/employee[2] stack: /1/2/2/reportsTo node: /employee[1]/employee[2]/employee[2] stack: /1/2/2/HimHer node: /employee[1]/employee[2] stack: /1/2/reportsTo node: /employee[1]/employee[2] stack: /1/2/HimHer node: /employee[1]/employee[3] stack: /1/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[1] stack: /1/2/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[1]/employee[1] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[1]/employee[2] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[1]/employee[3] stack: /1/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[1] stack: /1/2/2/reportsTo node: /employee[1]/employee[3]/employee[1] stack: /1/2/2/HimHer node: /employee[1]/employee[3]/employee[2] stack: /1/2/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[2]/employee[1] stack: /1/2/2/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[2]/employee[1]/employee[1] stack: /1/2/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[2]/employee[1] stack: /1/2/2/2/reportsTo node: /employee[1]/employee[3]/employee[2]/employee[1] stack: /1/2/2/2/HimHer node: /employee[1]/employee[3]/employee[2]/employee[2] stack: /1/2/2/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[2]/employee[2]/employee[1] stack: /1/2/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[2]/employee[2]/employee[2] stack: /1/2/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[2]/employee[2] stack: /1/2/2/2/reportsTo node: /employee[1]/employee[3]/employee[2]/employee[2] stack: /1/2/2/2/HimHer node: /employee[1]/employee[3]/employee[2]/employee[3] stack: /1/2/2/2 (match="employee[employee]") node: /employee[1]/employee[3]/employee[2]/employee[3]/employee[1] stack: /1/2/2/2/3 (match="employee") node: /employee[1]/employee[3]/employee[2]/employee[3] stack: /1/2/2/2/reportsTo node: /employee[1]/employee[3]/employee[2]/employee[3] stack: /1/2/2/2/HimHer node: /employee[1]/employee[3]/employee[2] stack: /1/2/2/reportsTo node: /employee[1]/employee[3]/employee[2] stack: /1/2/2/HimHer node: /employee[1]/employee[3] stack: /1/2/reportsTo node: /employee[1]/employee[3] stack: /1/2/HimHer node: /employee[1] stack: /1/reportsTo node: /employee[1] stack: /1/HimHer
The modified stylesheet outputs trace messages via the
xsl:message
mechanism. The format for every
processed node is as follows:
node: [XPath to this node] stack: [call stack of the templates invoked] param: name="[parameter name]" value="[parameter value]" more parameters ... variable: name="[variable name]" value="[variable value]" more variables ...
The call stack takes the form of a path (with /
as
separator) and includes all passed templates. If a template has a
name attribute, then this name is used. Otherwise, the number
(position) of the template appears within the stack. If the current
template does not have a name, the match attribute is displayed. If a
mode attribute is specified, its value is displayed.
One known problem is that the output for parameters or variables is
their string value (produced with
xsl:value-of
). That’s not
reasonable for node sets and result-tree fragments. However, using
xsl:copy-of
results in an error if the variable
contains attribute or namespace nodes without
parents.
The trace.xslt source and further examples can be found at http://www.informatik.hu-berlin.de/~obecker/XSLT/#trace.
The following stylesheet is meant to be included as a utility. However, this example provides the capability of testing the stylesheet by executing it as its own input document:
<!-- math.max.xslt --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://www.exslt.org/math" exclude-result-prefixes="math" xmlns:test="http://www.ora.com/XSLTCookbook/test" id="math:math.max"> <xsl:template name="math:max"> <xsl:param name="nodes" select="/.."/> <xsl:param name="max"/> <xsl:variable name="count" select="count($nodes)"/> <xsl:variable name="aNode" select="$nodes[ceiling($count div 2)]"/> <xsl:choose> <xsl:when test="not($count)"> <xsl:value-of select="number($max)"/> </xsl:when> <xsl:when test="number($aNode) != number($aNode)"> <xsl:value-of select="number($aNode)"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="math:max"> <xsl:with-param name="nodes" select="$nodes[not(. <= number($aNode))]"/> <xsl:with-param name="max"> <xsl:choose> <xsl:when test="not($max) or $aNode > $max"> <xsl:value-of select="$aNode"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$max"/> </xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- TEST CODE: DO NOT REMOVE! --> <xsl:template match="/xsl:stylesheet[@id='math:math.max'] | xsl:include[@href='math. max.xslt'] " priority="-1000"> <xsl:message> TESTING math.max </xsl:message> <xsl:for-each select="document('')/*/test:test"> <xsl:variable name="ans"> <xsl:call-template name="math:max"> <xsl:with-param name="nodes" select="test:data"/> </xsl:call-template> </xsl:variable> <xsl:if test="$ans != @ans"> <xsl:message> math:max TEST <xsl:value-of select="@num"/> FAILED [<xsl:value-of select="$ans"/>] </xsl:message> </xsl:if> </xsl:for-each> <!-- Test with Infinity --> <xsl:variable name="ans1"> <xsl:call-template name="math:max"> <xsl:with-param name="nodes" select="document('')/*/test:test[@num=1]/test: data"/> <xsl:with-param name="max" select="1 div 0"/> </xsl:call-template> </xsl:variable> <xsl:if test="$ans1 != Infinity"> <xsl:message> math:max Infinity Test FAILED [<xsl:value-of select="$ans1"/>] </xsl:message> </xsl:if> <!-- Test with -Infinity --> <xsl:variable name="ans2"> <xsl:call-template name="math:max"> <xsl:with-param name="nodes" select="document('')/*/test:test[@num=1]/test: data"/> <xsl:with-param name="max" select="-1 div 0"/> </xsl:call-template> </xsl:variable> <xsl:if test="$ans2 != document('')/*/test:test[@num=1]/@ans"> <xsl:message> math:max -Infinity Test FAILED [<xsl:value-of select="$ans2"/>] </xsl:message> </xsl:if> </xsl:template> <test:test num="1" ans="9" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>9</data> <data>8</data> <data>7</data> <data>6</data> <data>5</data> <data>4</data> <data>3</data> <data>2</data> <data>1</data> </test:test> <test:test num="2" ans="1" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>1</data> </test:test> <test:test num="3" ans="1" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>-1</data> <data>1</data> </test:test> <test:test num="4" ans="0" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>0</data> <data>0</data> </test:test> <test:test num="5" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>foo</data> <data>1</data> </test:test> <test:test num="6" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>1</data> <data>foo</data> </test:test> <test:test num="7" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test"> </test:test> </xsl:stylesheet>
The xsl:stylesheet
element has an optional
attribute called id
. This attribute idenfities
stylesheets that are embedded in larger documents. However, here the
ID is used for testing purposes. You want to package test code with
the stylesheet but make reasonably certain that this test code does
not interfere with the normal usage of the stylesheet. Do this by
creating a template that will match only when the stylesheet
processes itself:
<xsl:template match="/xsl:stylesheet[@id='math:math.max'] | xsl:include[@href='math.max.xslt']">
This explains the
/xsl:stylesheet[@id='math:math.max']
, but what
about the
xsl:include[@href='math.max.xslt']
part? To see the value of this, here is a stylesheet that packages
all your math utilities into a single file for easy inclusion. You
would like an easy way to test the entire package too:
<!-- math.xslt --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math" extension-element-prefixes="math" id="math:math"> <xsl:include href="math.abs.xslt"/> <xsl:include href="math.constant.xslt"/> <xsl:include href="math.exp.xslt"/> <xsl:include href="math.highest.xslt"/> <xsl:include href="math.log.xslt"/> <xsl:include href="math.lowest.xslt"/> <xsl:include href="math.max.xslt"/> <xsl:include href="math.min.xslt"/> <xsl:include href="math.power.xslt"/> <xsl:include href="math.sqrt.xslt"/> <!--TEST CODE --> <xsl:template match="xsl:stylesheet[@id='math:math'] | xsl:include[@href='math. xslt']"> <xsl:message> TESTING math </xsl:message> <xsl:for-each select="document('')/*/xsl:include"> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:template> <xsl:template match="xsl:include" priority="-10"> <xsl:message> WARNING: <xsl:value-of select="@href"/> has no test code. </xsl:message> </xsl:template> </xsl:stylesheet>
Here you see that the test code for a package simply loops over all
its xsl:include
elements and applies templates to them. This step causes each
included stylesheet tests to be exercised due to the aforementioned
xsl:include[@href=
'filename
']
part of the match.
Notice the template <xsl:template match="xsl:include"
priority="-10">
. This template causes emission of a
warning if an included file does not contain test code. This concept
is important for quality control, since forgetting to create tests is
easy.
If you object to packaging the tests with
the
actual code, you can achieve the same effect by creating separate
test files for each utility. In this case, there is no need to use
the id
attribute of the stylesheet; simply match
against the root:
<!-- math.max.test.xslt--> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://www.exslt.org/math" exclude-result-prefixes="math" xmlns:test="http://www.ora.com/XSLTCookbook/test"> <xsl:include href="../math/math.max.xslt"/> <!-- TEST CODE: DO NOT REMOVE! --> <xsl:template match="/ | xsl:include[@href='math.max.test.xslt']"> <xsl:message> TESTING math.max </xsl:message> <xsl:for-each select="document('')/*/test:test"> <xsl:variable name="ans"> <xsl:call-template name="math:max"> <xsl:with-param name="nodes" select="test:data"/> </xsl:call-template> </xsl:variable> <xsl:if test="$ans != @ans"> <xsl:message> math:max TEST <xsl:value-of select="@num"/> FAILED [<xsl:value-of select="$ans"/>] </xsl:message> </xsl:if> </xsl:for-each> <!-- ... Same as math.max.xslt above ... --> </xsl:stylesheet>
You would then create separate test packages:
<!-- math.test.xslt --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math" extension-element-prefixes="math"> <xsl:include href="math.max.test.xslt"/> <xsl:include href="math.min.test.xslt"/> <!-- ... Same as math.xslt, above ... --> </xsl:stylesheet>
If you separate your tests in this way, be sure to ship the test code with the actual implementations. Doing so allows your clients to verify tests for themselves. The test code also doubles as an example of how to use the templates.
Notice how Recipe 15.4 embedded test data in
the test stylesheets. Each test element contains a test
num
attribute and the correct result in the form
of an ans
attribute. The test driver then extracts
these test elements from the stylesheet, executes the test, and
compares the expected answer against the actual answer. The most
important aspect of the test driver is that it produces no output
when the test succeeds.
Some of the best advice on automating testing is in Brian W. Kernighan’s and Rob Pike’s The Practice of Programming (Addison Wesley, 1999). The authors state that test programs should produce output only when tests fail. Why? Who wants to wade through pages of test output to look for cases where the tests fail? If you expect test code to produce no output, you will quickly notice failures when there is output. Of course, you should test your test code to make sure it actually executes before relying on this testing technique.
The method that stores the answer as an attribute in the test element
works for simple tests that produce a primitive result. However, some
templates produce node sets. In this case, you might need to store
the correct answer as child elements in the tests. You can then use
the value set operations of Recipe 9.2 to
compare results. However, sometimes you can test node-set producing
templates more simply. Consider the test driver for the
math:lowest
template. Recall that
math:lowest
returns a node set consisting of all
instances of the lowest number in an input node set:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns: math="http://www.exslt.org/math" exclude-result-prefixes="math test" xmlns: test="http://www.ora.com/XSLTCookbook/test" id="math:math.lowest"> <xsl:import href="math.min.xslt"/> <xsl:template name="math:lowest"> <xsl:param name="nodes" select="/.."/> <xsl:variable name="min"> <xsl:call-template name="math:min"> <xsl:with-param name="nodes" select="$nodes"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="number($min) = $min"> <xsl:copy-of select="$nodes[. = $min]"/> </xsl:when> <xsl:otherwise/> </xsl:choose> </xsl:template> <!-- TEST CODE: DO NOT REMOVE! --> <xsl:template match="xsl:stylesheet[@id='math:math.lowest'] | xsl:include[@href='math.lowest.xslt'] " xmlns:exsl="http://exslt.org/common"> <xsl:message> TESTING math.lowest </xsl:message> <xsl:choose> <xsl:when test="function-available('exsl:node-set')"> <xsl:for-each select="document('')/*/test:test"> <xsl:variable name="ans"> <xsl:call-template name="math:lowest"> <xsl:with-param name="nodes" select="test:data"/> </xsl:call-template> </xsl:variable> <xsl:variable name="$ans-ns" select=" exsl:node-set($ans)"/> <xsl:if test="not($ans-ns/* != test:data[. = current()/@ans]) and count($ans-ns/*) != count(test:data[. = current()/@ans])"> <xsl:message> math:lowest TEST <xsl:value-of select="@num"/> FAILED [<xsl:copy-of select="$ans-ns"/>] [<xsl:copy-of select="test:data[. = current()/@ans]"/>] </xsl:message> </xsl:if> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:message> WARNING math.lowest test code requires exsl:node-set THIS VERSION=[<xsl:value-of select="system-property('xsl:version')"/>] VENDOR=[<xsl:value-of select="system-property('xsl:vendor')"/>] </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <test:test num="1" ans="1" xmlns="http://www.ora.com/XSLTCookbook/test"> <data>9</data> <data>8</data> <data>7</data> <data>6</data> <data>5</data> <data>4</data> <data>3</data> <data>2</data> <data>1</data> </test:test> <!-- more tests here ... > </xsl:stylesheet>
The comparison relies on the behavior of !=
when
both sides are node sets: the result is true
if a
pair of nodes, one from each node set, have different string values.
You can make sure that the nodes returned by selecting the answer
nodes from the test set are the same as the nodes returned by
math:lowest
. You can also make sure that the
counts are the same.
Some forms of computation (especially mathematical approximations) produce results that are correct even when the value produced is not exactly equal to the theoretically correct answer. In this case, you can include an error tolerance in the test data and make sure the computed answer is identical to the correct answer within the stated tolerance.
In all programming languages, bugs most often appear at boundary conditions. Thus, you should choose test data in which values lie along data extremes. Boundary values include maximum, minimum, and just inside/outside boundaries. If your templates work correctly for these special values, then they will probably work correctly for all other values. It is impossible to provide an exhaustive list of boundary conditions because they vary from problem to problem. Next is a list of typical cases you should consider.
If a template acts on node sets (sequences in 2.0), then be sure to test the following cases:
An empty node set
A node set with one element
A node set with two elements
A node set with an odd number of elements other than 1
A node set with an even number of elements other than 2
If a template acts on a string, be sure to test the following cases:
The empty string
A string of length 1
Other strings of varying sizes
If your template uses substring-before
or
substring-after
for searches, be sure to test the
following cases:
Strings that do not contain the test string
Strings that start with the search string
Strings that end with the search string
Strings that contain only the search string
Strings that contain multiple occurrences of the search string (back to back and separated by other text)
If a template acts on numbers, be sure to test:
The number 0
The number 1
Negative numbers
Fractional numbers (0 < x < 1)
Numbers with whole and fractional parts
Prime numbers
Other special boundary numbers that are unique to your problem
If a template compares two numbers X and Y, be sure to test cases in which:
X < Y, especially X = Y - 1 and X = Y - d, where d is a small fraction
X = Y
X > Y, especially X = Y + 1 and X = Y + d, where d is a small fraction
When you know or have access to the schema of a document that a stylesheet will process, be sure to test inputs where:
Optional elements are absent
Optional elements are present
Unbounded elements have only one instance
Unbounded elements have several instances
A robust reusable template or stylesheet should fail gracefully in
the face of erroneous input. Here you often use
xsl:message
with terminate=yes
to report illegal parameter values.
If a template acts on numbers, be sure to test error handling for:
NaN (0 div 0)
Infinity ( 1 div 0)
-Infinity (-1 div 0)
Zero when undefined (e.g., logarithms)
Negative numbers when undefined (e.g., for factorial)
Non-numeric input (e.g., “foo”)
When templates or stylesheets use parameters, be sure to test what happens when:
Parameters without default values are not set
Parameters receive out-of-bound values
Parameters receive values of the wrong type
You can check for parameters that aren’t set by using the following trick:
<xsl:param name="param1"> <xsl:message terminate="yes"> $param1 has not been set. </xsl:message> </xsl:param>
However, this trick is not guaranteed to work because nothing in the standard setup states the value of a parameter not evaluated if a value is passed for that parameter. However, most XSLT processors are friendly to this technique. If you wanted to be absolutely safe, you could set the value to an illegal value (such as 1 div 0) and test for it in the body of the template:
<xsl:param name="param1" select="1 div 0" /> <xsl:if test="$param1 = 1 div 0"> <xsl:message terminate="yes"> $param1 has not been set, or has been set to Infinity, which is invalid. </xsl:message> </xsl:if>
When you know or have access to the schema of a document a stylesheet is expected to process, see how the stylesheet responds to documents that:[1]
Completely violate the schema (e.g., an unrelated XML document as input)
Contain some elements that violate the schema
Violate minOccurs
and maxOccurs
constraints
Violate data type constraints
When developing XSLT for your own consumption, you are free to choose just how robust you want the code to be. However, when others use your code, it is a good practice to include reasonable error handling. Your clients will also thank you for delivering code that works for legal but unusual input.
When you create templates that use recursion, dividing the implementation into two templates is a good idea. The main template does the error checking and, if no errors are detected, calls an implementation template that computes the result recursively. Recipe Recipe 3.5 used this tactic for logarithms.
[1] This test assumes that the XSLT processor uses a nonvalidating parser or that you remove the schema reference from the input document.