Matching templates, named templates or for-each? My rules of thumb

  • This discussion is closed: you can't post new comments.
  • This discussion is closed: you can't post new comments.
  • This discussion is closed: you can't post new comments.
  • This discussion is closed: you can't post new comments.

More rules of thumb: these are about when to use matching templates, when to use named templates, and when to use for-each. These have as much bearing in XSLT 1.0 as they do in XSLT 2.0.

My rules of thumb are:

  • If the code depends on the context position (position()), put it in a <xsl:for-each>.
  • If the code depends on the context node (. or any location path), put it in a matching template.
  • Otherwise, use a named template.

It’s easiest to debug and maintain a block of code if those factors that have an effect on its functionality are in close proximity to that block of code. Context position is affected by exactly which nodes are selected, and whether they’re sorted. For example, if you add commas between author names with

<xsl:template match="author/name">
  <xsl:apply-templates />
  <xsl:if test="position() != last()">, </xsl:if>
</xsl:template>

then you can’t know (just by looking at the template) when the commas will actually be added, because it all depends on how the template is invoked. It will work fine when you do

<xsl:apply-templates select="author/name" />

(because the sequence of nodes being processed contains the <name> elements) but say that six months later, someone else decided to add a <span> around the author information, with the template

<xsl:template match="author">
  <span class="author">
    <xsl:apply-templates select="name" />
  </span>
</xsl:template>

and changed the original <xsl:apply-templates> to

<xsl:apply-templates select="author" />

Assuming each <author> has only one <name>, both context position and size in the original author/name template will always be 1, and they will no longer get any commas.

So, I think it’s better to use <xsl:for-each> when you have tests on position, for example

<xsl:for-each select="author/name">
  <xsl:apply-templates select="." />
  <xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>

With this code, you can change the select on the <xsl:for-each> and the commas will still be added.

Similarly, I hate having to debug named templates that use the context node. The only way of finding out what the context node is (and therefore whether the location paths in the template are correct) is to examine all the <xsl:call-template> instructions that call that template, which can be really tedious. At least with a matching template, you know the kinds of nodes that the template might be used on. For example, rather than

<xsl:template name="CreateTOCentry">
  <xsl:call-template name="CreateTOCnumber" />
  <xsl:call-template name="CreateTOCtitle" />
</xsl:template>

I prefer

<xsl:template match="part | chapter | section" mode="TOC">
  <xsl:apply-templates select="number" mode="TOC" />
  <xsl:apply-templates select="title" mode="TOC" />
</xsl:template>

I can look up the definitions of <part>, <chapter> and <section> to make sure that they have <number> and <title> children.

Another pattern I see is named templates that contain a big <xsl:choose> instruction that tests what the context node is; the code would be much better split into separate templates with different match patterns for the different kinds of nodes.

Of course sometimes you need to use a named template because you need to recurse, but want to use information about the context node, either to provide a default value for a parameter or within the body of the template itself. A really stupid example is

<xsl:template name="CopyNode">
  <xsl:param name="n" select="1" />
  <xsl:if test="$n > 0">
    <xsl:copy-of select="." />
    <xsl:call-template name="CopyNode">
      <xsl:with-param name="n" select="$n - 1" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

In these cases, I either pass the node in as an additional (required) parameter, as in

<xsl:template name="CopyNode">
  <xsl:param name="node" />
  <xsl:param name="n" select="1" />
  <xsl:if test="$n > 0">
    <xsl:copy-of select="$node" />
    <xsl:call-template name="CopyNode">
      <xsl:with-param name="node" select="$node" />
      <xsl:with-param name="n" select="$n - 1" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

(there’s no way of saying the $node parameter is required in XSLT 1.0, so defaulting it to an empty string is the best we can do) or make the template a matching template as well

<xsl:template match="*" mode="CopyNode" name="CopyNode">
  <xsl:param name="n" select="1" />
  <xsl:if test="$n > 0">
    <xsl:copy-of select="." />
    <xsl:call-template name="CopyNode">
      <xsl:with-param name="n" select="$n - 1" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

and only invoke it with <xsl:apply-templates>. I don’t really care that the match pattern doesn’t tell me anything special about the node being copied: the fact that it’s a general pattern tells me that it’s a template that is designed to work with any element.

