问题
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:
- element 1
- element 2
The desired output for element 2 should be:
- element 1
And the desired output for element 6 should be
- element 5
- element 2
- element 1
Is this even possible to achieve with XPath? If yes, how could you do it?
回答1:
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"/>
回答2:
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="start-id"/>
<xsl:key name="elem" match="element" use="id"/>
<xsl:template match="/root">
<root>
<xsl:apply-templates select="key('elem', $start-id)"/>
</root>
</xsl:template>
<xsl:template match="element">
<element id="{id}"/>
<xsl:apply-templates select="key('elem', parentId)"/>
</xsl:template>
</xsl:stylesheet>
Applying this to your XML input with a start-id
parameter value of 6
, will produce:
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element id="6"/>
<element id="5"/>
<element id="2"/>
<element id="1"/>
</root>
To exclude the starting node and list only its ancestors, you could do:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="start-id" select="6"/>
<xsl:key name="elem" match="element" use="id"/>
<xsl:template match="/root">
<root>
<xsl:apply-templates select="key('elem', key('elem', $start-id)/parentId)"/>
</root>
</xsl:template>
<xsl:template match="element">
<element id="{id}"/>
<xsl:apply-templates select="key('elem', parentId)"/>
</xsl:template>
</xsl:stylesheet>
来源:https://stackoverflow.com/questions/58101443/xpath-recursive-parent-selection-in-a-flat-structure