timlarsson
timlarsson

Reputation: 23

Check any level of parent nodes with XPath and XSLT

I have searched, but might have missed something obvious just because I don't know what to search for. And I found it hard to explain my question as a simple question, so let me explain: I am using the following code (XSLT 1.0 & XPath) to check if the parent to this node belongs to the last grand-parent node or not:

<xsl:when test="count(parent::*/preceding-sibling::*)+1 = count(parent::*/parent::*/*)">

It works exactly like I want. What I would like though is to make it more general in one way or the other, to make it work with even more parent nodes. I can add another template match and add another test:

<xsl:when test ="count(parent::*/parent::*/preceding-sibling::*)+1 = count(parent::*/parent::*/*)">

Is there a way to add "parent::*/" in a recursive template loop instead of creating a lot of specific template matches? Or should I work out a better XPath code altogether?

Please note: I want to do a check for every level of parent nodes. Is the parent the last of the parents, is the grand-parent the last of the grand-parents, etc.

For clarity's sake, I use it like this:

<xsl:choose>
  <xsl:when test="count(parent::*/preceding-sibling::*)+1 = count(parent::*/parent::*/*)">
    <!-- show image A -->
  </xsl:when>
  <xsl:otherwise>
    <!-- show image B -->
  </xsl:otherwise>
</xsl:choose>

Upvotes: 2

Views: 2435

Answers (1)

Tomalak
Tomalak

Reputation: 338118

<xsl:when test="count(parent::*/preceding-sibling::*)+1 = count(parent::*/parent::*/*)">

can be simplified to:

<xsl:when test="not(../following-sibling::*)">

In plain English "my parent does not have a following element sibling".


That would be easily modifiable to:

<xsl:when test="not(../../following-sibling::*)">

In plain English "my parent's parent does not have a following element sibling". Etc.


To check all ancestors at the same time:

<xsl:when test="not(ancestor::*/following-sibling::*)">

In plain English "none of my ancestors has a following element sibling".


To check parent and grandparent at the same time:

<xsl:when test="not(ancestor::*[position() &lt;= 2]/following-sibling::*)">

In plain English "none of my two closest ancestors has a following element sibling".


EDIT To check all ancestors individually, either use a recursive template (advantage: the position of the inner <xsl:apply-templates> determines if you effectively go up or down the list of ancestors):

<xsl:template match="*" mode="line-img">
  <xsl:if test="following-sibling::*">
    <!-- show image A -->
  </xsl:if>
  <xsl:if test="not(following-sibling::*)">
    <!-- show image B -->
  </xsl:if>
  <xsl:apply-templates select=".." mode="line-img" />
</xsl:template>

<!-- and later... -->

<xsl:apply-templates select=".." mode="line-img" />

...or a simple for-each loop (always works in document order):

<xsl:for-each select="ancestor::*">
  <xsl:if test="following-sibling::*">
    <!-- show image A -->
  </xsl:if>
  <xsl:if test="not(following-sibling::*)">
    <!-- show image B -->
  </xsl:if>
</xsl:for-each>

...or, to be completely idiomatic (always works in document order):

<xsl:template match="*" mode="line-img">
  <xsl:if test="following-sibling::*">
    <!-- show image A -->
  </xsl:if>
  <xsl:if test="not(following-sibling::*)">
    <!-- show image B -->
  </xsl:if>
</xsl:template>

<!-- and later... -->

<xsl:apply-templates select="ancestor::*" mode="line-img" />

Upvotes: 4

Related Questions