平面结构中的XPath递归“父”选择

时间:2019-09-25 15:04:07

标签: xml xslt xpath xpath-3.0

给出以下XML:

<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>

现在,我想选择所有“父”节点,例如元素3。假设元素3的期望输出应为:

  • 元素1
  • 元素2

元素2的期望输出应为:

  • 元素1

元素6的期望输出应为

  • 元素5
  • 元素2
  • 元素1

使用XPath甚至可以实现吗?如果是,您该怎么办?

2 个答案:

答案 0 :(得分:2)

  

使用XPath甚至可以实现吗?如果可以,你该怎么办   它吗?

I。通用XSLT 1.0解决方案

如OA的评论所述:

  

“目标是在孩子之前产生父母元素。”

这也称为“ topological sorting

这是我的2001年XSLT 1.0拓扑排序实现:

The Solution -- Re: how to rearrange nodes based on a dependency graph?

这是此XSLT拓扑排序的另一种变体,“使团簇保持在一起”(稳定的拓扑排序)https://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/200112/msg01009.html

关于使用纯XPath 获取给定元素的隐式层次祖先的ID序列,以下是使用XPath 3.0或更高版本的解决方案。


II。纯XPath 3解决方案

此XPath 3.0表达式定义了一个内联(XPath 3.0)函数,该函数计算作为外部参数$ pCurrent

传递的元素的祖先路径。
|--js
|  |-- app.js
|  |-- partials
|  |  |-- partialOne.js
|  |  |-- partialTwo.js

基于XSLT 3.0的验证

   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), '-')

此转换应用于提供的XML文档时:

<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>

产生了所需的正确结果

<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>

答案 1 :(得分:1)

考虑以下示例:

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>

将此参数应用于start-id参数值为6的XML输入将产生:

结果

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <element id="6"/>
  <element id="5"/>
  <element id="2"/>
  <element id="1"/>
</root>

要排除起始节点并仅列出其祖先,可以执行以下操作:

<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>