Comments

Re: Matching templates, named templates or for-each? My rules of

The other reason I would normally use named templates that depend on the context node is to factor out code that is common to a number of element types where you wouldn’t want (or be able) to simply extend the match statement. This most often occurs where you have templates that handle some base type and you want to make it easy to apply that processing to specializations of that base (either in the strict XSD sense or in the looser DITA sense of specialization).

I also try to document what the expectations on the context node are (otherwise, like you say, it can be very difficult to debug or understand later).

For example, I might do something like this:

<xsl:template match="docbook:section" name="sectionHandler">
  <!-- generic processing for section -->
</xsl:template>

and in a stylesheet for my customization I might have:

<xsl:template match="mystuff:supersection">
  <xsl:call-template name="sectionHandler"/>
</xsl:template>

Is there a more effective way to do this (short of having either type-aware templates or using DITA-style class= attributes and corresponding match values).

Cheers,

Eliot

Re: Matching templates, named templates or for-each? My rules of

In XSLT 1.0, I think I’d do what you’ve done, except that I’d probably use a parameter in the base template, as in

<xsl:template match="docbook:section" name="sectionHandler">
  <xsl:param name="section" select="." />
  <!-- generic processing for section -->
</xsl:template>

and refer to $section rather than the context node in the body of the template. Or, knowing me, and although I can’t justify it, I’d use a mode, and do

<xsl:template match="*" mode="sectionHandling">
  <!-- generic processing for section -->
</xsl:template>

<xsl:template match="docbook:section">
  <xsl:apply-templates select="." mode="sectionHandling" />
</xsl:template>

Then, create a template for the specialisation that applies templates to that element in the same mode:

<xsl:template match="mystuff:supersection">
  <xsl:apply-templates select="." mode="sectionHandling" />
</xsl:template>

In XSLT 2.0, I’d create a function to test whether something was a section:

<xsl:function name="docbook:is-section" as="xs:boolean">
  <xsl:param name="node" as="element()" />
  <xsl:apply-templates select="$node" mode="docbook:is-section" />
</xsl:function>

<xsl:template match="docbook:section" mode="docbook:is-section" as="xs:boolean">
  <xsl:sequence select="true()" />
</xsl:template>

<xsl:template match="*" mode="docbook:is-section" as="xs:boolean">
  <xsl:sequence select="false()" />
</xsl:template>

and use that function in the match pattern for my generic template:

<xsl:template match="*[docbook:is-section(.)]">
  <!-- generic processing for section -->
</xsl:template>

Then, in the stylesheet for my specialisation, add

<xsl:template match="mystuff:supersection" mode="docbook:is-section" as="xs:boolean">
  <xsl:sequence select="true()" />
</xsl:template>

(ie register <mystuff:supersection> as being a Docbook section).

If I needed to do extra things to the <mystuff:supersection> element, I can use <xsl:next-match> (or <xsl:apply-imports>) to get the basic result:

<xsl:template match="mystuff:supersection">
  <!-- other stuff -->
  <xsl:next-match />
  <!-- other stuff -->
</xsl:template>

In XSLT 2.0 Schema-Aware, you could use substitution groups to do the same kind of thing, even without using a full schema. But you would be limited to a single inheritance hierarchy, which might or might not fit your specialisations. User-defined functions like the above, on the other hand, can classify elements into groups in any way that you like.

The downside of this design is that it depends on you knowing, when you create the base stylesheet, what kind of specialisations you’re going to have. Plus it’s a lot more verbose than the method you describe.

Re: Matching templates, named templates or for-each? My rules of

In the last example, why not just lose the name and do the recursive call with apply templates select=”.” ? I think we’ll generally see named templates used less and less in 2.0 (apart from use as named initial entry points, which is quite a useful feature).

David

Re: Matching templates, named templates or for-each? My rules of

Dunno really: perhaps because with <xsl:apply-templates> there’s always the chance that some other template might be invoked accidentally (I’ve lost count of the number of times I’ve forgotten to put the right mode on an <xsl:apply-templates>).

I agree about named templates being used less in XSLT 2.0: it’s not just a matter of having functions instead, but XSLT 2.0 being more powerful anyway, and therefore being able to do much more without resorting to recursion.