XPath recursive “parent”-selection in a flat structure

六月ゝ 毕业季﹏ 提交于 2020-01-22 00:24:10

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!