5

I'm having some trouble determining the correct context for a node set. I have a template match that looks a bit like this (using XSL 2.0):

<xsl:template match="//chapter/body/*[matches(name(), '^toc')][crossref][not(crossref/@idref='cip' or crossref/@idref='copy')]">
  <xsl:variable name="curr_id" select="crossref/@idref"/>
  <xsl:element name="location">
    <xsl:attribute name="id"><xsl:value-of select="$curr_id"/></xsl:attribute>
    <xsl:attribute name="order"><xsl:value-of select="position()"/></xsl:attribute>
    <xsl:element name="label">
        <text><xsl:value-of select="."/></text>
    </xsl:element>
  </xsl:element>
</xsl:template>

The XML looks a bit like this:

<chapter id="toc">
  <body>
    <title>Contents</title>
    <tocfm><crossref idref="cip">Catalog</crossref></tocfm>
    <tocfm><crossref idref="copy">Copyright</crossref></tocfm>
    <tocfm><crossref idref="ded">Dedication</crossref></tocfm>
    <toc><crossref idref="prologue">Prologue</crossref></toc>
    <toc><crossref idref="pt1">Book One</crossref></toc>
    <toc><crossref idref="pt2">Book Two</crossref></toc>
    <toc><crossref idref="pt3">Book Three</crossref></toc>
  </body>
</chapter>

My expectation is that the predicate will generate a node set that contains:

<tocfm><crossref idref="ded">Dedication</crossref></tocfm>
<toc><crossref idref="prologue">Prologue</crossref></toc>
<toc><crossref idref="pt1">Book One</crossref></toc>
<toc><crossref idref="pt2">Book Two</crossref></toc>
<toc><crossref idref="pt3">Book Three</crossref></toc>

In other words, all of the toc-like elements that contain a crossref whose idref isn't cip or copy. The template does this in terms of output, but the position function doesn't seem to be working on that node set. Instead, it generates a position of '3' for Dedication. However, if I output the value of the node found by following the predicate with [1], I do get Dedication as that value. So, I'm stumped as to what position is working on. Can anyone enlighten me?

Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
Christina
  • 1,349
  • 1
  • 12
  • 22

2 Answers2

9

You expectation is wrong because it is based on a common misconception about how the context sequence is defined. The context sequence is NOT determined by the template match pattern, but rather by something outside of the template.

At some point xsl:apply-templates is invoked with a select expression. This expression is evaluated to produces an item sequence. The current context sequences is pushed onto a stack. This item sequence becomes the context sequence. The context sequence is iterated through in sequence order. At each item is visited, this item becomes the context item, and the position() function reflects the position of the context item within the context sequence. At this point the appropriate template is found based on the match expression and priorities. Match expressions DO NOT return sequences. They are simply a boolean test on the the context item - either the context item matches the template rule or it does not. If matched and highest priority, then the template sequence constructor is entered. At this point context sequence, context item and position() have all been defined by the client xsl:apply-templates, NOT by the match condition. After exiting the sequence constructor, control returns back to the client xsl:apply-templates. It will increment the position() number, update the context item, and again find the appropriate template. This may be the same template as the previous item or a different one.

Illustration

This style-sheet illustrates that it is the client instruction (apply-templates, for-each etc) that determines the context sequence and position(), not the template match pattern.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

<xsl:template match="/">
 <t>
   <note message ="apply-templates on EVEN items">
    <xsl:apply-templates select="*/item[ (@id mod 2) = 0]"/>
   </note>  
   <note message ="apply-templates on ODD items">
    <xsl:apply-templates select="*/item[ (@id mod 2) = 1]">
     <xsl:sort select="position()" data-type="number" order="descending"/>
    </xsl:apply-templates>
   </note>  
 </t>
</xsl:template>
      
<xsl:template match="item[@colour='blue']">
 <note message='Brought to you by the Blue template!' positon="{position()}"> 
  <xsl:copy-of select='self::*'/>
  </note>  
</xsl:template>
      
<xsl:template match="item[@colour='red']">
 <note message='Brought to you by the Red template!' positon="{position()}"> 
  <xsl:copy-of select='self::*'/>
  </note>  
</xsl:template>
      
