Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPath 1.0 closest preceding and/or ancestor node with an attribute in a XML Tree

Tags:

Here are three XML-trees

(1)

<?xml version="1.0" encoding="UTF-8"?> <content>    <section id="1">         <section id="2"/>         <section id="3"/>         <section id="9"/>     </section>     <section id="4">         <section id="5">             <section>                 <bookmark/> <!-- here's the bookmark-->                 <section id="6">                     <section id="7">                         <section id="8"/>                     </section>                </section>             </section>         </section>     </section> </content> 

(2)

<?xml version="1.0" encoding="UTF-8"?> <content>     <section id="1"/>     <section id="2">         <section id="9">             <section id="10">                 <section id="11"/>             </section>                     </section>         <section>             <section id="4">                 <section id="5"/>             </section>                     </section>         <section/>         <bookmark/> <!-- here's the bookmark-->         <section id="6">             <section id="7">                 <section id="8"/>             </section>         </section>     </section> </content> 

The desired result is in both cases the id 5.

With XSLT 1.0 and XPath 1.0 I can either get the ancestor from (1)

<xsl:value-of select="//bookmark/ancestor::*[@id][1]/@id"/> 

or the preceding node from (2)

<xsl:value-of select="//bookmark/preceding::*[@id][1]/@id"/> 

How do I get the nearest ancestor or preceding node with an id from my bookmark?
I need a single xsl:value-of which matches both cases. Thanks.

EDIT:

The solution should also cover this structure. Desired id is still 5.

(3)

<?xml version="1.0" encoding="UTF-8"?> <content>    <section id="1">         <section id="2"/>         <section id="3"/>         <section id="9"/>     </section>     <section id="4">         <section>             <section id="10"/>             <section id="5"/>             <section>                 <bookmark/> <!-- here's the bookmark-->                 <section id="6">                     <section id="7">                         <section id="8"/>                     </section>                 </section>             </section>         </section>     </section> </content> 
like image 325
therealmarv Avatar asked Jul 01 '11 23:07

therealmarv


People also ask

What is difference between ancestor and parent in XPath?

The difference between parent:: and ancestor:: axis is conveyed by their names: A parent is the immediately direct ancestor. So, yes /a/b/c/d/ancestor::*[1] would be the same as /a/b/c/d/parent::* .

What is a node in XPath?

In XPath, there are seven kinds of nodes: element, attribute, text, namespace, processing-instruction, comment, and document nodes. XML documents are treated as trees of nodes. The topmost element of the tree is called the root element.


2 Answers

Use:

    (//bookmark/ancestor::*[@id][1]/@id  |      //bookmark/preceding::*[@id][1]/@id      )      [last()] 

Verification: Using XSLT as host of XPath, the following transformation:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:output omit-xml-declaration="yes" indent="yes"/>   <xsl:template match="/">   <xsl:value-of select=   "(//bookmark/ancestor::*[@id][1]/@id   |    //bookmark/preceding::*[@id][1]/@id    )     [last()]"/>  </xsl:template> </xsl:stylesheet> 

when applied on any of the provided three XML documents, produces the wanted, correct result:

5

I strongly recomment using the XPath Visualizer for playing with / learning XPath.

like image 184
Dimitre Novatchev Avatar answered Sep 17 '22 14:09

Dimitre Novatchev


Try with :

<xsl:value-of      select="//bookmark/ancestor::*[1]/descendant-or-self::*[last()-1]/@id"/> 

It returns 5 for both XML documents.

EDIT:

In such conditions you could use simple xsl:choose:

<xsl:variable name="lastSibling"     select="//bookmark/preceding-sibling::*[1]"/> <xsl:choose>     <xsl:when test="$lastSibling">         <xsl:value-of             select="$lastSibling/descendant-or-self::*[last()]/@id"/>     </xsl:when>     <xsl:otherwise>         <xsl:value-of select="//bookmark/ancestor::*[@id][1]/@id"/>     </xsl:otherwise> </xsl:choose> 

Another general solution:

<xsl:for-each     select="//section[following::bookmark or descendant::bookmark][@id]">     <xsl:if test="position() = last()">         <xsl:value-of select="./@id"/>     </xsl:if> </xsl:for-each> 
like image 32
Grzegorz Szpetkowski Avatar answered Sep 18 '22 14:09

Grzegorz Szpetkowski