Perl XML获取同一父级的子级详细信息

时间:2019-03-21 23:33:58

标签: xml perl

我有以下XML,如果正则表达式与另一个子元素匹配,我希望从同一个父元素获得子元素。问题是,XML到处都有重复的命名标签,因此很难完成Movie-> Year,因为电影元素很多。

例如

数据:

<movie>
    <title>Titanic</title>
    <year>1997</year>
    <genre>Drama</genre>
</movie>
<movie>
    <title>Moneyball</title>
    <year>2011</year>
    <genre>Sport/Drama</genre>
</movie>
<movie>
    <title>Fight Club</title>
    <year>1999</year>
    <genre>Drama/Action</genre>
</movie>

Perl

 my $simple = XML::Simple->new( );
 my $tree = $simple->XMLin($_);
 my $movie = $tree->{movie}{title};

if($movie =~ /Titanic/)
{
    # $movie -> year ???
    # desired output = 1997
}

使用XML :: Simple最简单的方法是什么?

4 个答案:

答案 0 :(得分:4)

使用XML :: Simple没有简单的方法,因为it's the hardest XML parser to use。它自己的文档警告不要使用它。 (“强烈建议不要在新代码中使用此模块。”)


您所拥有的没有有效的XML,因此我们首先必须使其成为有效的XML

use XML::LibXML qw( );

my $parser = XML::LibXML->new();
my $doc = $parser->parse_string("<movies>$not_quite_xml</movies>");

my ($movie_node) = $doc->findnodes('/movies/movie[title/text()="Titanic"]')
   or die("Titanic not found\n");

my $year = $movie_node->findvalue('year/text()');
...

答案 1 :(得分:3)

我希望已经表明,不应该使用XML::Simple,因为它早就被更好的模块所取代,而"strongly discouraged"也被它自己的作者反对,也就在几年前。

此示例显示了一种使用父节点来查询兄弟姐妹的方法,这是专门要求的。 (我用<document>根节点填充了示例,以便获得格式正确的XML。)ikegami的回答显示了如何更直接地执行您似乎需要的操作。

如果您有理由扫描<title>节点(也许正在寻找各种标题),则可以通过以下方式找到它们的同级<year>节点:

use strict;
use warnings;
use feature 'say';    

use XML::LibXML;    

my $file = shift || die "Usage: $0 filename\n";

my $doc = XML::LibXML->load_xml(location => $file, no_blanks => 1); 

my $xpath = '/document/movie/title';

foreach my $node ($doc->findnodes($xpath)) {
    if ($node->to_literal =~ /(Titanic)/) {
        say "Title: $1";
        foreach my $yr ($node->parentNode->findnodes('./year')) {
            say "\tyear: ", $yr->to_literal;
        }   
    }   
}

如果在一个<year>节点下始终只有一个<movie>节点,则可以通过快捷方式findvalue来简化,将

$node->parentNode->findnodes

这里我们直接获取文本,因此也不需要foreach my $node ($doc->findnodes($xpath)) { if ($node->to_literal =~ /(Titanic)/) { say "Title: $1"; say "\tyear: ", $node->parentNode->findvalue('./year'); } }

XML::LibXML::Node中还有许多方法,这是用于派生其他特定类的节点的基类。这里有趣的一个可能是->to_literal,作为一种在nextSibling中细读有关标题的其他信息的方法。

请注意,这个完整且功能齐全的库提供了更多使用XML的工具。首先,将详细信息(如属性)添加到源文件中将允许利用库的其他优势。

文档分布在许多页面上。有关相关文档的链接摘要,请参见this post<movie>的作者还有tutorial for XML::LibXML

答案 2 :(得分:2)

还有另一种方法,这次用Mojo::DOM。除了其他解决方案(除了XML :: Simple之外),没有什么可推荐的。

这将添加一个根元素,然后使用CSS选择器来获取标题:

use utf8;
use strict;
use warnings;

my $xml = <<'HERE';
<movies>
<movie>
    <title>Titanic</title>
    <year>1997</year>
    <genre>Drama</genre>
</movie>
<movie>
    <title>Moneyball</title>
    <year>2011</year>
    <genre>Sport/Drama</genre>
</movie>
<movie>
    <title>Fight Club</title>
    <year>1999</year>
    <genre>Drama/Action</genre>
</movie>
</movies>
HERE

use Mojo::DOM;

my @movies = Mojo::DOM
    ->new( $xml )
    ->find( 'movies title' )
    ->map( 'text' )
    ->each;

say join "\n", @movies;

答案 3 :(得分:1)

您还可以从Perl中调用xmlstarlet之类的命令行工具来快速提取所需信息。

例如,如果您的XML文档的片段存储在/tmp/foo.xml,则以下shell脚本会将其转换为表格形式,通过一次读取一行即可在Perl中更容易处理。 / p>

{ echo '<movies>' ; cat /tmp/foo.xml ; echo '</movies>'; } \
    | xmlstarlet sel -T -t -m '//movie' -v "concat(title, '|', year)" -n

打印

Titanic|1997
Moneyball|2011
Fight Club|1999

这种将xml文档转换为更方便的格式的特殊方法对于电影标题中的换行符或|来说并不可靠,并且需要外部工具,但这很容易。