I have experimented a little bit with Saxon B 8.8.0.4j to implement a try/catch
instruction in XSLT. The overall idea is simple: an element error-safe (the name is
maybe not very nice, but I didn't find anything else now) contains an element try
then a suite of 1 or more elements catch. try and catch both
contain any sequence constructor. catch has an optional @errors attribute
that is a space-separated list of QNames, representing error names:
<error-safe>
<try>
<xsl:any-sequence-constructor/>
</try>
<catch errors="err:ERRNAME">
<xsl:any-sequence-constructor/>
</catch>
<catch>
<xsl:any-sequence-constructor/>
</catch>
<error-safe>These are just some tests, and there is still a lot of things to specify, but this first implementation is quite usable I think and was incredibly easy to write with Saxon 8.8. Thanks Mike (as well for your help today)! Below are the four Java files: Exslt2InstructionFactory.java, ErrorSafe.java, Try.java and Catch.java.
package org.fgeorges.exslt2.saxon;
import net.sf.saxon.style.ExtensionElementFactory;
public class Exslt2InstructionFactory
implements ExtensionElementFactory
{
public Class getExtensionClass(String localname) {
if ( localname.equals("error-safe") ) {
return ErrorSafe.class;
}
if ( localname.equals("try") ) {
return Try.class;
}
if ( localname.equals("catch") ) {
return Catch.class;
}
return null;
}
}package org.fgeorges.exslt2.saxon;
import java.util.Map;
import java.util.HashMap;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.SimpleExpression;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.instruct.Executable;
import net.sf.saxon.om.Axis;
import net.sf.saxon.om.AxisIterator;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.style.ExtensionInstruction;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.trans.XPathException;
public class ErrorSafe
extends ExtensionInstruction
{
public Expression compile(Executable exec)
throws XPathException
{
Map<String, Catch> handlers = new HashMap<String, Catch>();
AxisIterator kids = iterateAxis(Axis.CHILD);
Expression tryExpr = null;
while ( true ) {
NodeInfo curr = (NodeInfo) kids.next();
if (curr == null) {
break;
}
else if ( curr instanceof Try ) {
tryExpr = ((Try) curr).compile(exec);
}
else if ( curr instanceof Catch ) {
Catch c = (Catch) curr;
String[] errors = c.getErrors();
if ( null == errors ) {
handlers.put(null, c);
}
else {
for ( int i = 0; i < errors.length; ++i ) {
handlers.put(errors[i], c);
}
}
c.compile(exec);
}
else {
// TODO: Report a compilation error!
}
}
return new ErrorSafeExpression(tryExpr, handlers);
}
public void prepareAttributes() {
}
}
class ErrorSafeExpression
extends SimpleExpression
{
public ErrorSafeExpression(Expression expr, Map<String, Catch> handlers) {
myTry = expr;
myHandlers = handlers;
myDefaultHandler = handlers.get(null);
}
public void process(XPathContext ctxt)
throws XPathException
{
try {
myTry.process(ctxt);
}
catch ( DynamicError err ) {
String error = '{' + err.getErrorCodeNamespace() + '}' + err.getErrorCodeLocalPart();
Catch handler = myHandlers.get(error);
if ( handler != null ) {
handler.handle(err, ctxt);
}
else if ( null != myDefaultHandler ) {
myDefaultHandler.handle(err, ctxt);
}
else {
throw err;
}
}
}
private Expression myTry = null;
private Catch myDefaultHandler = null;
private Map<String, Catch> myHandlers = null;
}package org.fgeorges.exslt2.saxon;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.instruct.Executable;
import net.sf.saxon.om.Axis;
import net.sf.saxon.style.ExtensionInstruction;
import net.sf.saxon.trans.XPathException;
public class Try
extends ExtensionInstruction
{
public Expression compile(Executable exec)
throws XPathException
{
return compileSequenceConstructor(exec, iterateAxis(Axis.CHILD), true);
}
public void prepareAttributes() {
}
}package org.fgeorges.exslt2.saxon;
import java.util.ArrayList;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.SimpleExpression;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.instruct.Executable;
import net.sf.saxon.om.Axis;
import net.sf.saxon.om.AttributeCollection;
import net.sf.saxon.style.ExtensionInstruction;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.trans.XPathException;
public class Catch
extends ExtensionInstruction
{
public Expression compile(Executable exec)
throws XPathException
{
myAction = compileSequenceConstructor(exec, iterateAxis(Axis.CHILD), true);
return myAction;
}
public void prepareAttributes()
throws XPathException
{
String errors = null;
AttributeCollection atts = getAttributeList();
for ( int a = 0; a < atts.getLength(); ++a ) {
if ( "".equals(atts.getPrefix(a)) && "errors".equals(atts.getLocalName(a)) ) {
errors = atts.getValue(a);
break;
}
}
if ( errors != null ) {
String[] qnames = errors.split(" +");
myErrors = new String[qnames.length];
for ( int i = qnames.length; i > 0; --i ) {
String qn = qnames[i - 1];
if ( ! "".equals(qn) ) {
int colons = qn.indexOf(':');
String prefix = qn.substring(0, colons);
String local = qn.substring(colons + 1);
myErrors[i - 1] =
'{' + getNamespaceResolver().getURIForPrefix(prefix, false) + '}' + local;
}
}
}
}
public String[] getErrors() {
return myErrors;
}
public void handle(DynamicError err, XPathContext ctxt)
throws XPathException
{
myAction.process(ctxt);
}
private String[] myErrors = null;
private Expression myAction = null;
}
Here is an simple complete example in XSLT using the above classes, error-safe.xsl:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:err="http://www.w3.org/2005/xqt-errors"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
xmlns:my="my:error-safe.xsl"
exclude-result-prefixes="err my xs"
extension-element-prefixes="ex"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template name="main">
<root>
<first>
<xsl:call-template name="first-test"/>
</first>
<second>
<xsl:call-template name="second-test"/>
</second>
</root>
</xsl:template>
<xsl:template name="first-test">
<ex:error-safe>
<ex:try>
<xsl:sequence select="1 div 0"/>
<ok/>
</ex:try>
<ex:catch errors="err:FOAR0001">
<div-by-0/>
</ex:catch>
</ex:error-safe>
</xsl:template>
<xsl:template name="second-test">
<ex:error-safe>
<ex:try>
<xsl:sequence select="error(xs:QName('my:err0001'), 'Error message!')"/>
<ok/>
</ex:try>
<ex:catch errors="my:err0001 my:err0003">
<handle-1/>
</ex:catch>
<ex:catch errors="my:err0002">
<handle-2/>
</ex:catch>
<ex:catch>
<handle-all/>
</ex:catch>
</ex:error-safe>
</xsl:template>
</xsl:transform>
Run with the right incantation, for example the following, with the script described
here: saxon --b --add-cp=fgeorges.jar -it main
error-safe.xsl (if the classes are compiled in fgeorges.jar), this produces:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<first>
<div-by-0/>
</first>
<second>
<handle-1/>
</second>
</root>
Extension functions could now be added to provide the name and message of the currently
trapped error within a catch element.
Posted by Florent Georges, on 2007-01-09T01:36:00, tag: xslt.