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 CM开发者_Python百科S. 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 :)
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.
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>
Look at the "ancestor" axis in your XSL/XPath reference
<xsl:if test="ancestor::*[. is $somenode]">
EDIT: I'm learning along with you. See if this works (I wish I had my XSL debugger available on this system :-)
The answer is quite easy,
all you have to do is:
<xsl:if test="count(ancestor::somenode)>0">
... then add the rest
the logic is that if the node is a descendant of somenode, then it will have one or more of that node.
Descendant checking
The best way would be to use the descendant::* axis.
If you have a piece of XML looking like this and want to match the descendant <a>
nodes from the first <alpha>
node.
XML
<root>
<!-- check for descendants of the following alpha node-->
<alpha>
<!-- match the following node-->
<a>...</a>
<b>...</b>
<c>...</c>
</alpha>
<alpha>
<a>...</a>
<c>...</c>
</alpha>
</root>
XSLT
<xsl:if test="/root/alpha[1]/descendant::a[. is current()]">
Ancestor checking
This would probably work in most cases.
<xsl:if test="ancestor::*[name() = $node]">
Where
<xsl:variable name="node" select="name(//alpha)"/>
Or you can just use generate-id() if you have multiple nodes to compare against and need to make sure that it is a node match and not a name match.
<xsl:variable name="node" select="generate-id(//alpha[3])"/>
<xsl:if test="ancestor::*[generate-id(.) = $node]">
The is
comparison operator cannot IMO be used when comparing ancestors since it requires a leaf node, and leaf nodes do not have descendants.
But i'd recommend using the descendant::* axis since it's much more readable and flexible.
精彩评论