Search code examples
xsltxslt-2.0

XSLT: How to ensure that a multi-mode identity template does not override all imported match templates?


I am writing an XSLT transformation that should largely leave the structure of the input XML unchanged, but add a few attributes and change a few element names.

To achieve this, I have an identity template, which ensures that all nodes which are not matched by another template (with a higher priority) will be retained as-is.

<!-- Identity template: leaves in place nodes not matched by a more specific template. -->
<xsl:template mode="#all" priority="2" match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates mode="#current" select="@*|node()"/>
  </xsl:copy>
</xsl:template>

I am performing a multipass transformation of the input document, with each pass using a different mode. To avoid duplication of the identity template across all these modes, I have therefore set its mode to #all.

The issue arises when I want to import a few templates with match attributes I want to be applied in this stylesheet. The algorithm XSLT uses to decide which template with a match attribute to apply when calling <xsl:apply-templates> is:

  1. mode
  2. match
  3. import precedence
  4. priority
  5. (declaration order)

Since the identity template has mode="#all" and match="@*|node()", it will match all modes and all node types. Then, the property next in line to determine which template will be applied is the template's import precedence. However, since imported templates always have a lower import precedence than local templates, this identity template will always trump any imported template, thus rendering importing templates with match attributes a futile endeavour.

My question is: what is the most suitable way around this? I.e. How to ensure that a multi-mode identity template does not override all imported match templates?

I have two proposed solutions, but neither are entirely satisfactory to me:

  1. change the value of the identity template's mode attribute to explicitly list all modes used, e.g. mode="#default mode1 mode2 mode3". I find this problematic in the sense that I now need to remember to update this attribute value every time I alter the modes in use (e.g. add or rename a mode). It would also involve keeping track of all the modes used in the imported stylesheet, or alternatively another identity template in the imported stylesheet for those modes.
  2. use <xsl:include> instead of <xsl:import> to fetch the relevant external templates. This would probably work in my specific case, but I don't like it as a general solution, as these XSLT elements have different semantics and work in different ways, and this is likely not what I want as my web of XSLT stylesheets grows in complexity.

Solution

  • A third potential solution seems to be the best one: putting the identity template in its own stylesheet, and then importing that into the stylesheet which needs it. If we ensure we make this the first import, that will give the identity template the lowest import precedence, which will again ensure that it will only be applied if there are no other applicable templates, which is the behaviour we sought.

    Thanks to Martin Honnen for the suggestion.