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):
- error-safe-8-8-0-2j.jar
- error-safe-8-8-0-3j.jar
- error-safe-8-8-0-4j.jar
- error-safe-8-8-0-6j.jar
- error-safe-8-9j.jar
- error-safe-8-9-0-2j.jar
- error-safe-8-9-0-3j.jar
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
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.