【问题标题】:Perl LibXML findvalues(...) concatenates valuesPerl LibXML findvalues(...) 连接值
【发布时间】:2020-07-06 04:04:52
【问题描述】:

我正在尝试使用 LibXML 从 XML 文件中提取节点值。当我调用findvalue 时,所有相同元素类型的节点都被连接起来。我对使用 LibXML 完全陌生,而且我对 Perl 不是最敏感的。不幸的是,xml 不是最好的。如何提取单个节点?

下面是示例 XML 和输出。 XML 是来自 iTunes 库导出的 sn-p。

<playlists>
    <dict>
        <key>Name</key><string>Yes - Tales From Topographic Oceans</string>
        <key>Description</key><string></string>
        <key>Playlist ID</key><integer>67312</integer> 
        <key>Playlist Persistent ID</key><string>F28F195257143396</string> 
        <key>All Items</key><true/> 
        <key>Playlist Items</key> 
        <array> 
            <dict>
                <key>Track ID</key><integer>25912</integer>
            </dict>
            <dict>
                <key>Track ID</key><integer>25914</integer>
            </dict>
            <dict>
                <key>Track ID</key><integer>25916</integer>
            </dict>
            <dict>
                <key>Track ID</key><integer>25918</integer>
            </dict>
        </array>
    </dict>
    <dict>
        <key>Name</key><string>Yes - Yessongs</string>
            <key>Description</key><string>Live Album</string>
            <key>Playlist ID</key><integer>67319</integer>
            <key>Playlist Persistent ID</key><string>405B144877D8B8E4</string>
            <key>All Items</key><true/>
            <key>Playlist Items</key>
            <array>
                <dict>
                    <key>Track ID</key><integer>25920</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>25922</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>25924</integer>
            </dict>

                <dict>
                    <key>Track ID</key><integer>25926</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>25928</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>25930</integer>
                </dict>
            </array>
    </dict> 
</playlists>

my $dom = XML::LibXML->load_xml(location => $playlistxml);
foreach my $title ($dom->findnodes('//playlists/dict')) {
    my $nodestring = $title->findvalue('./string');
    print $nodestring, "\n";
    foreach my $tracks ($title->findnodes('//playlists/dict/array')) {
        my @trackid = $tracks->findvalue('./dict/integer');
        print @trackid, "\n";
    }
}

这是生成的输出:

Yes - Tales From Topographic OceansF28F195257143396
25912259142591625918
259202592225924259262592825930
Yes - YessongsLive Album405B144877D8B8E4
25912259142591625918
259202592225924259262592825930

期望的输出:

Yes - Tales From Topographic Oceans
25912
25914
25916
25918

Yes - YessongsLive Album
25920
25922
25924
25926
25928
25930

任何帮助将不胜感激

