Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get all ancestors of current node

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
like image 324
Moriarty Avatar asked Sep 11 '12 12:09

Moriarty


2 Answers

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:

  1. item[@title='b']
  2. item[@title='a']
  3. the root element (a.k.a. the document element)
  4. 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).

like image 96
Sean B. Durkin Avatar answered Sep 19 '22 17:09

Sean B. Durkin


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

like image 32
Dimitre Novatchev Avatar answered Sep 18 '22 17:09

Dimitre Novatchev