Re: [xsl] Confusion about preceding-sibling axis

Subject: Re: [xsl] Confusion about preceding-sibling axis
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 10 Apr 2001 00:24:38 +0100
Hi Darren,

> Once I'd created the item_list variable and used this for the
> <xsl:for-each> I expected all item elements to have the same
> ancestor and therefore all be siblings of each other. The results
> suggest that the original document hierarchy is being used instead.
>
> Am I missing something really obvious here?!

I guess so :) *All* XPath axes work on the node tree, *not* on the
current node list (the list of nodes that are currently being
processed) as defined in the select expression of xsl:for-each or
xsl:apply-templates. The siblings of a node are always the same no
matter what you're processing - the other children of the same parent
in the node tree.

It's actually fairly difficult to find out about the other nodes that
are being iterated over within XSLT.  You can look at the position()
of the node that's being processed, so for example you can identify
whether the current node is the first node that's being processed:

<xsl:template match="root">
  <xsl:variable name="item_list" select="//item"/>
  <html>
  <body>
    <xsl:for-each select="$item_list">
      <p><b><xsl:value-of select="."/></b><br/>
      <xsl:if test="position() = 1">First node<br /></xsl:if>
      </p>
    </xsl:for-each>
  </body>
  </html>
</xsl:template>

But in order to find the nodes that were processed before it, you have
to 'cheat' and index into the same node set again:

<xsl:template match="root">
  <xsl:variable name="item_list" select="//item"/>
  <html>
  <body>
    <xsl:for-each select="$item_list">
      <p><b><xsl:value-of select="."/></b><br/>
      <!-- record position of this node -->
      <xsl:variable name="pos" select="position()" />
      <xsl:choose>
         <xsl:when test="$pos = 1">First node<br /></xsl:when>
         <xsl:otherwise>Nodes before in document order are:
            <!-- iterate over the same node list, but only pick those
                 whose position is less than the position of the node
                 -->
            <xsl:for-each select="$item_list[position() &lt; $pos]">
               <xsl:value-of select="." />
               <xsl:text> </xsl:text>
            </xsl:for-each>
         </xsl:otherwise>
      </xsl:choose>
      </p>
    </xsl:for-each>
  </body>
  </html>
</xsl:template>

The other method is to change the siblings of an element by making a
new node tree in which the element has different siblings.  You can do
this by creating a variable that contains *copies* of the elements
you're interested in within a new result tree fragment:

   <xsl:variable name="item_list">
      <xsl:copy-of select="//item" />
   </xsl:variable>

You can then access the result tree fragment as a node set, by first
converting it to a node set with a node-set extension function such as
saxon:node-set(), msxsl:node-set(), lxslt:node-set() or equivalent for
whatever processor you're using (or exsl:node-set() when processors
adopt EXSLT).  If you iterate over that node set, you can use your
original code:

    <xsl:for-each select="exsl:node-set($item_list)">
      <p><b><xsl:value-of select="."/></b><br/>
        <xsl:choose>
          <xsl:when test="count(preceding-sibling::*) &lt; 1">No preceding
siblings<br/></xsl:when>
          <xsl:otherwise>Preceding siblings are:
            <xsl:for-each select="preceding-sibling::*">
              <xsl:value-of select="."/>&#160;
            </xsl:for-each>
          </xsl:otherwise>
        </xsl:choose>
      </p>
    </xsl:for-each>

(You'll probably find it more efficient to just test whether there are
any preceding siblings rather than counting them all and comparing it
to '1' - just use test="not(preceding-sibling::*)".)

The other things you might be interested in are the preceding:: and
following:: axes, which don't just select nodes with the same parent.
You could have used:

    <xsl:for-each select="$item_list">
      <p><b><xsl:value-of select="."/></b><br/>
        <xsl:choose>
          <xsl:when test="not(preceding::item)">No preceding
items<br/></xsl:when>
          <xsl:otherwise>Preceding items are:
            <xsl:for-each select="preceding::item">
              <xsl:value-of select="."/>&#160;
            </xsl:for-each>
          </xsl:otherwise>
        </xsl:choose>
      </p>
    </xsl:for-each>

Sorry to go on - it's just I think that playing with these different
approaches can really help you understand what's going on.
    
I hope that helps anyway,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread