XPath recursive “parent”-selection in a flat structure

前端 未结 2 1211
北恋
北恋 2021-01-16 04:12

The following XML is given:


  
    1
  
  
    2
    <         


        
相关标签:
2条回答
  • 2021-01-16 04:40

    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>
    
    0 讨论(0)
  • 2021-01-16 04:54

    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"/>
    
    0 讨论(0)
提交回复
热议问题