Techniques for XSLT exception handling
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">
<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:
- Emit each table into a variable instead of directly into the output
- If the exception occurs, emit an
<EXCEPTION/>
tag - After each table is processed, look through the variable for the
<EXCEPTION/>
tag. - 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">
<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>
If you can wait for XSLT 2.1 there will then be try/catch expressions
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">
<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.
精彩评论