Get all ancestors of current node
Solution 1
Congradulations to Adam for a very quick first answer.
Just to add a little detail:
Your listed expected result does not match your words. the root element also an ancestor node and the document is also an ancestor node.
ancestor::node()
... will return a sequence in this order:
item[@title='b']
item[@title='a']
- the
root
element (a.k.a. the document element) - the root node
/
To get the specific result you listed, you need:
ancestor::item/.
The effect of the /. is to change the ordering back to forward document order. The native order of ancestor:: is reverse document order.
Update: Illustration of points made in the comment feed.
This style-sheet (with OP's input)...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="//item[@title='c']">
<xsl:value-of select="ancestor::item[1]/@title" />
<xsl:value-of select="ancestor::item[2]/@title" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
... will output 'ba' illustrating the point that ancestor:: is indeed a reverse axis. And yet this style-sheet ...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="//item[@title='c']">
<xsl:value-of select="(ancestor::item/@title)[1]" />
<xsl:value-of select="(ancestor::item/@title)[2]" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
... has the opposite result 'ab' . This is instructive because it shows that in XSLT 1.0 (not so in XSLT 2.0), the brackets remove the reverse nature, and it becomes a document ordered node-set.
The OP has asked about a transform something like....
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="//item[@title='c']">
<xsl:for-each select="ancestor::item">
<xsl:value-of select="@title" />
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This one returns 'ab' (in XSLT 2.0 it would return 'ba'). Why? Because in XSLT 1.0, the xsl:for-each instruction ignores the reverse-ness of the axis and processes in document order (unless an xsl:sort instruction says otherwise).
Solution 2
I want to get all ancestors of current node
The designated nodes obviously aren't all ancestors of the "green" node.
To select all ancestors use:
ancestor::node()
This selects all ancestor nodes of the initial conext node (or current node), including the top element and its parent -- the root node /
-- which is a node, but isn't an element.
To select all element ancestors use:
ancestor::*
This is similar to the previous expression, but doesn't select the root node (/
), because it isn't an element.
To select all ancestors named item
, use:
ancestor::item
Do Note: All expressions above assume that (//item[@title='c'])[1]
is the initial context node (current node). If this assumption is not correct, then (//item[@title='c'])[1]/
must be prepended to each of the expressions.
Note2: I strongly recommend for learning XPath to use a tool like the XPath Visualizer. For many years this tool has helped many thousands of people learn XPath the fun way -- by playing with XPath expressions and observing the result of their evaluation.
Note: The XPath Visualizer was created by me in year 2000 and was never a financial product. I am recommending it based solely on its value to the users, proven in the course of many years
Solution 3
You can use the xpath ancestor::item
Solution 4
The reason why you were outputting bcdx
instead of ab
is because your <xsl:for-each>
is iterating over all of the item
children of the selected item
elements, rather than just iterating over the selected elements in the $test
variable.
Change your for-each to just iterate over $test
:
<xsl:variable name="curr" select="//item[@title = 'c']"></xsl:variable>
<xsl:variable name="test" select="$curr/ancestor::item"></xsl:variable>
<xsl:for-each select="$test">
<xsl:value-of select="@title"></xsl:value-of>
</xsl:for-each>
Moriarty
Updated on September 13, 2020Comments
-
Moriarty over 3 years
I want to get all ancestors of current node:
XML:
<root> <item title="a"> <item title="b"> <item title="c"></item> <!--CURRENT--> <item title="d"></item> </item> <item title="x"> <item title="y"></item> <item title="z"></item> </item> </item> </root>
Result:
<item title="a">...</item> <item title="b">...</item>
Edit: Answers with axes ancestor are fine. My problem was elsewhere, in XSLT
XSLT:
<xsl:variable name="curr" select="//item[@title = 'c']"></xsl:variable> <xsl:variable name="test" select="$curr/ancestor::item"></xsl:variable> <xsl:for-each select="$test/item"> <xsl:value-of select="@title"></xsl:value-of> </xsl:for-each>
Returns:
bcdx
Edit2: for dimitre and for all who have a similar problem
All the answers to my question were good.
Just XSLT (up) returns to me a strange result and @Mads Hansen corrected me.
FINAL WORKING EXAMPLE:
XML:
<?xml version="1.0" encoding="utf-8"?> <root> <item title="a"> <item title="b"> <item title="c"></item> <item title="d"></item> </item> <item title="x"> <item title="y"></item> <item title="z"></item> </item> </item> </root>
XSLT:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:variable name="curr" select="//item[@title = 'c']"></xsl:variable> <xsl:variable name="test" select="$curr/ancestor::item"></xsl:variable> <xsl:for-each select="$test"> <xsl:value-of select="@title"></xsl:value-of> </xsl:for-each> </xsl:template> </xsl:stylesheet>
Returns:
ab
-
Sean B. Durkin over 11 yearsSlight error: You need to reverse the order to meet the OP's expectations.
-
Moriarty over 11 yearsi have same result with /. and without. <xsl:for-each select="//item[@title = 'c']/ancestor::item"> is the same <xsl:for-each select="//item[@title = 'c']/ancestor::item/."> .. result: ab
-
Sean B. Durkin over 11 yearsIn XPATH 1.0 or 2.0, ancestor:: is indeed a reverse axis (refer w3.org/TR/xpath section 2.4; and w3.org/TR/xpath20 section 3.2.1.1), But how you actually use it XSLT may change the order - and the behaviour depends on XSLT version. ....
-
Sean B. Durkin over 11 yearsIn XSLT 1.0, an xsl:for-each instruction will ignore the order in the xpath expression, (unless a sort applies). But in XSLT 2.0, the instrinsic order is retained.
-
Sean B. Durkin over 11 yearsTo understand what I mean, try using a positional predicate. Even in XSLT 1.0 using xsl:for-each, you get the reverse order.
-
Sean B. Durkin over 11 yearsI will post a short piece of XSLT 1.0 to illustrate.
-
Sean B. Durkin over 11 yearsMy statement "Even in XSLT 1.0 using xsl:for-each, you get the reverse order" in earlier comment was very badly worded. Just ignore that one.