Chapter 15. Testing and Debugging

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

Introduction

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:

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.

15.1. Using xsl:message Effectively

Problem

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

Warning

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:saxonsaxon8.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>

15.2. Tracing the Flow of Your Stylesheet Through Its Input Document

Problem

You want to trace your stylesheet’s navigation through the XML document.

Solution

XSLT 1.0

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

XSLT 2.0

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:

-TJ

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.

-TL classname

Run the stylesheet using the specified TraceListener. The classname names a user-defined class, which must implement net.sf.saxon.trace.TraceListener

-TP

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.

Discussion

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>&#xa;&#xa;</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>&#xa;&#xa;</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>. &#xa;</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]

See Also

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.

15.3. Automating the Insertion of Debug Output

Problem

You want to transform your s tylesheet into another stylesheet that is instrumented with debug traces.

Solution

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(. &gt; current()/xsl:template/@priority)]" />
      <xsl:variable name="newpri">
        <xsl:choose>
          <xsl:when test="$priority &lt; -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>&#xA;   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>&#xA;   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>&#xA;   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>

Discussion

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.

See Also

The trace.xslt source and further examples can be found at http://www.informatik.hu-berlin.de/~obecker/XSLT/#trace.

15.4. Including Embedded Unit Test Data in Utility Stylesheets

Problem

You want to package tests with your utility stylesheets so they can be verified at any time.

Solution

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(. &lt;= 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>

Discussion

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.

15.5. Structuring Unit Tests

Problem

You want to structure tests to simplify testing.

Solution

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.

Discussion

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.

See Also

Brian W. Kernighan’s and Rob Pike’s The Practice of Programming (Addison Wesley, 1999), although not specifically written for XSLT, contains relevant advice for testing and debugging all kinds of programs.

15.6. Testing Boundary and Error Conditions

Problem

You are writing utility templates to be used by others, and you want them to be robust.

Solution

Boundary-condition testing

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

Error-condition testing

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

Discussion

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.22.107