Reputation: 653
I have an XSLT (1.0) stylesheet with a bunch of rules that transforms XML into HTML. Here's a sample of an XML file it might be used on (in practice, the XML files are much more complicated):
<msItem n="1">
<locus from="1r" to="21r">1r-21r</locus>
<author>Anonymous</author>
<title>Treatise on the practice of measured music</title>
</msItem>
The XSL file takes these (often deeply nested) attributes and transforms them into different HTML elements. Here's a (simplified) sample from the XSL file:
<xsl:template match="msItem[not(@style)]">
<tr class="ms-item">
<td class="num"><xsl:value-of select="@n"/></td>
<td class="locus">
<xsl:apply-templates select="locus"/>
</td>
<td class="ms-data">
<table class="ms-item fol">
<xsl:apply-templates select="*[name(.) != 'locus']"/>
</table>
</td>
</tr>
</xsl:template>
Today, the powers that be decided that the XML files should be bilingual and that the XSL should be able to handle either language. The new XML files look like something this:
<msItem n="1">
<locus from="1r" to="21r">1r-21r</locus>
<author xml:lang="en">Anonymous</author>
<author xml:lang="it">Anonimo</author>
<title xml:lang="en">Treatise on the practice of measured music</title>
<title xml:lang="it">Trattato sull’arte pratica del canto misurato</title>
</msItem>
What I've done so far is to define a stylesheet parameter (called $lang
) that holds the xml:lang
attribute we'd like to process ('it' or 'en'). What I would like to do is to be able to "wrap" my existing templates in some way such that I didn't have to write a bunch of <xsl:when>
tests to check the language on every one of them.
I have this template so far:
<xsl:template match="*">
<xsl:choose>
<xsl:when test="./@xml:lang">
<!-- Not all elements have an xml:lang attribute; we only want those that do. -->
<xsl:choose>
<xsl:when test="./@xml:lang=$lang">
<!-- This item is in the language we want, so process it. -->
<xsl:apply-templates/>
</xsl:when>
<xsl:otherwise/> <!-- Ignore other languages -->
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- No xml:lang attribute -->
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The problem is (of course) that this doesn't work, since all of my other templates have a greater priority than the wildcard here. Is there a way that I can effectively wrap this logic around all of my existing templates? I know that I could have this template effectively "return" a boolean, but then I'd have to duplicate all of the <xsl:when>
/<xsl:otherwise
junk in every template, which I'd like to avoid.
Upvotes: 1
Views: 119
Reputation: 60414
Unfortunately, it is illegal to reference a variable/param in a match pattern in XSLT 1.0. However, you can reference your $lang
parameter any time you apply templates. So, for example, in the root template (i.e. a template that matches /
or some other sufficiently top-level node), you could specify that you only want to apply templates to elements that have an xml:lang
attribute equal to the value of your param (or no xml:lang
at all):
<xsl:apply-templates select="*[not(@xml:lang) or @xml:lang=$lang]" />
This won't process any undesired elements and no other templates need to specify the language to match.
Upvotes: 2
Reputation: 122374
In XSLT 1.0 you can achieve what you want if you put your new template into a separate XSLT file which imports the existing one using <xsl:import>
, and change the <xsl:apply-templates/>
to <xsl:apply-imports/>
.
The templates in the importing stylesheet will take precedence whenever they match, and you use apply-imports
to delegate processing of the current node to the templates from the imported stylesheet where appropriate.
In XSLT 2.0 you could do all this in one file using xsl:next-match
but in 1.0 you have to use imports.
Finally, rather than checking the value of @xml:lang
directly, consider using the lang
function which can handle scoping (looking up the tree to find the nearest xml:lang
) and sub languages (en-GB
is a valid match when you are looking for en
).
Upvotes: 3