Thursday, January 18, 2007

Creating namespace nodes in XSLT 1.0

Introduction and motivating example

This entry discusses the better way to add namespace nodes to an element in XSLT 1.0. The goal is to try to have the right namespace bindings in context for content using them, as attribute values storing XPath expressions. There is no way to guarantee it, but the code developed below is reasonable enough to work in most cases.

In addition to showing how to generate a namespace node in the result tree, this entry discusses specific problems and tricks when employed in a meta-stylesheet, and how to use the XSLT 2.0 instruction xsl:namespace when available.

The motivating case is the base meta-stylesheet (the skeleton) currently developped for ISO Schematron. Schematron Love In, the mailing list for Schematron, was very active these last days, after Rick posted a first draft of the skeleton for ISO Schematron. One of the most discussed points was How to have the correct namespace bindings in the generated stylesheet? But here is an example first, with a little explanation, before going further:

<schema xmlns="http://purl.oclc.org/dsdl/schematron">

  <ns prefix="fg" uri="http://www.fgeorges.org/"/>

  <title>Sample schema</title>

  <pattern name="Test schema">
    <rule context="fg:elem">
      <assert test="@id">'fg:elem' must have an @id.</assert>
    </rule>
  </pattern>

</schema>

Namespaces used in the attribute contents are declared by the element ns. For each node in the tested document matching the rule/@context match pattern, the XPath expression in assert/@test is evaluated and must be true. If not, an error is reported. The more natural way to implement this language is generating an XSLT stylesheet, and the more natural way to generate it is using an XSLT meta-stylesheet.

Generating a namespace binding in XSLT 1.0

There is no way to guarantee, in an XSLT 1.0 stylesheet, that the output tree will contain a namespace binding for a specified prefix. The serializer is permitted to change any prefix in the output tree, providing that the prefix is still bound to the right URI. This is convenient to not worry about to bindings to the same prefix. The serializer change one prefix, and all is fine.

All? Really? All is fine regarding names of elements and attributes in the output tree (this is what nemspaces are about), but not regarding the attribute values using them, for example. If one attribute value is "fg:elem" as above, and the serializer change the prefix to ns1 (changing also all element and attribute names in this namespace), the attribute value cannot be interpreted as an XPath expression anymore. And if a nemspace binding is not used (if no element or attribute is n this namespace), the serializer can drop the namespace binding.

The usual trick to add a namespace node to an element is to use one of the xxx:node-set() functions like the following:

<!-- In the stylesheet. -->
<elem>
  <xsl:variable name="dummy">
    <ns:elem xmlns:ns="http://uri"/>
  </xsl:variable>
  <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/>
</elem>

<!-- In the result tree (in most cases). -->
<elem xmlns:ns="http://uri"/>

