Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSL: How to test if the current node is a descendent of another node

I'm pretty new to XSLT, but need to use it for a CMS using at the moment. I've already come up with a problem, but I'll try to describe my it without going into too much information about the underlying CMS. If you need more context to help me, I can add it in.

So all I want to do is test if a node of my xml is a descendant of a particular node.

<xsl:if test="$currentNode::IsADescendantOf($someNode)">
Write this out.
</xsl:if>

Any ideas anyone?

Thanks in advance :)

like image 884
Ev. Avatar asked Jan 22 '23 21:01

Ev.


2 Answers

You should use union operation and node-set size comparison:

<xsl:if test="count($someNode|$currentNode/ancestor::*) = count($currentNode/ancestor::*)">
Write this out.
</xsl:if>

If $someNode is an ancestor of $currentNode, $someNode|$currentNode/ancestor::* will return the same node-set as $currentNode/ancestor::* (node-set don't have any doublons).

If not, the first node-set will have one more node than the second because of the union.

like image 184
Erlock Avatar answered May 06 '23 10:05

Erlock


A portable (XPath 1.0 and 2.0) solution would be:

<xsl:if test="
  $currentNode/ancestor::*[generate-id() = generate-id($someNode)]
">
Write this out.
</xsl:if>

This walks up the ancestor axis and checks every element in it. If (and only if) the unique ID of one of the ancestors matches the unique ID of $someNode, the resulting node-set is not empty.

Non-empty node-sets evaluate to true, so the condition is fulfilled.

Test - find all <baz> that are descendant of <foo>:

<xml>
  <foo>
    <bar>
      <baz>Test 1</baz>
    </bar>
  </foo>
  <baz>Test 2</baz>
</xml>

and

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template match="/">
    <xsl:variable name="someNode" select="//foo[1]" />

    <!-- test each <baz> node if it has <foo> as a parent -->
    <xsl:for-each select="//baz">
      <xsl:if test="
        ancestor::*[generate-id() = generate-id($someNode)]
      ">
        <xsl:copy-of select="." />
      </xsl:if>

    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

results in

<baz>Test 1</baz>

Note that if you are referring to the actual current node, like I do in the for-each, there is no need for a separate $currentNode variable. All XPath is relative to the current node by default.

A variant would be the top-down way. It is less efficient though (probably by several orders of magnitude):

<xsl:if test="
  $someNode[//*[generate-id() = generate-id($currentNode)]]
">
Write this out.
</xsl:if>
like image 25
Tomalak Avatar answered May 06 '23 11:05

Tomalak