【问题讨论】:

    标签: xml perl libxml2


    【解决方案1】:

    按如下方式更改 XPath:

    //playlists/dict        →  /playlists/dict
    ./string                →  key[text()="Name"]/following-sibling::*[1]
    //playlists/dict/array  →  key[text()="Playlist Items"]/following-sibling::*[1]/*
    ./dict/integer          →  key[text()="Track ID"]/following-sibling::*[1]
    

    是的,那些 XPath 非常混乱,但那是因为我们正在处理一个可怕的架构。

    固定:

    use strict;
    use warnings;
    use feature qw( say );
    
    use XML::LibXML qw( );
    
    my $doc = XML::LibXML->load_xml( location => $ARGV[0] );
    
    my @playlist_nodes = $doc->findnodes('/playlists/dict');
    for my $playlist_idx (0..$#$playlist_nodes) {
       my $playlist_node = $playlist_nodes->[$playlist_idx];
    
       say "" if $playlist_idx;
    
       my $name = $playlist_node->findvalue('key[text()="Name"]/following-sibling::*[1]');
       say $name;
    
       for my $track_node ($playlist_node->findnodes('key[text()="Playlist Items"]/following-sibling::*[1]/*')) {
          my $id = $track_node->findvalue('key[text()="Track ID"]/following-sibling::*[1]');
          say $id;
       }
    }
    

    在上面,我提到所使用的架构很糟糕。设计 XML 模式的人被告知要使用 XML,但显然不理解 XML。即使模式对 JSON 等任意数据结构进行编码也是很糟糕的。 (This 会更好。)设计它的人只是为了在使用之前将数据转换为不同的格式。以下是这样做的:

    use strict;
    use warnings;
    use feature qw( say state );
    
    use Carp              qw( croak );
    use Types::Serialiser qw( );
    use XML::LibXML       qw( );
    
    
    sub qname {
       my ($node) = @_;
       my $ns   = $node->namespaceURI();
       my $name = $node->nodeName();
       return defined($ns) ? "{$ns}$name" : $name;
    }
    
    sub deserialize_array {
       my ($array_node) = @_;
       return [ map { deserialize_value($_) } $array_node->findnodes("*") ];
    }
    
    sub deserialize_dict {
       my ($dict_node) = @_;
    
       my $dict = {};
       my @children = $dict_node->findnodes("*");
       while (@children) {
          my $key_node = shift(@children);
          qname($key_node) eq "key"
             or croak("Expected key");
    
          my $val_node = shift(@children)
             or croak("Expected value");
    
          my $key = $key_node->textContent();
          my $val = deserialize_value($val_node);
          $dict->{$key} = $val;
       }
    
       return $dict;
    }
    
    sub deserialize_value {
       my ($val_node) = @_;
    
       state $deserializers = {
          string  => sub { $_[0]->textContent() },
          integer => sub { 0 + $_[0]->textContent() },
          true    => sub { $Types::Serialiser::true },
          false   => sub { $Types::Serialiser::false },
          array   => \&deserialize_array,
          dict    => \&deserialize_dict,
       };
    
       my $val_type = qname($val_node);
       my $deserializer = $deserializers->{$val_type}
          or croak("Unrecognized value type \"$val_type\"");
    
       return $deserializer->($val_node);
    }
    
    sub deserialize_doc {
       my ($doc) = @_;
       return deserialize_array($doc->documentElement());
    }
    

    有了上述,解决方案变成了以下:

    my $doc = XML::LibXML->load_xml( location => $ARGV[0] );
    my $playlists = deserialize_doc($doc);
    
    for my $playlist_idx (0..$#$playlists) {
        my $playlist = $playlists->[$playlist_idx];
    
        say "" if $playlist_idx;
    
        my $name = $playlist->{"Name"};
        say $name;
    
        for my $track (@{ $playlist->{"Playlist Items"} }) {
           my $id = $track->{"Track ID"};
           say $id;
        }
    }
    

    【讨论】:

    • 非常感谢。你是 Perl 的白巫师!我会使用你的代码,但我需要一段时间才能理解它。一个问题,指定模块时 qw(...) 是什么意思?
    • 它说不要导入任何东西。我认为明确列出导入而不是导入默认导出是一种好习惯。它避免了意外,但更重要的是,它使维护程序变得更容易,因为您可以轻松地确定代码使用什么提供了功能。
    • [position()=1] 可以缩短为[1]
    • @nwellnhof,呵呵。固定。
    【解决方案2】:

    正如其他发帖者所指出的那样,您的输入数据不容易处理。

    使用提供的输入数据示例,您的代码可能如下所示。

    use strict;
    use warnings;
    use feature 'say';
    
    use XML::LibXML;
    
    my $playlistxml = 'playlist.xml';
    
    my $dom = XML::LibXML->load_xml(location => $playlistxml);
    
    foreach my $title ($dom->findnodes('//playlist')) {
        say 'Title: ', $title->findvalue('./title');
        my $tracks = join "\n", map {
            $_->to_literal();
        } $title->findnodes('./tracks/track/@id');
        say $tracks;
        say '';
    }
    

    输入数据'playlist.xml'示例

    <playlists>
        <playlist id="67312">
            <title>Yes - Tales From Topographic Oceans</title>
            <persistent_id>F28F195257143396</persistent_id> 
            <tracks> 
                <track id="25912" />
                <track id="25914" />
                <track id="25916" />
                <track id="25918" />
            </tracks>
        </playlist>
        <playlist id="67319">
            <title>Yes - Yessongs</title>
            <description>Live Album</description>
            <persistent_id>405B144877D8B8E4</persistent_id>
            <tracks>
                <track id="25920" />
                <track id="25922" />
                <track id="25924" />
                <track id="25926" />
                <track id="25928" />
                <track id="25930" />
            </tracks>
        </playlist> 
    </playlists>
    

    输出

    Title: Yes - Tales From Topographic Oceans
    25912
    25914
    25916
    25918
    
    Title: Yes - Yessongs
    25920
    25922
    25924
    25926
    25928
    25930
    

    【讨论】:

    • 是的,您建议的架构更好。 (您甚至可以拥有比 OP 中用于通用数据结构的架构更好的架构。)OP 正在处理来自第三方的数据——它是“iTunes 库导出”——因此他们更改架构不是一个选项。这对 OP 没有任何帮助
    • 我不知道 OP 的架构是从哪里来的,而且绝对可以在很多方面进行改进。如果 OP 的播放列表只是单个文件,那么可能不值得编写一些可以将播放列表转换为更可用的 xml 格式的服务。如果在全球范围内有大量 OP 格式的播放列表,那么一些 Web 服务对进行转换是有意义的。客户可以上传 XML 播放列表,转换会即时进行,客户会返回已转换的 XML 播放列表版本。
    • 提供了播放列表的代码和示例,以便 OP 了解播放列表的外观,并避免为播放列表选择错误的 XML 架构的可能陷阱。
    • 非常感谢您的帮助。 xml 文件由 iTunes 生成。你会认为苹果本可以做得对。
    猜你喜欢
    • 1970-01-01
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多