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>
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::* .
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.
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With