【问题标题】:XPath between two elements两个元素之间的 XPath
【发布时间】:2012-01-01 04:24:33
【问题描述】:

我有一个 Word 2003 XML 文档,我试图在其中搜索某些元素。我已经能够执行简单的 XPath 查询来查找单个元素,但是我很难想出一个查询来在两个元素之间进行搜索:

    <w:r>
      <w:fldChar w:fldCharType="begin"/>
    </w:r>
    <w:r>
      <w:instrText> DOCPROPERTY  EvidenceBase  \* MERGEFORMAT </w:instrText>
    </w:r>
    <w:r>
      <w:fldChar w:fldCharType="separate"/>
    </w:r>
    <w:r>
      <w:t>EvidenceBase</w:t>
    </w:r>
    <w:r>
      <w:fldChar w:fldCharType="end"/>
    </w:r>

我正在搜索上面的 XML,其中有一个 w:r,其中有一个 w:fldChar,其属性为 w:fldCharType,值为“begin”。它应该返回每个元素,直到它碰到一个 w:r,其中有一个 w:fldChar,它的属性为 w:fldCharType,值为“end”。

这可能吗?

【问题讨论】:

    标签: xml xpath


    【解决方案1】:
    //w:r[preceding-sibling::w:r[w:fldChar/@w:fldCharType='begin'] and following-sibling::w:r[w:fldChar/@w:fldCharType='end']]
    

    请注意前缀 w 需要绑定到 XPath 表达式命名空间上下文的正确命名空间。这如何完成取决于您如何使用 XPath(XSLT、Java、C#...)。

    此外,如果有多个可能嵌套的“开始”和“结束”标记,这将更加复杂。

    【讨论】:

      【解决方案2】:

      在任何类似的问题中,都可以使用 Kayessian 公式计算节点集交集

      如果我们有两个节点集$ns1$ns2,那么属于这两个节点集的所有节点都被这个 XPath 表达式选择:

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

      在您的情况下,您只需将 $ns1 替换为

      //w:r[w:fldChar/@w:fldCharType='begin'][1]/following-sibling::*  
      

      ..

      并将$ns2 替换为

      //w:r[w:fldChar/@w:fldCharType='end'][1]/preceding-sibling::*  
      

      生成的 XPath 表达式可能看起来过于复杂,但您获得的是能够非常轻松且几乎机械地解决任何此类问题的能力:

        /*/w:r
            [w:fldChar/@w:fldCharType='begin']/following-sibling::*
           [count(. | /*/w:r[w:fldChar/@w:fldCharType='end']
                                           /preceding-sibling::*
                  )
           =
            count(/*/w:r[w:fldChar/@w:fldCharType='end']
                                           /preceding-sibling::*)
           ]
      

      基于 XSLT 的验证:

      <xsl:stylesheet version="1.0"
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
       xmlns:w="some:namespace">
       <xsl:output omit-xml-declaration="yes" indent="yes"/>
       <xsl:strip-space elements="*"/>
      
       <xsl:template match="/">
           <xsl:copy-of select=
           "/*/w:r
                [w:fldChar/@w:fldCharType='begin']/following-sibling::*
               [count(. | /*/w:r[w:fldChar/@w:fldCharType='end']
                                               /preceding-sibling::*
                      )
               =
                count(/*/w:r[w:fldChar/@w:fldCharType='end']
                                               /preceding-sibling::*)
               ]
           "/>
       </xsl:template>
      </xsl:stylesheet>
      

      当此转换应用于此 XML 文档时

      <t xmlns:w="some:namespace">
          <w:r>
            <w:fldChar w:fldCharType="before-begin"/>
          </w:r>
          <w:r>
            <w:fldChar w:fldCharType="begin"/>
          </w:r>
          <w:r>
            <w:instrText> DOCPROPERTY  EvidenceBase  \* MERGEFORMAT </w:instrText>
          </w:r>
          <w:r>
            <w:fldChar w:fldCharType="separate"/>
          </w:r>
          <w:r>
            <w:t>EvidenceBase</w:t>
          </w:r>
          <w:r>
            <w:fldChar w:fldCharType="end"/>
          </w:r>
          <w:r>
            <w:fldChar w:fldCharType="after-end"/>
          </w:r>
      </t>
      

      正是需要的元素被选中并复制到输出中

      <w:r xmlns:w="some:namespace">
         <w:instrText> DOCPROPERTY  EvidenceBase  \* MERGEFORMAT </w:instrText>
      </w:r>
      <w:r xmlns:w="some:namespace">
         <w:fldChar w:fldCharType="separate"/>
      </w:r>
      <w:r xmlns:w="some:namespace">
         <w:t>EvidenceBase</w:t>
      </w:r>
      

      【讨论】:

      • +1 - 我很欣赏 Kayessian 公式的优雅,但一开始我很惊讶地看到(经过几次 非常 非正式基准测试后)它的执行速度比@G_H 回答中的“天真”解决方案。 (使用 Saxon 进行测试。)
      • @lwburk:谢谢。是的,这是一个“快速而肮脏”的解决方案,它对于大型节点集可能效率不高。无论如何,在 XPath 2.0 中,intersect 运算符可能会更有效。
      【解决方案3】:

      如果前面的开始数与结束数不同,我们必须在开始和结束之间。因此:

      w:r[count(preceding-sibling::w:r[w:fldChar/@w:fldCharType='begin']) != count(preceding-sibling::w:r[w:fldChar/@w:fldCharType='end'])]
      

      【讨论】:

        【解决方案4】:

        前面的答案都是关于 XPath 1.0 的。我将在 XPath 2.0 和 XPath 3.x 中添加解决方案。

        对于 XPath 2.0

        我们可以使用intersect 关键字来简化XPath。

        w:r[w:fldChar/@w:fldCharType='begin']/following-sibling::*
        intersect
        w:r[w:fldChar/@w:fldCharType='end']/preceding-sibling::*
        

        对于 XPath 3.x

        我们可以声明两个变量来获取开始和结束元素的索引。并按两个变量过滤列表。

        let $x=:index-of(w:r[w:fldChar/@w:fldCharType='begin']),
        $y=:index-of(w:r[w:fldChar/@w:fldCharType='end']),
        w:r[position()>=$x and position()<=$y]
        

        XPath 3.x 中的解决方案会快得多,因为时间复杂度只有 n,而对于 XPath 2.0 和 1.0,时间复杂度是 n 平方。

        【讨论】:

          猜你喜欢
          • 2012-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多