The following XML is given:
<root>
<element>
<id>1</id>
</element>
<element>
<id>2</id>
<parentId>1</parentId>
</element>
<element>
<id>3</id>
<parentId>2</parentId>
</element>
<element>
<id>4</id>
<parentId>3</parentId>
</element>
<element>
<id>5</id>
<parentId>2</parentId>
</element>
<element>
<id>6</id>
<parentId>5</parentId>
</element>
</root>
Now, I want to select all "parent"-nodes for e.g. element 3. Let's say, the desired output for element 3 should be:
The desired output for element 2 should be:
And the desired output for element 6 should be
Is this even possible to achieve with XPath? If yes, how could you do it?
Is this even possible to achieve with XPath? If yes, how could you do it?
I. General XSLT 1.0 solution
As expressed in a comment by the OA:
"The goal is to produce parent-elements before their children."
This is also known as "topological sorting"
And here is my XSLT 1.0 topological sort implementation, dated 2001:
"The Solution -- Re: how to rearrange nodes based on a dependency graph?"
And here is another variation of this XSLT topological sorting "that keeps the cliques together" (stable topological sort) https://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/200112/msg01009.html
As for getting with pure XPath the sequence of IDs of the implied-hierarchy-ancestors for a given element, below is a solution using XPath 3.0 or later.
II. Pure XPath 3 solution
This XPath 3.0 expression defines an inline (XPath 3.0) function that calculates the ancestor-path of an element, passed as outside parameter $pCurrent:
let $pCurrent := current(),
$ancestor-path-inner := function($el as element(), $self as function(*)) as xs:string*
{
let $parent := $el/../element[id eq $el/parentId]
return
if(not(empty($parent))) then $self($parent, $self)
else ()
,
$el/parentId
},
$ancestor-path := function($el as element()) as xs:string*
{ $ancestor-path-inner($el, $ancestor-path-inner)}
return
string-join($ancestor-path($pCurrent), '-')
XSLT 3.0 - based verification:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="element">
<element id="{id}" ancestor-path-ids=
"{let $pCurrent := current(),
$ancestor-path-inner := function($el as element(),
$self as function(*)) as xs:string*
{
let $parent := $el/../element[id eq $el/parentId]
return
if(not(empty($parent))) then $self($parent, $self)
else ()
,
$el/parentId
},
$ancestor-path := function($el as element()) as xs:string*
{ $ancestor-path-inner($el, $ancestor-path-inner)}
return
string-join($ancestor-path($pCurrent), '-')}"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<element>
<id>1</id>
</element>
<element>
<id>2</id>
<parentId>1</parentId>
</element>
<element>
<id>3</id>
<parentId>2</parentId>
</element>
<element>
<id>4</id>
<parentId>3</parentId>
</element>
<element>
<id>5</id>
<parentId>2</parentId>
</element>
<element>
<id>6</id>
<parentId>5</parentId>
</element>
</root>
the wanted, correct result is produced:
<element id="1" ancestor-path-ids=""/>
<element id="2" ancestor-path-ids="1"/>
<element id="3" ancestor-path-ids="1-2"/>
<element id="4" ancestor-path-ids="1-2-3"/>
<element id="5" ancestor-path-ids="1-2"/>
<element id="6" ancestor-path-ids="1-2-5"/>