【问题标题】:XPath to select elements between elements with different parentsXPath 在具有不同父级的元素之间选择元素
【发布时间】:2011-10-19 03:25:43
【问题描述】:

给定一个这样的 XML 文档:

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

给定条件“从 b 开始,在 d2b 停止” 是否有一个 XPath 表达式可以选择:

理想情况下

<c/><d><d1/><d2><d2a/></d2></d>

合理

<c/>

我知道,对于“从 'a' 开始,在 'e' 结束”这个条件,我可以使用表达式 //*[preceding-sibling::a][following-sibling::e];我想知道当开始和结束元素不能保证共享同一个父元素时,是否有办法对祖先轴和前面的兄弟元素进行一些奇怪的交集以找到一个共同的祖先。

【问题讨论】:

  • 这个问题不需要修改结构吧?它只需要返回一个有点奇怪的子集。 (不知道有没有可能)
  • @SteveBennett:实际上,问题确实需要修改结构,OP想要一个新结构,其中d2没有d2b和@ 987654327@ children -- 所以这些元素必须以某种方式被删除 -- 而 XPath 不能删除元素!

标签: xml xslt xpath


【解决方案1】:

XPath(1.0 和 2.0)是一种用于 XML 文档的查询语言。因此它不能改变任何 XML 文档的节点和结构

可以通过 XSLT 转换获得想要的结果(I. XSLT 1.0 下面使用):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*">
  <xsl:choose>
      <xsl:when test=
      "count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ">
       <xsl:call-template name="identity"/>
      </xsl:when>
      <xsl:otherwise>
       <xsl:apply-templates/>
      </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

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

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

产生了想要的正确结果

<c/>
<d>
   <d1/>
   <d2>
      <d2a/>
   </d2>
</d>

说明

  1. 身份规则“按原样”复制每个匹配的节点。

  2. 有一个覆盖任何元素的模板。

  3. 在这个模板中做了两个测试:当前节点是否属于所有元素的集合“跟随开始”当前节点是否属于所有元素的集合“在结束之前”。如果是,则将当前节点传递给标识模板(复制),否则将其忽略(删除)。


二。 XSLT 2.0 解决方案

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:variable name="vWanted" select=
  "$vFollowingStart intersect $vPrecedingEnd"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[not(. intersect $vWanted)]">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

当对上述 XML 文件应用此转换时,再次产生相同的正确结果。

说明:使用 XPath 2.0 运算符intersect


三。 XPath 1.0 解决方案,只选择节点而不更改文档:

为了便于阅读,我提供了一个 XSLT 转换,它输出选择所需节点的结果。出于同样的目的,子表达式被定义为变量:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

 <xsl:template match="node()|@*">
  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

  <xsl:copy-of select=
   "$vFollowingStart
      [count(.|$vPrecedingEnd)
      =
       count($vPrecedingEnd)
      ]
   "/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档(上图)时,输出所需的选定节点

<c/>
<d>

   <d1/>

   <d2>

      <d2a/>

      <d2b/>

      <d2c/>

   </d2>

</d>
<d1/>
<d2>

   <d2a/>

   <d2b/>

   <d2c/>

</d2>
<d2a/>

说明:这里我使用 Kayessian(@Michael Kay)公式计算两个节点集 $ns1$ns2 的交集:

$ns1[count(.|$ns2) = count($ns2)]

四。最后是Xpath 2.0的方案(对应XPath 1.0的方案):

我再次使用 XSLT (2.0) 转换将结果复制到输出:

<xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:param name="pStart" select="/*/b"/>
     <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

     <xsl:template match="node()|@*">
      <xsl:variable name="vFollowingStart" select=
      "$pStart/following::* | $pStart/descendant::*"/>

     <xsl:variable name="vPrecedingEnd" select=
      "$pEnd/preceding::* | $pEnd/ancestor::*"/>

      <xsl:sequence select=
       "$vFollowingStart intersect $vPrecedingEnd"/>
     </xsl:template>
</xsl:stylesheet>

产生与 XPath 1.0 解决方案相同的结果(正是需要的节点)

<c/>
<d>
        <d1/>
        <d2>
               <d2a/>
               <d2b/>
               <d2c/>
        </d2>
    </d>
