tags:

views:

594

answers:

1

Hi,

I was wondering if there was any way to access common parent node using Xpath.

<outer>
<main>
           <a><b> sometext </b></a>
           <c><d> sometext2 </d></c>
</main>
</outer>

I have the text nodes sometext and sometext2. is there a way i can access main (common parent) of these two nodes? I do not know the layout of the xml containing these nodes.

Any help is appreciated..

Thanks RP

+5  A: 

Use the following XPath 1.0 expression:

$v1/ancestor::*
   [count(. | $v2/ancestor::*) 
   = 
    count($v2/ancestor::*)
   ]
    [1]

where $v1 and $v2 hold the two text nodes (in case you use XPath not within XSLT, you will have to replace $v1 and $v2 in the above expression with the XPath expressions that select each one of these two text nodes).

Explanation:

The above XPath 1.0 expression finds the intersection of two node-sets: the node-set of all element ancestors of $v1 and the node-set of all element ancestors of $v2. This is done with the so called Kaysian method for intersection (after Michael Kay, who discovered this in 2000). Using the Kaysian method for intersection, the intersection of two nodesets, $ns1 and $ns2 is selected by the following XPath expression:

  $ns1[count(. | $ns2) = count($ns2)]

Then, from the intersection of the ancestors we must select the last element. However, because we are using a reverse axis (ancestor), the required node position must be denoted as 1.

One can quickly verify that the above XPath expression really selects the lowest common ancestor, by applying the following transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

 <xsl:output method="text"/>

  <xsl:variable name="v1" select="/*/*/a/b/text()"/>
  <xsl:variable name="v2" select="/*/*/c/d/text()"/>

  <xsl:variable name="vCommonAncestor" select=
   "$v1/ancestor::*
       [count(. | $v2/ancestor::*) 
       = 
        count($v2/ancestor::*)
       ]
        [1]"
   />

    <xsl:template match="/">
      <xsl:value-of select="name($vCommonAncestor)"/>
    </xsl:template>
</xsl:stylesheet>

when applied on the originally-provided XML document (corrected to well-formed XML):

<outer>
    <main>
     <a>
      <b>sometext</b>
     </a>
     <c>
      <d>sometext2</d>
     </c>
    </main>
</outer>

the wanted result (the name of the element that is the lowest common ancestor of the two text nodes) is produced:

main

The XPath 2.0 expression that selects the lowest common ancestor of the two nodes is simpler, because it uses the standard XPath 2.0 operator "intersect":

   ($v1/ancestor::* intersect $v2/ancestor::*) 
                                         [last()]
Dimitre Novatchev
Hi,As a follow up to the Xpath expression you mentioned. I'm kinda new to XPath and am accessing text nodes using Node objects (java). How do I compile this expression using the two node objects I have as parameters? something like (n1/ancestor::* intersect n2/ancestor::*) .. ?Thanks
Replace $v1 with: /*/*/a/b/text() and $v2 with: /*/*/c/d/text()
Dimitre Novatchev
There we go again. OP lost interest in his own question. Though maybe he didn't know that accepting a sufficient answer was part of the concept of this site.
Tomalak
@Tomalak I hope R Martin is really well (not sick) and that he can read the comments. As for SO, we recently saw that a person, whose sloppy answer was downvoted, can lowdly complain of being TROLLed and gain another 16 up-votes only from this complaint. So, how much reputation is real?
Dimitre Novatchev
Not much, I guess. I saw the thread and wondered if I would ever find out who the other guy was complaining about. Soon enough I knew. ;-) Though I must say the tone of the argument was a bit harsh on both sides. I tend to avoid dissecting other answers unless they are plain wrong because of that.
Tomalak