首页 > 解决方案 > 平面结构中的 XPath 递归“父”选择

问题描述

给出了以下 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 的所需输出应该是:

元素 2 的期望输出应该是:

元素 6 的期望输出应该是

这甚至可以用 XPath 实现吗?如果是,你怎么能做到?

标签: xmlxsltxpathxpath-3.0

解决方案


这甚至可以用 XPath 实现吗?如果是,你怎么能做到?

一、通用XSLT 1.0解决方案

正如 OA 的评论中所表达的:

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

这也称为“拓扑排序

这是我的 XSLT 1.0 拓扑排序实现,日期为 2001 年:

解决方案——回复:如何根据依赖图重新排列节点?

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

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


二、纯 XPath 3 解决方案

此 XPath 3.0 表达式定义了一个内联 (XPath 3.0) 函数,该函数计算元素的祖先路径,作为外部参数 $ 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 的验证

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

当此转换应用于提供的 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>

产生了想要的正确结果

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

推荐阅读