Florent Georges

The ex:error-safe XSLT 2.0 extension

Introduction

One functionality that crually misses in XSLT 2.0 is a correct error handling mechanism. Off course, we have now the XPath 2.0 error() function, and it is a Good Thing. But the developer has no way to get a chance to solve a problem occuring somewhere else, as he can do in other languages with the exceptions for example.

This document tries to define what such a mechanism could look like, and introduces a first draft of an implementation for Saxon 8.

The idea is simple. Errors are thrown by the standard error() function (or by the implementation in case of some standard errors). Within an ex:error-safe instruction, a sequence constructor is enclosed in an ex:try instruction to catch errors thrown while evaluating them, and one or more ex:catch instruction tell what to do in case an error is thrown:

<ex:error-safe>
  <ex:try>
    <xsl:apply-templates/>
  </ex:try>
  <ex:catch>
    <substitute in-case-of="error"/>
  </ex:catch>
</ex:error-safe>

If any error is encountered while evaluating the xsl:apply-templates instruction, the result of the ex:error-safe will be the element substitute as it appears in the ex:catch.

Download & setup

You can either download a compiled JAR file or compile it yourself. Once you have the JAR file (either downloaded or compiled), you juste have to add it to your classpath. And voila!

As it is a user extension instruction (as opposed to modifying Saxon internals directly), it is not required to compile Saxon itself, only the extension, if you want to compile it yourself.

$ javac -cp "path/to/saxon8.jar:." org/fgeorges/exslt2/saxon/*.java
$ jar cf error-safe.jar org/fgeorges/exslt2/saxon/*.class

These are the available files to download. To be able to use the ex:error-safe extension instruction in your stylesheets, you just need to download the JAR corresponding to your Saxon's version (you should be able to use the same one for all Saxon B 8.8j and 8.9j, but I recommend to use the JAR compiled with the same version of Saxon that you use):

Other files include the stylehseet to import to get the definition of the extension functions (as ex:current-error() for example), and a ZIP or TGZ archive with all the sources, test files, and other:

Definitions

ex:error-safe: This is just a container for ex:try and ex:catch instructions, in the same way xsl:choose is a container for xsl:when and xsl:otherwise instructions. It must have one ex:try child followed by one or more ex:catch.

ex:try: This is a wrapper around the sequence constructor for which errors have to be catched if thrown.

ex:catch: Describe the error handlers. In case of an error in the associated ex:try, one (and one at most) error handler can be selected to fallback the error and provide an alternative sequence as result of the overall ex:error-safe.

Error matching: An error handler can declare the errors it can handle, with the attribute @errors. If this attribtue is not present, the handler is the catch-all handler, and matches any error. If this attribute is present, it is a list of one or more QNames. It matches an error if one of its QName matches the QName of the error (both the URI and the local name parts must match). In the QName list, there can be also pseudo-QNames. A pseudo-QName is of the form prefix:*. In this case, only the URI part is used for the match (not the local name part). Prefixes of QNames (and pseudo-QNames) are resolved with the in-bound namespaces.

ef:current-error(): Return the QName of the currently caught error. Particularly useful when an error handler can handle several errors.

ef:current-error-message(): Return the message used when the currently caught error was thrown.

ef:current-error-trace(): Return the stack trace when then currently caught error was thrown, as a string.

ef:current-error-clark(): Return the name of the currently caught error in the Clark notation. That is, a string of the form: "{http://the.uri/}local-name".

Examples

TODO: ... (as for now, you can see the test stylesheets in the archive, under the test/ directory)

Limitations of this implementation

Take care this implementation is very experimental! Following are a few known limitations, but there can be others. Some limitations are rooted in the fact that this implementation is a Saxon user extension, not a change in Saxon's sources themselves, and are related to the expression rewriting performed by Saxon.

This is a design choice. Saxon is a rather complex engine, and all the expression rewritings that can occur are very complex to masterise. I'm definitely not able to write a completely right implementation. Furthemore, my goal was to write a toy implementation and open the discussion about the need of such a feature.

But with the following limitations in mind and by testing well the code that use this extension, it could be possible to use it to provide a feature that would be not available otherwise (even if it is not guaranted it will catch every error that should be caught).

As for now, the extension works only with Saxon B. It works with Saxon SA JARs, but only if Saxon B is used. Actually, the reallity is a little bit more complex. A few test cases work with Saxon SA, but a few fail (and the only one that fails with Saxon B passes with SA...) The tests that fail with SA seem to be caused by the same thing, so I would maybe able to support Saxon SA if I find time to work on this issue.

Michael provided me some test cases, on the Saxon's ML (see his email). From those (and a few others), only the second one fails with Saxon B. Actually, the error shouldn't be caught, but because of lazy evaluation, the expression causing the error is not actually evaluated outside the ex:error-safe, but inside (where the result of it evaluation is used for the first time), and then it is finally caught:

<xsl:template name="test">
  <xsl:param name="zero" select="0" as="xs:integer"/>
  <xsl:variable name="v" select="for $n in 1 to 10 return $n div $zero"/>
  <ex:error-safe>
    <ex:try>
      <xsl:value-of select="$v[1]"/>
    </ex:try>
    <ex:catch errors="err:*">
      <xsl:element name="{ ef:current-error() }">
        <xsl:text>Error: shouldn't be caught!</xsl:text>
      </xsl:element>
    </ex:catch>
  </ex:error-safe>
</xsl:template>

results in the following, though it shouldn't catch the error (err:FOAR00001 is the XPath error code for Integer division by zero):

<err:FOAR0001>Error: shouldn't be caught!</err:FOAR0001>

TODO: present also the other test cases

TODO: think about the way to use this instruction in a portable way (how to tell other processors to evaluate only the content of the ex:try?)

Implementation

(draft...)

ErrorSafe class diagram

ErrorSafe is a StyleElement, standing for the ex:error-safe extension instruction. Try is for ex:try, and Catch is for ex:catch.

An ErrorSafe object compiles to an ErrorSafeExpression, constructed from the expression compiled by Try and a vector of Catch.

An ErrorSafeExpression use ProxyErrorSafeReceiver to wrap the Receiver used while processing itslef, and SubExpressionIterator as the result of iterateSubExpressions().

When processed, an ErrorSafeExpression processes the Expression standing for the ex:try, wrapped in a Java try bloc. If a Java exception is catched, it iterates over each Catch, to see if one matches this exception. The first that matches handle the exception (process its own expression). If no one matches, the exception is rethrown.

A Catch object contains one or more CatchError. Each stands for an error found in ex:catch/@errors (a list of error QNames). In addition to XPath errors, Java exceptions can be caught, by using a QName with the URI http://saxon.sf.net/java-type and the Java exception class name as local part. A Catch matches an exception if one of its CatchError matches.