If a xxx:node-set() extension function is not available, the only way to add a namespace binding in the result tree is to add an element or an attribute in the namespace to the result tree. In a meta-stylesheet, we can add an attribute to an XSLT instruction, if this attribute is not in the null namespace or in the XSLT namespace (say we don't use extensions in the generated stylesheet):

<!-- In the stylesheet. -->
<elem>
  <xsl:attribute name="ns:dummy-for-xmlns" xmlns:ns="http://uri"/>
</elem>

<!-- In the result tree (in most cases). -->
<elem xmlns:ns="http://uri" ns:dummy-for-xmlns=""/>

So we can write a named template to handle all of this:

<xsl:template name="make-namespace-node">
  <xsl:param name="prefix"/>
  <xsl:param name="uri"/>
  <xsl:choose>
    <!-- All supported node-set() functions. -->
    <xsl:when test="function-available('exsl:node-set')">
      <xsl:variable name="dummy">
        <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
      </xsl:variable>
      <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/>
    </xsl:when>
    <xsl:when test="function-available('msxsl:node-set')">
      <xsl:variable name="dummy">
        <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
      </xsl:variable>
      <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/>
    </xsl:when>
    <!-- If no node-set() available. -->
    <xsl:otherwise>
      <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Back to Schematron

The skeleton for ISO Schematron is a meta-stylesheet. That means the result tree of the transformation is itself an XSLT stylesheet. The ns element in Schematron appear as childs of the root element. The goal is to generate namespace declarations on the generated element xsl:stylesheet. Are there some specificities we have to take care to?

There is one for sure. In the case a xxx:node-set() function is not available, we are creating a dummy attribute on the xsl:stylesheet. But this cannot be an attribute in the XSLT namespace. If it was, the XSLT processor would say it doesn't know this attribute when running the generated stylesheet, and report an error.

An error that can occurs is trying to add a namespace node with the same name of a namespace node already existsing on the element, but bound to another URI. But we cannot know all prefixes used in the generated stylesheet. The only thing we can do, I think, is asking schema authors to not bind usual prefixes to other URIs they are usually bound to. For example, if you really want to use the xsl prefix for the XSLT namespace, no problem. If you want to use it for your own namespace URI, accept all consequences.

But I'm affraid no check can be done formally. So here is what the template now looks for:

<xsl:template name="make-namespace-node">
  <xsl:param name="prefix"/>
  <xsl:param name="uri"/>
  <xsl:choose>
    <!-- EXSLT's node-set(). -->
    <xsl:when test="function-available('exsl:node-set')">
      <xsl:variable name="dummy">
        <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
      </xsl:variable>
      <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/>
    </xsl:when>
    <!-- MSXSL's node-set(). -->
    <xsl:when test="function-available('msxsl:node-set')">
      <xsl:variable name="dummy">
        <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
      </xsl:variable>
      <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/>
    </xsl:when>
    <!-- If no node-set(), cannot handle XSLT namespace. -->
    <xsl:when test="@uri = 'http://www.w3.org/1999/XSL/Transform'">
      <xsl:message terminate="yes">
          <xsl:text>Using the XSLT namespace in Schematron </xsl:text>
          <xsl:text>rules is not supported in this processor: </xsl:text>
        <xsl:value-of select="system-property('xsl:vendor')"/>
      </xsl:message>
    </xsl:when>
    <!-- If no node-set(), dummy attribute trick. -->
    <xsl:otherwise>
      <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Is it possible to use xsl:namespace if available?

This entry discusses only generating namespace nodes in an XSLT 1.0 stylesheet, because in XSLT 2.0 tere is the xsl:namespace that does this job. But an XSLT 1.0 stylesheet can be run by an XSLT 2.0 processor. And in this case it can uses XSLT 2.0 instructions. How to do that in a reliabe way?

If we add xsl:namespace in the XSLT 1.0 stylesheet, XSLT 1.0 processors will generate a compilation error, telling you that you probably made a mistake because you use an unknown XSLT instruction. Don't try to lie to your processor, and tell it the truth: that piese of code is actually XSLT 2.0 code. How? Simply by creating an external stylesheet module with @version="2.0".

Obviously, XSLT 2.0 processors will not have problem here. And XSLT 1.0 processors? They will run (for this module) in forwards-compatible mode. So they will not generate compilation error for XSLT instructions they don't know, but you still have to prevent them to actually instantiate these instructions. It is easy with element-available():

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template name="make-namespace-node">
    <xsl:param name="prefix"/>
    <xsl:param name="uri"/>
    <xsl:choose>
      <!-- In XSLT 2.0. -->
      <xsl:when test="element-available('xsl:namespace')">
        <xsl:namespace name="{ $prefix }" select="$uri"/>
      </xsl:when>
      <!-- EXSLT's node-set(). -->
      <xsl:when test="function-available('exsl:node-set')" use-when="false()">
        <xsl:variable name="dummy">
          <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
        </xsl:variable>
        <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/>
      </xsl:when>
      <!-- MSXSL's node-set(). -->
      <xsl:when test="function-available('msxsl:node-set')" use-when="false()">
        <xsl:variable name="dummy">
          <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/>
        </xsl:variable>
        <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/>
      </xsl:when>
      <!-- If no node-set(), cannot handle XSLT namespace. -->
      <xsl:when test="@uri = 'http://www.w3.org/1999/XSL/Transform'" use-when="false()">
        <xsl:message terminate="yes">
          <xsl:text>Using the XSLT namespace in Schematron </xsl:text>
          <xsl:text>rules is not supported in this processor: </xsl:text>
          <xsl:value-of select="system-property('xsl:vendor')"/>
        </xsl:message>
      </xsl:when>
      <!-- If no node-set(), dummy attribute trick. -->
      <xsl:otherwise use-when="false()">
        <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Note the @version="2.0" and the use of @use-when. With an XSLT 2.0 processor, that will cause the other branches to be even not compiled. With an XSLT 1.0 processor, that will have no effect, because it doesn't know this attribute.

Labels:

Friday, January 12, 2007

Gexslt: using the CVS version

This entry is a short comment describing how to download, set up and compile Gexslt from the CVS repository. Gexslt is an XSLT 2.0 processor written in Eiffel by Colin Adams, and is part of the Gobo project.

This entry is not a comprehensive documentation. On contrary, it is a short explanation for non-Eiffel programmers that want to use the last version of Gexslt. The fewer steps are described here, the better. I also describe my own setup; it is on Windows, with EiffelStudio.

So you first need an Eiffel compiler. Then you need the Gobo source code. A few environment variables have to be setup. Then you can compile Gexslt. I'll describe at the end how to update your copy of Gexslt to include the last fixes.

Initial setup

Gobo comes with its own Eiffel compiler: gec. I don't use it because I had troubles to install it the first time I compiled Gexslt. So I describe the use of EiffelStudio (the compiler I use). Feel free to see the Gobo documentation to use gec instead.

Download EiffelStudio from http://www.eiffel.com/products/studio/. Just follow the instructions there to install the software. Basically, you can download the installer, run it, then push on "Next", "Next"... Say you installed it in C:\Eiffel57. Install the Borland C++ compiler shipped with EiffelStudio: just say "Yes" when the installer ask you for (if you already have MSC compiler, you can avoid installing the Borland one, and try to use MSC instead; see the Gobo documentation).

Warning: It is strongly recommended to install EiffelStudio in a path without any space (for example, try to avoid C:\Program Files), as it may result in an error in the Gobo build process! If you want to save time to yourself, install it for example in C:\Eiffel57.

To get the source code for Gobo (including Gexslt) from its CVS repository, you need to use your favorite CVS client. With the command-line client from http://cvshome.org/, you can use the following to check out the sources (but you can put these information in your graphical client if you prefer, as the one for Eclipse):

cvs -d :pserver:anonymous@gobo-eiffel.cvs.sf.net:/cvsroot/gobo-eiffel     co gobo

That will create a gobo directory in the current directory. Say you are in C:\, so that will create C:\gobo.

Now the environment variables. You need to create a variable to say where is the Gobo directory, as well as updating your %PATH% in order to the build system will find the Eiffel and C compilers. So create the %GOBO% variable with the value C:\gobo (or whatever you actually used), and add both C:\Eiffel57\BCC55\Bin and C:\Eiffel57\studio\spec\windows\bin to your %PATH%.

Now you can compile Gobo. Just go to Gobo directory and run:

.\work\bootstrap\bootstrap.bat bcc ise

Now go to make a coffe, or even go to lunch, that step will take a long time. After the compilation complete, you can use C:\gobo\bin\gexslt.exe from the command line to run XSLT 2.0 transformations.

But that is not the end of the story. If you are ready to do all this work to can use Gexslt from its CVS, you will probably be interested in two additional steps: compiling Gexslt with debug output enabled, and updating your copy with the last CVS state.

To enable the debug output, just go the the C:\gobo\src\gexslt and run geant compile_debug_ise. That will create the executable C:\gobo\src\gexslt\gexslt.exe. My advice is to not move it to C:\gobo\bin, so you keep both versions, with and without debug info. It is usefull, because the debug enabled version takes a lot of time more than the normal version.

So to resume all the steps for the initial setup:

rem Install EiffelStudio

rem Create %GOBO% = C:\gobo

rem Add both C:\Eiffel57\BCC55\Bin and
rem C:\Eiffel57\studio\spec\windows\bin
rem to your %PATH%

rem Go to C:\ in a command prompt
C:

rem Get Gobo sources
cvs -d :pserver:anonymous@gobo-eiffel.cvs.sf.net:/cvsroot/gobo-eiffel co gobo

rem Compile Gobo the first time
work\bootstrap\bootstrap.bat bcc ise

rem Compile Gexslt with debug support
cd src\gexslt
geant compile_debug_ise

rem Now you can use C:\gobo\bin\gexslt.exe and
rem C:\gobo\src\gexslt\gexslt.exe (with debug support)

Updates

To update your Gexslt, just update your local copy of the Gobo repository with your prefered CVs client. For example with the command-line CVS client, just go to C:\gobo, then run cvs update -dP. To recompile, run first geant install within C:\gobo and then both geant compile_ise and geant compile_debug_ise within C:\gobo\src\gexslt, to recompile both the normal and the debug-enabled versions (don't forget to copy gexslt.exe between the two compilation phases!):

cd C:\gobo
cvs update -dp
geant install
cd src\gexslt
geant compile_ise
copy gexslt.exe C:\gobo\bin
geant compile_debug_ise

I think there is all that is needed by an XSLT developer working on Windows to can use Gexslt from the CVS.

If you think you find a bug in Gexslt, you can use the following mailing list to report it.

Thanks Colin for your work!

Labels: ,

Wednesday, January 10, 2007

Try/catch in XSLT 2.0: first test cases, first problems

Introduction

In addition to a comment and some advices on Try/catch in XSLT 2.0, Michael Kay gave me today in a post on the Saxon mailing list four interesting test cases. The comment and advices say:

The design of this from the user perspective looks reasonable: it's better than my own attempt to do it solely using extension functions. I did it that way because I was targetting XQuery, but it's not ideal there either, if only because saxon:try() is not a true function.

The tricky part in doing try/catch properly, however, is the semantics. You need to make sure, as far as possible, that (a) you don't catch errors in expressions that are written outside the try but lazily evaluated within it, and (b) that you do catch errors in expressions that are written inside the try but lazily evaluated outside it. This involves both compile-time work, to suppress rewrites that move expressions into or out of the try block, and run-time work, to suppress lazy evaluation (or to make sure that lazily-evaluated expressions carry their catch block with them)

So I created a real test case for each of his test case advice, and added one more, more severe IMHO. Actually, three of his test cases passed, and one failed. The fifth one cause an illegal state in the serializer.

Thank you again Mike for your input!

Results

Here is first the output of the test cases, then each test case individually with a few words.

(drkm) [168]> saxon --b --add-cp=fgeorges.jar -it main error-safe-01.xsl
<root>
   <div-by-0 i="1"/>
   <div-by-0 i="2"/>
   <div-by-0 i="3"/>
   <div-by-0 i="4"/>
   <div-by-0 i="5"/>
   <div-by-0 i="6"/>
   <div-by-0 i="7"/>
   <div-by-0 i="8"/>
   <div-by-0 i="9"/>
   <div-by-0 i="10"/>
</root>

(drkm) [169]> saxon --b --add-cp=fgeorges.jar -it main error-safe-02.xsl
<root>
   <div-by-0 where="In main."/>
</root>

(drkm) [170]> saxon --b --add-cp=fgeorges.jar -it main error-safe-03.xsl
<root>
   <ERROR what="Div by 0" where="In main!"/>
</root>

(drkm) [171]> saxon --b --add-cp=fgeorges.jar -it main error-safe-04.xsl
<root>
   <div-by-0/>
</root>

(drkm) [172]> saxon --b --add-cp=fgeorges.jar -it main error-safe-05.xsl
java.lang.IllegalStateException: Attempt to end document in serializer when elements are unclosed
        at net.sf.saxon.event.XMLEmitter.endDocument(XMLEmitter.java:110)
        at net.sf.saxon.event.ProxyReceiver.endDocument(ProxyReceiver.java:102)
        at net.sf.saxon.event.ProxyReceiver.endDocument(ProxyReceiver.java:102)
        at net.sf.saxon.event.ProxyReceiver.endDocument(ProxyReceiver.java:102)
        at net.sf.saxon.event.ProxyReceiver.endDocument(ProxyReceiver.java:102)
        at net.sf.saxon.event.ComplexContentOutputter.endDocument(ComplexContentOutputter.java:115)
        at net.sf.saxon.Controller.transformDocument(Controller.java:1654)
        at net.sf.saxon.Controller.transform(Controller.java:1438)
        at net.sf.saxon.Transform.execute(Transform.java:890)
        at net.sf.saxon.Transform.doTransform(Transform.java:491)
        at net.sf.saxon.Transform.main(Transform.java:60)
Fatal error during transformation: Attempt to end document in serializer when elements are unclosed

(drkm) [173]> 

error-safe-01.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
                exclude-result-prefixes="xs"
                extension-element-prefixes="ex"
                version="2.0">

  <xsl:output indent="yes" omit-xml-declaration="yes"/>

  <!--
      Test from Michael Kay, see http://sf.net/mailarchive/message.php?msg_id=37863852

      (the danger here is that the 1 div $zero, being independent of
      the loop context, will be evaluated outside the loop. This might
      be OK: In fact, I think it probably will be OK, because although
      Saxon moves the expression outside the loop, it always evaluates
      it lazily to avoid triggering errors if the loop is executed
      zero times. But it needs testing).
  -->

  <xsl:template name="main">
    <ex:error-safe>
      <ex:try>
        <root>
          <xsl:call-template name="test"/>
        </root>
      </ex:try>
      <ex:catch>
        <ERROR what="Div by 0 error not caught!"/>
      </ex:catch>
    </ex:error-safe>
  </xsl:template>

  <xsl:template name="test">
    <xsl:param name="zero" select="0" as="xs:integer"/>
    <xsl:for-each select="1 to 10">
      <ex:error-safe>
        <ex:try>
          <xsl:value-of select="1 div $zero"/>
        </ex:try>
        <ex:catch>
          <div-by-0 i="{ . }"/>
        </ex:catch>
      </ex:error-safe>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

error-safe-02.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
                exclude-result-prefixes="xs"
                extension-element-prefixes="ex"
                version="2.0">

  <xsl:output indent="yes" omit-xml-declaration="yes"/>

  <!--
      Test from Michael Kay, see http://sf.net/mailarchive/message.php?msg_id=37863852

      Here the error shouldn't be caught, but it might be, because of
      lazy evaluation.
  -->

  <xsl:template name="main">
    <root>
      <ex:error-safe>
        <ex:try>
          <xsl:call-template name="test"/>
        </ex:try>
        <ex:catch>
          <div-by-0 where="In main."/>
        </ex:catch>
      </ex:error-safe>
    </root>
  </xsl:template>

  <xsl:template name="test">
    <xsl:param name="zero" select="1" as="xs:integer"/>
    <xsl:variable name="v" select="for $n in 1 to 10 return $n div $zero"/>
    <xsl:for-each select="1 to 10">
      <ex:error-safe>
        <ex:try>
          <xsl:value-of select="$v[current()]"/>
        </ex:try>
        <ex:catch>
          <ERROR what="Div by 0 error caught!" i="{ . }"/>
        </ex:catch>
      </ex:error-safe>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

error-safe-03.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:my="my:error-safe-03.xsl"
                xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
                exclude-result-prefixes="my xs"
                extension-element-prefixes="ex"
                version="2.0">

  <xsl:output indent="yes"/>

  <!--
      Test from Michael Kay, see http://sf.net/mailarchive/message.php?msg_id=37863852

      Here the risk is that the evaluation is done lazily after exit
      from the function, outside the range of the try/catch. Again, I
      think this might work OK, but it needs careful checking. (If it
      works, it's because the whole xsl:error-safe expression is being
      lazily evaluated).
  -->

  <xsl:template name="main">
    <root>
      <ex:error-safe>
        <ex:try>
          <xsl:sequence select="my:div(1 to 5, 0)"/>
        </ex:try>
        <ex:catch>
          <ERROR what="Div by 0" where="In main!"/>
        </ex:catch>
      </ex:error-safe>
    </root>
  </xsl:template>

  <xsl:function name="my:div">
    <xsl:param name="n" as="xs:integer*"/>
    <xsl:param name="d" as="xs:integer"/>
    <ex:error-safe saxon:explain="yes">
      <ex:try>
        <xsl:value-of select="for $i in $n return $i div $d"/>
      </ex:try>
      <ex:catch>
        <div-by-0/>
      </ex:catch>
    </ex:error-safe>
  </xsl:function>

</xsl:stylesheet>

This test case failed. The error is caught on the main template, instead of within the my:div() function. I looked at the Saxon's internals deeper, but I am still lost with all rewriting, optimizing, simplifying, lazy evaluation, optimizing, etcetera. It is interesting to see that the expression trees, after optimization, are as follow:

Optimized expression tree for template at line 23 in error-safe-03.xsl:
    element
      name root
      content
      error-safe
        try
          call my:div
            operator to
              1
              5
            0
        catch
          element
            name ERROR
            content
                attribute
                  name what
                  "Div by 0"
                attribute
                  name where
                  "In main!"
Optimized expression tree for function my:div at line 38 in error-safe-03.xsl:
    error-safe
      try
        value-of
          construct simple content
            for $i as xs:integer in
              $n
            return
              operator div
                $i
                $d
            " "
      catch
        element
          name div-by-0
          content
          ()

I need more investigation (and I think more help) to see if that can be solved.

error-safe-04.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
                exclude-result-prefixes="xs"
                extension-element-prefixes="ex"
                version="2.0">

  <xsl:output indent="yes" omit-xml-declaration="yes"/>

  <!--
      Test from Michael Kay, see http://sf.net/mailarchive/message.php?msg_id=37863852

      Here the risk is that the error won't be caught because the
      expression is evaluated early, at compile time (the so-called
      "constant folding" process).
  -->

  <xsl:template name="main">
    <root>
      <ex:error-safe>
        <ex:try>
          <xsl:call-template name="test"/>
        </ex:try>
        <ex:catch>
          <ERROR what="Div by 0" where="In main!"/>
        </ex:catch>
      </ex:error-safe>
    </root>
  </xsl:template>

  <xsl:template name="test">
    <xsl:variable name="zero" select="0" as="xs:integer"/>    
      <ex:error-safe>
        <ex:try>
           <xsl:value-of select="1 div $zero"/>
        </ex:try>
        <ex:catch>
          <div-by-0/>
        </ex:catch>
      </ex:error-safe>
  </xsl:template>

</xsl:stylesheet>

error-safe-05.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:my="my:error-safe-05.xsl"
                xmlns:ex="java:/org.fgeorges.exslt2.saxon.Exslt2InstructionFactory"
                exclude-result-prefixes="xs"
                extension-element-prefixes="ex"
                version="2.0">

  <xsl:output indent="yes" omit-xml-declaration="yes"/>

  <!--
      The error occurs in the middle of an element construction.  What
      to do?
  -->

  <xsl:template name="main">
    <ex:error-safe>
      <ex:try>
        <root>
          <xsl:sequence select="error(xs:QName('my:ERROR'), 'Error')"/>
        </root>
      </ex:try>
      <ex:catch>
        <error/>
      </ex:catch>
    </ex:error-safe>
  </xsl:template>

</xsl:stylesheet>

This test case shows in my opinion the worst problem. The problem is both technical and specification-related. The test case seems simple: an ex:try element contains as sequence constructor a unique literal element, that in turn contains a simple xsl:sequence instruction that throw an error. Something (more simple than) usual.

Usually, when an error is thrown, the transformation failed, stop brutaly, an the result is not serialized. But with ex:error-safe, the error is caught and the transformation continues. But the closing tag of the literal element within the error was thrown was never seen. So when the result "tree" is serialized, there is an error, because the sequence of events no longer represents a valid XML fragment.

So I need to define what to do in this case. Discard all the pending result of the sequence constructor already generated? Close every still opened nodes? Keep the items of the sequence but the last one if it is a node not fully build?

In the points of view of both the specification and the implementation, I am not sure what os the right thing to do.

To be continued...

Labels: