【问题标题】:XPath Expression: Select elements between A HREF="expr" tagsXPath 表达式:在 A HREF="expr" 标记之间选择元素
【发布时间】:2011-07-02 00:04:30
【问题描述】:

我没有找到一种明确的方法来选择 HTML 文件中两个锚点(<a></a> 标记对)之间存在的所有节点。

第一个锚点的格式如下:

<a href="file://START..."></a>

第二个锚点:

<a href="file://END..."></a>

我已经验证可以使用starts-with来选择两者(注意我使用的是HTML Agility Pack):

HtmlNode n0 = html.DocumentNode.SelectSingleNode("//a[starts-with(@href,'file://START')]"));
HtmlNode n1 = html.DocumentNode.SelectSingleNode("//a[starts-with(@href,'file://END')]"));

考虑到这一点,并凭借我业余的 XPath 技能,我编写了以下表达式来获取两个锚点之间的所有标签:

html.DocumentNode.SelectNodes("//*[not(following-sibling::a[starts-with(@href,'file://START0')]) and not (preceding-sibling::a[starts-with(@href,'file://END0')])]");

这似乎可行,但会选择所有 HTML 文档!

我需要,例如下面的 HTML 片段:

<html>
...

<a href="file://START0"></a>
<p>First nodes</p>
<p>First nodes
    <span>X</span>
</p>
<p>First nodes</p>
<a href="file://END0"></a>

...
</html>

移除两个锚点,三个 P(当然包括内部 SPAN)。

有什么办法吗?

我不知道 XPath 2.0 是否提供了更好的方法来实现这一点。

*编辑(特殊情况!)*

我还应该处理以下情况:

"在 X 和 X' 之间选择标签,其中 X 是&lt;p&gt;&lt;a href="file://..."&gt;&lt;/a&gt;&lt;/p&gt;"

所以而不是:

<a href="file://START..."></a>
<!-- xhtml to be extracted -->
<a href="file://END..."></a>

我也应该处理:

<p>
  <a href="file://START..."></a>
</p>
<!-- xhtml to be extracted -->

<p>
  <a href="file://END..."></a>
</p>

再次感谢您。

【问题讨论】:

  • 好问题,+1。请参阅我对两个解决方案(XPath 1.0 和 XPath 2.0)的回答、解释以及使用 XSLT 作为 XPath 的宿主的验证。

标签: html select xpath tags


【解决方案1】:

使用这个 XPath 1.0 表达式

//a[starts-with(@href,'file://START')]/following-sibling::node()
     [count(.| //a[starts-with(@href,'file://END')]/preceding-sibling::node())
     =
      count(//a[starts-with(@href,'file://END')]/preceding-sibling::node())
     ]

或者,使用这个 XPath 2.0 表达式

    //a[starts-with(@href,'file://START')]/following-sibling::node()
  intersect
    //a[starts-with(@href,'file://END')]/preceding-sibling::node()

XPath 2.0 表达式使用 XPath 2.0 intersect 运算符。

XPath 1.0 表达式使用 Kayessian(@Michael Kay 之后)公式计算两个节点集的交集:

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

使用 XSLT 进行验证

这个 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:template match="/">
  <xsl:copy-of select=
  "    //a[starts-with(@href,'file://START')]/following-sibling::node()
         [count(.| //a[starts-with(@href,'file://END')]/preceding-sibling::node())
         =
          count(//a[starts-with(@href,'file://END')]/preceding-sibling::node())
         ]
  "/>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时

<html>...
    <a href="file://START0"></a>
    <p>First nodes</p>
    <p>First nodes    
        <span>X</span>
    </p>
    <p>First nodes</p>
    <a href="file://END0"></a>...
</html>

产生想要的正确结果

<p>First nodes</p>
<p>First nodes    
        <span>X</span>
</p>
<p>First nodes</p>

这个 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:template match="/">
  <xsl:copy-of select=
  " //a[starts-with(@href,'file://START')]/following-sibling::node()
   intersect
    //a[starts-with(@href,'file://END')]/preceding-sibling::node()
  "/>
 </xsl:template>
</xsl:stylesheet>

当应用到同一个 XML 文档(上图)上时,会再次产生所需的结果

【讨论】:

  • +1。即使我很清楚 XPath 1.0 Kayessian 公式何时会派上用场,但我总是很难意识到如何在各种情况下应用它;而我可以很容易地阅读 XPath 2.0 的交集。不过这个例子真的很有用。
  • @empo:您可以使用 XPath Visualizer 将它们都可视化 :)
  • 我很惊讶 HtmlAgilityPack SelectNodes 方法不支持 XPath 2.0。
  • @Hernan:XPath 2.0 实现很少,它们通常作为 XSLT 2.0 处理器或 XQuery 处理器的一部分出现——几乎从不独立。我们在这方面存在技术差距,并且随着 XPath 3.0 逐渐成为官方标准,差距将会扩大。
  • Dimitry,我添加了一个我应该处理的特殊情况:P
【解决方案2】:

我添加了一个我应该处理的特殊情况

要处理这种特殊情况,您可以以相同的方式工作,我的意思是使用 Kayessian(并使用 XPath Visualizer ;-))。相交节点集变化如下:

相交节点集 C

    "//p[.//a[starts-with(@href,'file://START')]]
         /following-sibling::node()"

p 的所有后续兄弟包含 aSTART

相交节点集 D

"./following-sibling::p[.//a[starts-with(@href,'file://END')]]
    /preceding-sibling::node())"

p 的所有前面的兄弟姐妹包含 a END 和当前 p 的以下兄弟姐妹


现在您可以将交集执行为:

C ∩ D

那是

    "//p[.//a[starts-with(@href,'file://START')]]
            /following-sibling::node()[
            count(.| ./following-sibling::p
                     [.//a[starts-with(@href,'file://END')]]
                       /preceding-sibling::node())
            =
            count(./following-sibling::p
                   [.//a[starts-with(@href,'file://END')]]
                     /preceding-sibling::node())
            ]"

如果您需要同时管理这两种情况,您可以将相交的节点集合并为

(A ∩ B) ∪ (C ∩ D)

地点:

  • 必须使用 XPath 联合运算符 |
  • 节点集 A e B 已在 @Dimitre'answer 中显示
  • 节点集 C e D 是我的答案中显示的。

【讨论】:

  • 太棒了,非常感谢!似乎 XPath 2.0 使这些设置操作变得更加容易,不幸的是 .NET 3 中没有 2.0 支持!
  • 看来。你可以写$n1 intersect $n2 代替$n1[count(.|$n2)=count($n2)]。无论如何,节点集的选择很棘手。
  • @Dimitre:嗯,这意味着我绝对很好! :D 谢谢
猜你喜欢
  • 2017-01-23
  • 2012-03-01
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
相关资源
最近更新 更多