<d1/>
<d2>
            <d2a/>
            <d2b/>
            <d2c/>
</d2>
<d2a/>

更新:这是针对“合理”问题的 XPath 1.0 解决方案。它再次表示为 XSLT 样式表模块,其中为了更好的可读性,子表达式被定义为单独的变量:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/*/b"/>
 <xsl:param name="pEnd" select="/*/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

  <xsl:variable name="vcommonAncestor" select=
  "$pStart/ancestor::*
    [count(.|$pEnd/ancestor::*)
    =
     count($pEnd/ancestor::*)
    ][1]
    "/>
 <xsl:variable name="vEndHighestAncestor" select=
  "$vcommonAncestor/*
       [count($pEnd | descendant::*)
       =
        count(descendant::*)
       ]"/>

  <xsl:variable name="vPrecedingEnd" select=
  "$vEndHighestAncestor/preceding::*
  |
   $vEndHighestAncestor/ancestor::*"/>


 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:copy-of select=
  "//*[count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ]
  "/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时(与提供的相同,但包装在一个顶部元素中,并将两个子元素(gh)添加到 @ 987654336@——让它更有趣:

<t>
<r>
  <a/><b/><c><g/><h/></c>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>
</t>

选择所需的正确节点集并将其复制到输出

<c>
   <g/>
   <h/>
</c>
<g/>
<h/>

解释:这和以前几乎一样,但是我们把$pEnd当作它的最高祖先——它是$pStart$pEnd共同祖先的直系子代。

【讨论】:

  • 第一个“理想”案例的好答案。我怀疑只有选择不能产生我所描述的。很高兴您为可能需要它的人提供了 XPath 解决方案。您对“合理”案件有答案吗? (找到每个端点的祖先之间共享一个共同父节点的节点。)
  • @Phrogz:我认为 XSLT 解决方案是您所需要的,所以我没有考虑挑战性较低的愿望。会这样做,但在下班回家(我刚到的地方)之后。
  • @Phrogz:如果我理解你的“合理”任务,选择的节点应该是:cd——不仅仅是c。如果我的理解不正确,请解释一下。
  • &lt;b&gt;&lt;e&gt; 之间选择会得到&lt;c&gt;&lt;d&gt;;在&lt;b&gt;&lt;d&gt; 之间选择只会给出&lt;c&gt;。为了保持一致性,我建议将&lt;d2b&gt; 视为&lt;d&gt;,因此只会选择&lt;c&gt;
  • @Phrogz:在我回答末尾的更新中,我为您的“合理”要求提供了解决方案(纯 XPath)。
【解决方案2】:

为了您的“合理”目标:给定标准“从 b 开始,在 d2b 停止”,您可以使用以下 XPath:

//b/following-sibling::*[following::d2b]

由于 following:: 轴不包括后代,这将只选择 b 的以下兄弟姐妹,直到作为 d2b 的祖先(或自身)的兄弟姐妹。

(我假设文档中只有一个 &lt;b&gt; 元素,正如您所假设的那样。)

【讨论】:

  • @_LarsH:不幸的是,您的简单 XPath 表达式在我的更新中只选择了 XML 文档中的一个元素,但必须选择三个元素。
  • @Dimitre:我对规范的解释不同,尤其是。 “在&lt;b&gt;&lt;e&gt; 之间选择会得到&lt;c&gt;&lt;d&gt;;在&lt;b&gt;&lt;d&gt; 之间选择只会得到&lt;c&gt;。为了保持一致性,我建议应该处理&lt;d2b&gt;&lt;d&gt;,因此只会选择。”我会看@OP 来判断我是否正确理解了他想要什么。如果没有,我会很高兴知道这一点。
  • 是的,这部分的问题没有详细说明。
  • @LarsH 您确实按照我的意图解释了我的 cmets。我仍然不明白这可能如何工作,但我看到它确实如此。我必须阅读following 轴,因为它的行为似乎与我怀疑的相反。谢谢!
  • 好的,我现在明白了。使用foo[following::bar] 意味着“匹配foo,如果它有一个bar 跟随它”,而不是“匹配foo,如果它跟随bar。这是我以前从未见过的强大轴心。非常好,会派上用场的。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-23
  • 2012-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
相关资源
最近更新 更多