【问题标题】:Is it possible to truncate an XPath axis at a given node?是否可以在给定节点截断 XPath 轴?
【发布时间】:2012-02-09 19:11:50
【问题描述】:

我一直在编写一些从网页中提取主要文本内容的代码。一个有用的策略是定位内容的第一段,然后选择以下所有同级元素,直到但不包括第一个不是 pulolblockquote 元素。在 Perl 中,代码如下所示:

my ($firstpara) = $document->findnodes('//p[whatever]');
my @content = ($firstpara);
for my $sibling ($firstpara->findnodes('following-sibling::*')) {
    last if $sibling->tag !~ /^(?:p|ol|ul|blockquote)\z/;
    push @content, $sibling;
}

这还不错,但如果能够只使用 XPath 来获取我想要的节点,那就太酷了,所以我可以写这样的东西:

my ($firstpara) = $document->findnodes('//p[whatever]');
my @content = ($firstpara, $firstpara->findnodes('<query>'));

我做了很多实验,但还没有弄清楚如何编写最后一个查询。我能找到的最接近有效的解决方案是:

$firstpara->findnodes('following-sibling::*[position() < $EXPR]');

...其中$EXPR 是一些表达式,它返回标签不是pulolblockquote 的下一个兄弟的位置,但我无法确定这样的表达式是否可以在 XPath 中表达。

有什么方法可以做我在 XPath 中描述的事情吗?

例子:

假设我的文档如下所示:

<h1>Header</h1>
<p>Paragraph 1</p>
<p id="first">Paragraph 2</p>
<p>Paragraph 3</p>
<ul><li>Item 1</li><li>Item 2</li></ul>
<p>Paragraph 4</p>
<hr>
<p>Paragraph 5</p>
<blockquote>Blockquote 1</blockquote>
...

我引用了 ID 为 first&lt;p&gt; 元素。我正在寻找一个 XPath 表达式,使用该 first 元素作为内容节点,这将为我提供以下兄弟姐妹 Paragraph 3、无序列表和 Paragraph 4&lt;hr&gt; 元素不在我想要的元素中(&lt;p&gt;&lt;ul&gt;&lt;ol&gt;&lt;blockquote&gt;),因此该元素和之后的所有同级元素不应成为返回节点集的一部分。

【问题讨论】:

  • 冗长而令人困惑。请提供一个简单的 XML 作为示例,并指明您要选择的确切节点——解释每个节点必须满足的规则。
  • “以下所有兄弟元素,直到但不包括第一个不是 p、ul、ol 或 blockquote 元素的元素”冗长且令人困惑?
  • 请编辑问题——没有多少人会阅读评论。

标签: xpath


【解决方案1】:

正如 OP 解释的那样,他想要:

以下所有同级元素,直到但不包括 第一个不是 p、ul、ol 或 blockquote 元素的元素

我。 XPath 1.0 解决方案:

需要的节点是两个节点集的交集

  1. 所有跟在pid 且值为'first' 的兄弟姐妹之后的元素。

  2. hr 的兄弟姐妹之前的所有元素。

为了在 XPath 1.0 中找到这一点,我们使用 Kayessian 公式计算节点集交集

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

上述 XPath 表达式选择同时属于节点集 $ns1 到节点集 $ns2 的所有节点。

$vP1 定义为/*/p[@id='first']

$vFirstNotInRange 是:

   $vP1/following-sibling::*
    [not(self::p or self::ul
        or self::ol or self::blockquote)
    ] [1]

这将选择第一个不需要的节点(在本例中为 hr),或者更准确地说:第一个元素是 $vP1 的后续兄弟并且不是 pul、@ 987654336@ 或blockquote

那么我们想要相交的两个节点集都是$vP1的所有后续兄弟和$vFirstNotInRange的所有前面的兄弟:

让我们用$vFollowingP1 表示第一个节点集——这是:

$vP1/following-sibling::*

让我们用$vPreceedingNotInRange 表示第二个节点集——这是:

$vFirstNotInRange/preceding-sibling::*

最后,我们将 Kayessina 公式中的$ns1 替换为$vPreceedingNotInRange,将$ns2 替换为$vFollowingP1。这些替换的结果正是选择了想要的节点:

$vPreceedingNotInRange
         [count(.|$vFollowingP1)
         =
          count($vFollowingP1)
         ]

如果我们替换所有变量,直到我们得到一个不包含任何变量的表达式,我们得到:

   /*/p[@id='first']/following-sibling::*
     [not(self::p or self::ul
         or self::ol or self::blockquote
          )
     ] [1]
        /preceding-sibling::*
          [count(.| /*/p[@id='first']/following-sibling::*)
          =
           count(/*/p[@id='first']/following-sibling::*)
          ]

这个表达式准确地选择了想要的节点。

这是一个基于 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:variable name="vP1" select="/*/p[@id='first']"/>

 <xsl:variable name="vFirstNotInRange" select=
  "$vP1/following-sibling::*
    [not(self::p or self::ul
        or self::ol or self::blockquote)
    ] [1]"/>

 <xsl:variable name="vFollowingP1"
      select="$vP1/following-sibling::*"/>

 <xsl:variable name="vPreceedingNotInRange"
      select="$vFirstNotInRange/preceding-sibling::*"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "$vPreceedingNotInRange
   [count(.|$vFollowingP1)
   =
    count($vFollowingP1)
   ]"/>
================

  <xsl:copy-of select=
  "/*/p[@id='first']/following-sibling::*
     [not(self::p or self::ul
         or self::ol or self::blockquote
          )
     ] [1]
        /preceding-sibling::*
          [count(.| /*/p[@id='first']/following-sibling::*)
          =
           count(/*/p[@id='first']/following-sibling::*)
          ]

  "/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时(提供的非格式良好的 XML 片段 -- 已更正并包装以使其格式良好):

<html>
    <h1>Header</h1>
    <p>Paragraph 1</p>
    <p id="first">Paragraph 2</p>
    <p>Paragraph 3</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
    </ul>
    <p>Paragraph 4</p>
    <hr/>
    <p>Paragraph 5</p>
    <blockquote>Blockquote 1</blockquote>
</html>

计算两个 XPath 表达式(一个带有变量,一个带有所有变量替换)并输出所需的正确选定节点

<p>Paragraph 3</p>
<ul>
   <li>Item 1</li>
   <li>Item 2</li>
</ul>
<p>Paragraph 4</p>
================

  <p>Paragraph 3</p>
<ul>
   <li>Item 1</li>
   <li>Item 2</li>
</ul>
<p>Paragraph 4</p>

二。 XPath 2.0 解决方案

$vFirstNotInRange/preceding-sibling::*
                              [. >> $vP1]

这会选择 $vFirstNotInRange 的任何前面的同级,也就是在 $vP1 之后,并选择相同的想要的节点:

<p>Paragraph 3</p>
<ul>
   <li>Item 1</li>
   <li>Item 2</li>
</ul>
<p>Paragraph 4</p>

说明:这里我们使用XPath 2.0“跟随”运算符&gt;&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-25
    • 2021-09-19
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2018-07-22
    • 1970-01-01
    相关资源
    最近更新 更多