两个元素之间的XPath

时间:2011-11-18 11:42:01

标签: xml xpath

我有一个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”。

这可能吗?

4 个答案:

答案 0 :(得分:2)

//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#...)。

此外,如果存在多个可能嵌套的“开始”和“结束”标记,则会更复杂。

答案 1 :(得分:1)

在任何类似问题中,可以使用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>

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

之前的答案都是关于 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 平方。