Re: counter

Subject: Re: counter
From: Jeni Tennison <Jeni.Tennison@xxxxxxxxxxxxxxxx>
Date: Wed, 21 Jun 2000 17:23:22 +0100

>Kay Michael wrote:
>The way to print something every fifty <item> elements is:
>> <xsl:template match="item[position() mod 50 = 1]">
>> <fifty-items>
>> Here's the next 50:
>> <xsl:for-each select=". | following-sibling::item[position() &lt; 50]">
>>   <item><xsl:value-of select="."/></item>
>> </xsl:for-each>
>> </fifty-items>
>> </xsl:template>
>Having tried this solution and some variations I had a couple of questions (I
>presume that this template was called by <xsl:apply-templates select="item">)
>a. I found that when running this I was getting the item content repeated in 
>the ouput for each element where the expression in the template match 
>attribute was false. I put this down to a built-in template being called for 
>those elements, is this correct ?

That's right.  The built-in templates include:

<xsl:template match="*|/">

which steps through the document, processing the root node, the document
element, and its descedents, including its content and:

<xsl:template match="text()|@*">
  <xsl:value-of select="."/>

which matches any text that's found (or attribute values) and gives the
value of that text.  When you only want to process some particular
elements, but the elements that you *don't* want to process contain text,
you have to make sure that you are processing only the elements you *do*
want to process.  You can do this in three ways:

1. move the filtering XPath from the 'match' attribute of the xsl:template
to the 'select' attribute of the xsl:apply-template that calls it (as you
have done):

<xsl:template match="root">
  <xsl:apply-templates select="item[position() mod $cntr = 1]"/>

<xsl:template match="item">
  <xsl:for-each select=". | following-sibling::item[position() &lt; $cntr]">
    Item <xsl:value-of select="."/>

2. put the content of the xsl:template within an xsl:if that tests on the
filter that you're using, and change the templates to match all (wanted and
not wanted) elements, so something like:

<xsl:template match="item">
  <xsl:if test="position() mod $cntr = 1">
    <xsl:for-each select=". |
                          following-sibling::item[position() &lt; $cntr]">
      Item <xsl:value-of select="."/>

3. add a mode to the template and make sure that the xsl:apply-templates
that calls it only calls templates in that mode, so something like:

<xsl:template match="root">
  <xsl:apply-templates mode="grouped" />

<xsl:template match="item[position() mod $cntr = 1]" mode="grouped">
  <xsl:for-each select=". | following-sibling::item[position() &lt; $cntr]">
    Item <xsl:value-of select="."/>

>However I did notice a significant
>performance improvement over Mike's code which was disproportionate to the
>number of item elements in the source document.

Mike will be able to tell you better than I can about this, but I suspect
that it's because in the original code, the items that were not picked up
by your template were first processed by the built-in template for
elements, and then their content processed by the built-in template for
text, before outputting the value of the item.

Searching for templates takes time, so filtering down the nodes that you
have to search for templates that match on benefits you, as does using
<xsl:value-of select="." /> rather than <xsl:apply-templates /> when you
know that the content of an element is just text [1].

Based on that, Options 1 and 3, which both filter within the
xsl:apply-templates are probably better than Option 2 in most cases.



[1] #8 of Mike's Eight tips for how to write efficient XSLT at

 XSL-List info and archive:

Current Thread