tags:

views:

120

answers:

4

I have an XSL template that is called (below). What I would like to do is be able to tell if I am the last Unit being called.

  <xsl:template match="Unit[@DeviceType = 'Node']">
    <!-- Am I the last Unit in this section of xml? -->
    <div class="unitchild">
      Node: #<xsl:value-of select="@id"/>
    </div>
  </xsl:template>

Example XML

<Unit DeviceType="QueueMonitor" Master="1" Status="alive" id="7">
    <arbitarytags />
    <Unit DeviceType="Node" Master="0" Status="alive" id="8"/>
    <Unit DeviceType="Node" Master="0" Status="alive" id="88"/>
</Unit>
+1  A: 

You can test position() against last():

<xsl:template match="Unit[@DeviceType = 'Node']">
 <!-- Am I the last Unit in this section of xml? -->     
 <xsl:if test="position() = last()">
   <!-- Yes I am! -->
   Last Unit
 </xsl:if>

 <div class="unitchild">
  Node: #<xsl:value-of select="@id"/>
 </div>
</xsl:template>

See w3schools article on xsl:if.

Oded
I tried looking at `position()`, however it also takes into account other nodes i.e. `<arbitarytags />` as opposed to just `Unit`
Chris
@Chris - it should only use the nodes that are in the template scope. You can try `./position()` and see if that helps.
Oded
It didnt like `./position()` and gave me an error
Chris
@Oded - your original @test condition(without `./`) produces the desired output for the sample, but `position()` and `last()` are 2:3 and 3:3 respectively for those Unit elements. It isn't evaluating `position()` and `last()` in terms of the template match context(nodeset), but in terms of document order and position as children of the parent.
Mads Hansen
@Mads: This is incorrect. `position()` and `last()` inside a template consider the current context node list, not the document order. Whether or not the expression `position() = last()` will yield the intended result depends on the node set and sort order selected by the invoking `<xsl:apply-templates>`. Note that if the normal identity transform is used - i.e. `select="node()|@*"` - the test will fail with the sample input provided, as the last node in the node set will not be a `Unit` element, but rather a text node representing the newline and indentation following the last `Unit` element.
markusk
+1  A: 

If you want to test whether it is the last Unit element at the same level (with the same parent element), even if there are arbitrary tags before, after, and in-between then this should work:

<xsl:if test="not(following-sibling::Unit)">
Mads Hansen
Will not work with any XML document and any `<xsl:apply-templates>` !
Dimitre Novatchev
The original question is about the last Unit being matched, not the last sibling! Which is the last Unit being matched depends only on the expression in the `select` attribute of `<xsl:apply-templates>`, not on the phisical properties of the XML document.
Dimitre Novatchev
@Dimitre: It also depends on the sort order applied (if any), not only the expression in the `select` attribute.
markusk
@markusk Yes, sure.
Dimitre Novatchev
+1  A: 

The currently selected answer is generally incorrect!

<xsl:if test="not(following-sibling::Unit)">

This Will not work with any XML document and any <xsl:apply-templates>

The original question is about the last Unit being matched, not the last sibling! Which is the last Unit being matched depends only on the expression in the select attribute of <xsl:apply-templates>, not on the physical properties of the XML document.

The way to do it:

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

then in the template that matches nodes selected by SomeExpression:

<xsl:if test="position() = last()">
. . . . 
</xsl:if>

This checks if the current node is the last in the node-list selected by <xsl:apply-templates>, not that the current node is the last sibling. This answers exactly the original question.

If the question was framed in a different way, asking how to recognize if the last sibling Unit is the current node, then the best solution would be to specify a separate template for this last sibling node:

<xsl:template match="Unit[last()]">
    . . . . 
</xsl:template>

Do note, that in this case there is no need to write any conditional logic inside a template to test if the current node is "the last".

Dimitre Novatchev
A: 

You should test position() = last(), but you should test it in predicate, not in the body of the template:

<?xml version="1.0" encoding="utf-8"?>

<data>
    <item group="B">AAA</item>
    <item>BBB</item>
    <item group="B">CCC</item>
    <item>DDD</item>
    <item group="B">EEE</item>
    <item>FFF</item>
</data>

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

<xsl:template match="data">
    <xsl:apply-templates select="item[@group]"/>
</xsl:template>

<xsl:template match="item">
    ITEM
    <xsl:if test="position() = last()">
    LAST IN CONTEXT
    </xsl:if>
</xsl:template>

<xsl:template match="item[position() = last()]">
    LAST ITEM
</xsl:template>

shabunc