</xsl:stylesheet>
    

... applied on this document ...

<t>
 <item id="1" colour="blue" /> 
 <item id="2" colour="blue" /> 
 <item id="3" colour="blue" /> 
 <item id="4" colour="red" /> 
 <item id="5" colour="red" /> 
 <item id="6" colour="red" /> 
</t>

... yields this output ...

<t>
  <note message="apply-templates on EVEN items">
    <note message="Brought to you by the Blue template!" positon="1">
      <item id="2" colour="blue" />
    </note>
    <note message="Brought to you by the Red template!" positon="2">
      <item id="4" colour="red" />
    </note>
    <note message="Brought to you by the Red template!" positon="3">
      <item id="6" colour="red" />
    </note>
  </note>
  <note message="apply-templates on ODD items">
    <note message="Brought to you by the Red template!" positon="1">
      <item id="5" colour="red" />
    </note>
    <note message="Brought to you by the Blue template!" positon="2">
      <item id="3" colour="blue" />
    </note>
    <note message="Brought to you by the Blue template!" positon="3">
      <item id="1" colour="blue" />
    </note>
  </note>
</t>
Community
  • 1
  • 1
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
  • Thank you for the detailed response. That was incredibly helpful; I was applying templates to a broader subset of toc entries and then handling them through two different templates, which I don't really need to do in this case. Code now simplified and working! – Christina Jul 11 '12 at 16:33
3

position() refers to the context position. From the XSLT 2.0 spec:

The context position is the position of the context item within the sequence of items currently being processed. It changes whenever the context item changes. When an instruction such as xsl:apply-templates or xsl:for-each is used to process a sequence of items, the first item in the sequence is processed with a context position of 1, the second item with a context position of 2, and so on.] The context position is returned by the XPath expression position().

What’s crucial to understand is that xsl:template doesn’t change the context item. It’s probably being changed by xsl:apply-templates or xsl:for-each. For example, consider this code:

<xsl:apply-templates select="tocfm|toc"/>

When it gets round to processing the Dedication node, that node will become the context item, with the context position becoming the position of that node within the input sequence (the union of all the tocfm and toc elements). This or something similar is probably where your 3 is coming from.

As the apply-templates part of the spec says:

The xsl:apply-templates instruction takes as input a sequence of nodes (typically nodes in a source tree) […] If the instruction has one or more xsl:sort children, then the input sequence is sorted as described in 13 Sorting. The result of this sort is referred to below as the sorted sequence; if there are no xsl:sort elements, then the sorted sequence is the same as the input sequence. […] Each node in the input sequence is processed by finding a template rule whose pattern matches that node. […] The rule that matches the Nth node in the sorted sequence is evaluated with that node as the context item, with N as the context position, and with the length of the sorted sequence as the context size.

You may find this FAQ entry on position() useful (though it explains the issue in terms of XSLT 1.0’s concept of a current node list, which isn’t used in XSLT 2.0).

Alex Bishop
  • 473
  • 3
  • 6
  • Your quotes are correct, but your first statement is wrong. The sequence of items currently being processed is called the context sequence. The old XSLT 1.0 terminology for this was the current node-set. So using this terminology position() IS indeed resolved with reference t the 'current node-set'. The fragment from the spec that you quote actually confirms this. – Sean B. Durkin Jul 11 '12 at 02:44
  • @SeanB.Durkin You’re right: “position() isn’t resolved with reference to the current node-set” is factually incorrect. I’ve edited the answer to remove this statement. What I meant to say was that position() isn’t resolved with reference to the current node-set that would be produced if the match expression in the xsl:template were evaluated. This seems to be how Christina thought it worked. Incidentally, you state that “The sequence of items currently being processed is called the context sequence.” Where in the XSLT 2.0 spec is the phrase “context sequence” defined? I can’t find it. – Alex Bishop Jul 11 '12 at 08:39
  • From Michael Kay's book "XSLT 2.0 and XPath 2.0 Programmer's Reference (Programmer to Programmer)". – Sean B. Durkin Jul 11 '12 at 09:11
  • Thank you both for all of the discussion; this has helped me quite a bit in getting my head around context. – Christina Jul 11 '12 at 16:35