仅选择第一个元素 - 条件使用 XML::Twig

Posted

技术标签:

【中文标题】仅选择第一个元素 - 条件使用 XML::Twig【英文标题】:Select the 1st element only - with condition using XML::Twig 【发布时间】:2016-09-15 03:09:28 【问题描述】:

拥有此代码:

#!/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>

例如&lt;type&gt; eq 'release' 所在的第一个 &lt;files&gt; 节点。

根据this answer 使用的XPath 表达式(//type[string()="release"])[1]/..' 应该可以工作,但似乎我又错过了一些重要的事情。

有人可以帮忙吗?

【问题讨论】:

【参考方案1】:

XML::Twig 不支持完整的 XPath 语法。 get_xpath 方法(与 findnodes 相同)的文档说明了这一点

涵盖了 XPATH 缩写语法的一个子集:

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 只会返回找到的节点数的计数。您必须在列表上下文中使用它来检索节点本身,这意味着查找第一个匹配元素的更简单方法是编写

my ($latest) = $twig->findnodes( '//type[string()="release"]/..' );

效果很好

如果您真的需要 XPath 的全部功能,那么您可以改用XML::Twig::XPath。该模块使用XML::XPath 或出色的XML::XPathEngine 通过重载findnodes 来提供完整的XPath 语法。 (其他方法get_xpathfind_nodes 继续使用减少的XML::Twig 变体。)

findnodes 在标量上下文中现在返回一个数组索引重载的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(),但这纯粹是个人的

【讨论】:

是的!使用XML::Twig::XPathmy ($latest) = $twig-&gt;findnodes( '/root/files[type[string()="release"]]' ); 可以解决我的需求。谢谢! ;) @cajwine:我希望我明确表示,如果你只使用一个谓词,比如my ($latest) = $twig-&gt;findnodes( '/root/files/type[string()="release"]/..' ),那么标准的XML::Twig 可以正常工作 是的,两者都试过了。为了使用'/root/files[type[string()="release"]]'(来自您的最后一条语句),我需要 XPath。对于/root/files/type[string()="release"]/..,简单的XML::Twig 就足够了。精彩的答案! ;) @cajwine:我很高兴能提供帮助。就像我说的,这似乎是对谓词的限制,但这只是一个有根据的猜测。 mirod 也发布了答案,他是该模块的作者,所以你可能想问他一些问题【参考方案2】:

XML::Twig 不支持所有的 XPath,但 XML::Twig::XPath 支持。

所以use XML::Twig::XPath;,然后是my $twig = XML::Twig::XPath-&gt;parse(...,然后瞧……您现在可以修复$latest=... 行,它应该是:

my $latest = ($twig->findnodes( '(//type[string()="release"])[1]/..' ))[0];

(你拥有它的方式是 $latest 是 XML::XPathEngine::NodeSet,你需要获取该集合的第一个元素)。

【讨论】:

这有点离题了,但是如果XML::Twig::XPath 有一种方法可以指定在它们都安装的情况下使用哪个帮助模块,就像Text::CSV 一样。或者至少是一种发现选择了哪一个的方法。最初可能只是将my $XPATH 更改为our $XPATH 的问题? 没问题,只是对漂亮的XML::Twig 包表示“谢谢”! :) @borodin XML::XPathEngine 如果存在则使用。 XML::XPath 只是一个选项,因为在我将 XPath 部分分叉以创建 XML::XPathEngine 之前,它是第一个使用的选项。 @mirod:我明白了。谢谢。但是,除非您希望弃用 XML::Path,否则能够获得这些信息仍然是件好事。到目前为止我一直在使用ref $twig-&gt;twig_xp,它不是很干净【参考方案3】:

XML::Twig 不支持整个 XPath。该表达式在XML::LibXML 中正常工作。

您可以在 Perl 中自己浏览该结构:

my $latest = ($twig->findnodes('//type[string()="release"]'))[0]->parent;

【讨论】:

perl-walking - 是的 - 但如果这里不是 release 类型(例如只是 beta),它会显示 Can't call method "parent" on an undefined value - 所以需要测试返回值。因此我尝试使用(扩展的)Xpath。谢谢你。 :)

以上是关于仅选择第一个元素 - 条件使用 XML::Twig的主要内容,如果未能解决你的问题,请参考以下文章

“条件的长度 > 1,仅使用第一个元素”错误

错误:“条件长度 > 1,仅使用第一个元素”

使用 :contains 或类似方法仅选择第一个元素

如何仅选择满足条件的第一行?

jQuery选择器删除满足两个条件的表行

jQuery ID 选择器仅适用于第一个元素