Try/catch in XSLT 2.0
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.
Labels: xslt
2 Comments:
I don't think much of the over-heavy syntax - drop the try - it's not needed.
Colin Adams
Hi Colin
Glad to see you here :-)
You are right, the ex:try wrapper element is not needed. Actually, in my verry first try, I had no ex:error-safe element, but an ex:try element with a sequence constructor as content, and ex:catch elements at the end of the sequence constructor:
<ex:try>
< ... >
<ex:catch/>
</ex:try>
But I deliberately changed to:
<ex:error-safe>
<ex:try/>
<ex:catch/>
</ex:error-safe>
because I think it is more in the spirit of the XSLT 2.0 PR (think for example about xsl:choose). I don't say it is better, I'm just saying it is more the way XSLT itself is defined.
Thanks for your comments. Regards,
--drkm
Post a Comment
<< Home