我一直在编写一些从网页中提取主要文本内容的代码。一个有用的策略是找到内容的第一段,然后选择以下所有兄弟元素,但不包括第一段不是p
,ul
,{ {1}}或ol
元素。在Perl中,代码看起来像这样:
blockquote
这不是太糟糕,但是能够仅使用XPath获取我想要的节点会很酷,所以我可以写下这样的东西:
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;
}
我做了很多实验,但还没弄清楚如何编写最后一个查询。最接近我能够找到的有效解决方案的是:
my ($firstpara) = $document->findnodes('//p[whatever]');
my @content = ($firstpara, $firstpara->findnodes('<query>'));
...其中$firstpara->findnodes('following-sibling::*[position() < $EXPR]');
是一个表达式,用于返回其标记不是$EXPR
,p
,ul
或{{1}的下一个兄弟的位置但是,如果在XPath中可以表达这样的表达式,我就无法解决。
有没有办法做我在XPath中描述的内容?
示例:
假设我的文档如下:
ol
我引用了标识为blockquote
的{{1}}元素。我正在使用该<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>
...
元素作为内容节点的XPath表达式,它将为我提供以下兄弟<p>
,无序列表和first
。 first
元素不属于我想要的元素(Paragraph 3
,Paragraph 4
,<hr>
和<p>
),因此该元素和之后的所有兄弟元素都不应该成为返回节点集的一部分。
答案 0 :(得分:1)
正如OP解释的那样,他想要:
所有以下兄弟元素,但不包括 第一个不是p,ul,ol或blockquote元素
<强>予。 XPath 1.0解决方案:
需要的节点是两个节点集的交集:
p
的{{1}}兄弟姐妹的所有元素,其中id
的值为'first'
。
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
的后续兄弟,而不是p
, ul
,ol
或blockquote
。
然后我们想要交叉的两个节点集都是$vP1
的所有兄弟姐妹以及$vFirstNotInRange
的所有兄弟姐妹:
让我们用$vFollowingP1
表示第一个节点集 - 这是:
$vP1/following-sibling::*
让我们用$vPreceedingNotInRange
表示第二个节点集 - 这是:
$vFirstNotInRange/preceding-sibling::*
最后,我们将凯西娜公式$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>
<强> II。 XPath 2.0解决方案:
$vFirstNotInRange/preceding-sibling::*
[. >> $vP1]
这将选择跟随$vFirstNotInRange
的{{1}}的任何前一个兄弟并选择相同的所需节点:
$vP1
解释:这里我们使用XPath 2.0“跟随”运算符<p>Paragraph 3</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Paragraph 4</p>
。