We are designing a rather complex xslt 2.0 application, dealing with semistructured data. We must tolerate with errors during processing, as there are cases where an input is not perfectly valid (or the program is not designed or ready to get such an input).
The most typical error is unsatisfied expectation of tree structure like: <xsl:variable name="element" as="element()" select="some-element"/>
<xsl:variable name="element" as="element()" select="some-element"/>
Obviously, dynamic error occurs if a specified element is not present. To concentrate on primary logic, and to avoid a burden of illegal (unexpected) case recovery we have created a try/catch API. The goal of such API is:
Alternatives:
Do not think this is our arrogance, which has turned us to create a custom API. No, we were looking for alternatives! Please see [xsl] saxon:try() discussion:
We have no other way except to design this feature by ourselves. In our defence one can say that we are using innovatory approach that encapsulates details of the implementation behind template and calls handlers indirectly.
Use:
Try/catch API is designed as a template <xsl:template name="t:try-block"/> calling a "try" handler, and, if required, a "catch" hanler using <xsl:apply-templates mode="t:call"/> instruction. Caller passes any information to these handlers by the means of tunnel parameters.
<xsl:template name="t:try-block"/>
<xsl:apply-templates mode="t:call"/>
Handlers must be in a "t:call" mode. The "catch" handler may recieve following error info parameters:
t:call
<xsl:param name="error" as="xs:QName"/> <xsl:param name="error-description" as="xs:string"/> <xsl:param name="error-location" as="item()*"/>
where $error-location is a sequence of pairs (location as xs:string, context as item())*.
$error-location
(location as xs:string, context as item())*
A sample:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="http://www.nesterovsky-bros.com/xslt/public/" exclude-result-prefixes="xs t"> <xsl:include href="try-block.xslt"/> <xsl:template match="/"> <result> <xsl:for-each select="1 to 10"> <xsl:call-template name="t:try-block"> <xsl:with-param name="value" tunnel="yes" select=". - 5"/> <xsl:with-param name="try" as="element()"> <try/> </xsl:with-param> <xsl:with-param name="catch" as="element()"> <t:error-handler/> </xsl:with-param> </xsl:call-template> </xsl:for-each> </result> </xsl:template> <xsl:template mode="t:call" match="try"> <xsl:param name="value" tunnel="yes" as="xs:decimal"/> <value> <xsl:sequence select="1 div $value"/> </value> </xsl:template> </xsl:stylesheet>
The sample prints values according to the formula "1/(i - 5)", where "i" is a variable varying from 1 to 10. Clearly, division by zero occurs when "i" is equal to 5.
Please notice how to access try/catch API through <xsl:include href="try-block.xslt"/>. The main logic is executed in <xsl:template mode="t:call" match="try"/>, which recieves parameters using tunneling. A default error handler <t:error-handler/> is used to report errors.
<xsl:include href="try-block.xslt"/>
<xsl:template mode="t:call" match="try"/>
<t:error-handler/>
Error report:
Error: FOAR0001 Description: Decimal divide by zero Location: 1. systemID: "file:///D:/style/try-block-test.xslt", line: 34 2. template mode="t:call" match="element(try, xs:anyType)" systemID: "file:///D:/style/try-block-test.xslt", line: 30 context node: /*[1][local-name() = 'try'] 3. template mode="t:call" match="element({http://www.nesterovsky-bros.com/xslt/private/try-block}try, xs:anyType)" systemID: "file:///D:/style/try-block.xslt", line: 53 context node: /*[1][local-name() = 'try'] 4. systemID: "file:///D:/style/try-block.xslt", line: 40 5. call-template name="t:try-block" systemID: "file:///D:/style/try-block-test.xslt", line: 17 6. for-each systemID: "file:///D:/style/try-block-test.xslt", line: 16 context item: 5 7. template mode="saxon:_defaultMode" match="document-node()" systemID: "file:///D:/style/try-block-test.xslt", line: 14 context node: /
Implementation details:
You were not expecting this API to be pure xslt, weren't you?
Well, you're right, there is an extension function. Its pseudo code is like this:
function tryBlock(tryItems, catchItems) { try { execute xsl:apply-templates for tryItems. } catch { execute xsl:apply-templates for catchItems. } }
The last thing. Please get the implementation saxon.extensions.zip. There you will find sources of the try/catch, and tuples/maps API.