Select 仅第一个元素 - 条件使用 XML::Twig
Select the 1st element only - with condition using XML::Twig
有这个代码:
#!/usr/bin/env perl
use 5.014;
use warnings;
use XML::Twig;
my $twig = XML::Twig->parse( \*DATA );
$twig->set_pretty_print('indented_a');
# 1st search
# this prints OK the all <files> nodes where the <type> == 'release'
$_->print for ( $twig->findnodes( '//type[string()="release"]/..' ) );
# 2nd search
# try to get first matched only
my $latest = $twig->findnodes( '(//type[string()="release"])[1]/..' );
$latest->print;
__DATA__
<root>
<files>
<type>beta</type>
<ver>3.0</ver>
</files>
<files>
<type>alpha</type>
<ver>3.0</ver>
</files>
<files>
<type>release</type>
<ver>2.0</ver>
</files>
<files>
<type>release</type>
<ver>1.0</ver>
</files>
</root>
以上打印
<files>
<type>release</type>
<ver>2.0</ver>
</files>
<files>
<type>release</type>
<ver>1.0</ver>
</files>
error in xpath expression (//type[string()="release"])[1]/.. around (//type[string()="release"])[1]/.. at /opt/anyenv/envs/plenv/versions/5.24.0/lib/perl5/site_perl/5.24.0/XML/Twig.pm line 3648.
第二次搜索的结果
<files>
<type>release</type>
<ver>2.0</ver>
</files>
例如<type> eq 'release'
.
所在的第一个 <files>
节点
根据 this answer 使用的 XPath 表达式 (//type[string()="release"])[1]/..'
应该可以工作,但似乎我又错过了一些重要的东西。
有人能帮忙吗?
XML::Twig 不支持整个 XPath。该表达式在 XML::LibXML.
中正常工作
您可以自己在 Perl 中遍历该结构:
my $latest = ($twig->findnodes('//type[string()="release"]'))[0]->parent;
XML::Twig 不支持所有的 XPath,但 XML::Twig::XPath 支持。
所以 use XML::Twig::XPath;
,然后是 my $twig = XML::Twig::XPath->parse(...
瞧......你现在可以开始修复 $latest=...
行,它应该是:
my $latest = ($twig->findnodes( '(//type[string()="release"])[1]/..' ))[0];
($latest 的方式是 XML::XPathEngine::NodeSet
,您需要获取该集合的第一个元素)。
XML::Twig
不支持完整的 XPath 语法。 get_xpath
方法(与 findnodes
相同)的文档说这个
A subset of the XPATH abbreviated syntax is covered:
tag
tag[1] (or any other positive number)
tag[last()]
tag[@att] (the attribute exists for the element)
tag[@att="val"]
tag[@att=~ /regexp/]
tag[att1="val1" and att2="val2"]
tag[att1="val1" or att2="val2"]
tag[string()="toto"] (returns tag elements which text (as per the text method)
is toto)
tag[string()=~/regexp/] (returns tag elements which text (as per the text
method) matches regexp)
expressions can start with / (search starts at the document root)
expressions can start with . (search starts at the current element)
// can be used to get all descendants instead of just direct children
* matches any tag
因此不支持括号内的子表达式,您只能指定一个谓词
同样重要的是,在标量上下文中,findnodes
只会 return 找到的节点数。您必须在列表上下文中使用它来检索节点本身,这意味着仅查找第一个匹配元素的更简单方法是编写
my ($latest) = $twig->findnodes( '//type[string()="release"]/..' );
效果很好
如果您确实需要 XPath 的全部功能,那么您可以使用 XML::Twig::XPath
instead. This module uses either XML::XPath
or the excellent XML::XPathEngine
通过重载 findnodes
来提供完整的 XPath 语法。 (其他方法 get_xpath
和 find_nodes
继续使用减少的 XML::Twig
变体。)
findnodes
在标量上下文中现在 return 是一个 XML::XPathEngine::NodeSet
对象,它的数组索引已过载。所以你可以写
my $latest = $twig->findnodes( '//type[string()="release"]/..' );
$latest->[0]->print;
或者只是
my ($latest) = $twig->findnodes( '//type[string()="release"]/..' );
同上。
最后,我更希望看到 /root/files[type[string()="release"]]
而不是尾随 parent::node()
,但这纯属个人观点
有这个代码:
#!/usr/bin/env perl
use 5.014;
use warnings;
use XML::Twig;
my $twig = XML::Twig->parse( \*DATA );
$twig->set_pretty_print('indented_a');
# 1st search
# this prints OK the all <files> nodes where the <type> == 'release'
$_->print for ( $twig->findnodes( '//type[string()="release"]/..' ) );
# 2nd search
# try to get first matched only
my $latest = $twig->findnodes( '(//type[string()="release"])[1]/..' );
$latest->print;
__DATA__
<root>
<files>
<type>beta</type>
<ver>3.0</ver>
</files>
<files>
<type>alpha</type>
<ver>3.0</ver>
</files>
<files>
<type>release</type>
<ver>2.0</ver>
</files>
<files>
<type>release</type>
<ver>1.0</ver>
</files>
</root>
以上打印
<files>
<type>release</type>
<ver>2.0</ver>
</files>
<files>
<type>release</type>
<ver>1.0</ver>
</files>
error in xpath expression (//type[string()="release"])[1]/.. around (//type[string()="release"])[1]/.. at /opt/anyenv/envs/plenv/versions/5.24.0/lib/perl5/site_perl/5.24.0/XML/Twig.pm line 3648.
第二次搜索的结果
<files>
<type>release</type>
<ver>2.0</ver>
</files>
例如<type> eq 'release'
.
<files>
节点
根据 this answer 使用的 XPath 表达式 (//type[string()="release"])[1]/..'
应该可以工作,但似乎我又错过了一些重要的东西。
有人能帮忙吗?
XML::Twig 不支持整个 XPath。该表达式在 XML::LibXML.
中正常工作您可以自己在 Perl 中遍历该结构:
my $latest = ($twig->findnodes('//type[string()="release"]'))[0]->parent;
XML::Twig 不支持所有的 XPath,但 XML::Twig::XPath 支持。
所以 use XML::Twig::XPath;
,然后是 my $twig = XML::Twig::XPath->parse(...
瞧......你现在可以开始修复 $latest=...
行,它应该是:
my $latest = ($twig->findnodes( '(//type[string()="release"])[1]/..' ))[0];
($latest 的方式是 XML::XPathEngine::NodeSet
,您需要获取该集合的第一个元素)。
XML::Twig
不支持完整的 XPath 语法。 get_xpath
方法(与 findnodes
相同)的文档说这个
A subset of the XPATH abbreviated syntax is covered:
tag tag[1] (or any other positive number) tag[last()] tag[@att] (the attribute exists for the element) tag[@att="val"] tag[@att=~ /regexp/] tag[att1="val1" and att2="val2"] tag[att1="val1" or att2="val2"] tag[string()="toto"] (returns tag elements which text (as per the text method) is toto) tag[string()=~/regexp/] (returns tag elements which text (as per the text method) matches regexp) expressions can start with / (search starts at the document root) expressions can start with . (search starts at the current element) // can be used to get all descendants instead of just direct children * matches any tag
因此不支持括号内的子表达式,您只能指定一个谓词
同样重要的是,在标量上下文中,findnodes
只会 return 找到的节点数。您必须在列表上下文中使用它来检索节点本身,这意味着仅查找第一个匹配元素的更简单方法是编写
my ($latest) = $twig->findnodes( '//type[string()="release"]/..' );
效果很好
如果您确实需要 XPath 的全部功能,那么您可以使用 XML::Twig::XPath
instead. This module uses either XML::XPath
or the excellent XML::XPathEngine
通过重载 findnodes
来提供完整的 XPath 语法。 (其他方法 get_xpath
和 find_nodes
继续使用减少的 XML::Twig
变体。)
findnodes
在标量上下文中现在 return 是一个 XML::XPathEngine::NodeSet
对象,它的数组索引已过载。所以你可以写
my $latest = $twig->findnodes( '//type[string()="release"]/..' );
$latest->[0]->print;
或者只是
my ($latest) = $twig->findnodes( '//type[string()="release"]/..' );
同上。
最后,我更希望看到 /root/files[type[string()="release"]]
而不是尾随 parent::node()
,但这纯属个人观点