views:

46

answers:

2

As of XSLT 2.0, as far as I know (correct me if I’m wrong), there’s no native mechanism in the language for exception handling.

I have some stylesheets that attempt to do some processing on specified chunks of an input document, copying everything else unaltered. There are rare exceptional conditions that I can’t easily detect before I start producing output for a given chunk. These are rare enough that when I encounter them, all I want to do is cancel processing on this chunk and emit it unaltered. Some sort of exception handling is in order, but XSLT doesn’t help very much. I don't want to get Java or another language into the mix here.

I have a workable solution described below, but I'm wondering about other approaches. Do you all have a better way of doing something like this?

Here’s an example of the sort of scenario I’m talking about. Here’s an input document:

<doc>
    <block>some text, just copy.</block>
    <!-- the following table should have B substituted for a -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>c</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
    <block>some more text, just copy.</block>
    <!-- the following table should be copied unaltered because of the presence of an x -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>x</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
</doc>

I want to look through each table and replace all cell values ‘a’ with ‘B’. However, if there’s an ‘x’ somewhere in the table, I want to just copy the table unmodified. I know that in this case, I could just do a tr/td[.='x'] test on the table to discover this condition. In the real case, though, it’s not so easy to test ahead of time for the condition.

Here’s some XSLT that doesn’t account for the exception:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

    <xsl:template match="table">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates mode="inner"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template mode="inner" match="td">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:choose>
                <xsl:when test=". = 'a'">
                    <xsl:value-of select="'B'"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>

    <xsl:template mode="inner" match="@*|node()" priority="-10">
        <xsl:copy>
            <xsl:apply-templates mode="inner" select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*|node()" priority="-10">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

The output of that is:

<doc>
    <block>some text, just copy.</block>
    <!-- the following table should have B substituted for a -->
    <table>
        <tr><td>B</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>B</td><td>c</td></tr>
        <tr><td>b</td><td>c</td><td>B</td></tr>
    </table>
    <block>some more text, just copy.</block>
    <!-- the following table should be copied unaltered because of the presence of an x -->
    <table>
        <tr><td>B</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>B</td><td>x</td></tr>
        <tr><td>b</td><td>c</td><td>B</td></tr>
    </table>
</doc>

It did the substitutions in the second table, which I don’t want.

My current solution is to do this:

  1. Emit each table into a variable instead of directly into the output
  2. If the exception occurs, emit an <EXCEPTION/> tag
  3. After each table is processed, look through the variable for the <EXCEPTION/> tag.
  4. If the exception happened, copy the original table, else copy the contents of the variable.

Here’s the modified code:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

    <xsl:template match="table">
        <xsl:variable name="result">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:apply-templates mode="inner"/>
            </xsl:copy>
        </xsl:variable>
        <xsl:choose>
            <xsl:when test="$result//EXCEPTION">
                <xsl:copy-of select="."/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="$result"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template mode="inner" match="td">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:choose>
                <xsl:when test=". = 'a'">
                    <xsl:value-of select="'B'"/>
                </xsl:when>
                <xsl:when test=". = 'x'">
                    <EXCEPTION/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>

    <xsl:template mode="inner" match="@*|node()" priority="-10">
        <xsl:copy>
            <xsl:apply-templates mode="inner" select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*|node()" priority="-10">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

And the correct output:

<doc>
    <block>some text, just copy.</block>
    <!-- the following table should have B substituted for a -->
    <table>
        <tr><td>B</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>B</td><td>c</td></tr>
        <tr><td>b</td><td>c</td><td>B</td></tr>
    </table>
    <block>some more text, just copy.</block>
    <!-- the following table should be copied unaltered because of the presence of an x -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>x</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
</doc>
+1  A: 

If you can wait for XSLT 2.1 there will then be try/catch expressions

Nick Jones
I believe they have decided that next version of XSLT (and XPATH and XQuery) will be "3.0" http://www.w3.org/Bugs/Public/show_bug.cgi?id=9715
Mads Hansen
Nice. I can't wait, but I could switch to that later. That also got me looking at Saxon's extensions (which, admittedly, I should have done earlier). I'm using the HE version, but the EE version does have a similar mechanism ( http://www.saxonica.com/documentation/extensions/instructions/try-instr.html ). Not worth upgrading for this, but good to know.
Steven Ourada
A: 

The example provided is very simple and doesn't need any exception-handling capabilities.

The mode attribute is your friend:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
    <xsl:output omit-xml-declaration="yes"/>

 <xsl:template match="node()|@*" mode="#default copy">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*" mode="#current"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="table[tr/td[.='x']]">
  <xsl:apply-templates select="." mode="copy"/>
 </xsl:template>

 <xsl:template match="td/text()[.='a']">B</xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<doc>
    <block>some text, just copy.</block>
    <!-- the following table should have B substituted for a -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>c</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
    <block>some more text, just copy.</block>
    <!-- the following table should be copied unaltered because of the presence of an x -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>x</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
</doc>

the wanted, correct result is produced:

<doc>
    <block>some text, just copy.</block>
    <!-- the following table should have B substituted for a -->
    <table>
        <tr><td>B</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>B</td><td>c</td></tr>
        <tr><td>b</td><td>c</td><td>B</td></tr>
    </table>
    <block>some more text, just copy.</block>
    <!-- the following table should be copied unaltered because of the presence of an x -->
    <table>
        <tr><td>a</td><td>b</td><td>c</td></tr>
        <tr><td>b</td><td>a</td><td>x</td></tr>
        <tr><td>b</td><td>c</td><td>a</td></tr>
    </table>
</doc>

In case exceptions are really needed (we need yet to see a good use-case), they will be standard in XSLT 3.0.

Dimitre Novatchev
I'm sure you noticed that I specifically mentioned that my test case here was very simplified. Sorry I can't give you the entire real case; all the detail would obscure the basic idea anyway. You kinda just have to trust me that I believe exceptions would be useful to me :-). Sometimes the best solution isn't the conceptually most perfect one, but rather the easiest one...
Steven Ourada
@Steven-Ourada: Yes, I *have* to believe you. It would be better to be more convincing and to give such example. :)
Dimitre Novatchev