XPath用于选择具有不同父项的元素之间的元素

时间:2011-10-19 03:25:44

标签: xml xslt xpath

给出这样的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];我想知道当开始和结束元素不能保证共享同一个父元素时,是否有办法在祖先轴和前兄弟姐妹之间做一些奇怪的交叉来找到共同的祖先。

2 个答案:

答案 0 :(得分:4)

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. 在此模板中,进行了两项测试:当前节点是否属于“开始后”所有元素的集合当前节点是否属于所有元素的集合“在结尾之前“。如果是,则将当前节点传递给身份模板(已复制),否则将忽略(删除)。


  4. <强> II。 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


    <强> III。 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)]
    

    <强> IV。最后是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 )添加到c - 使其更有趣:

    <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

答案 1 :(得分:2)

对于“合理”目标:根据标准“从b开始,在d2b处停止”,您可以使用以下XPath:

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

由于following::轴排除了后代,因此只选择跟随b的兄弟姐妹,直到d2b的祖先(或自我)。

(我假设文档中只有一个<b>元素,正如您似乎所假设的那样。)