Perl Lib XML findvalues ​​(...) объединяет значения - PullRequest
3 голосов
/ 25 марта 2020

Я пытаюсь извлечь значения узлов из файла XML, используя Lib XML. Когда я вызываю findvalue, все узлы одного и того же типа элемента объединяются. Я абсолютно новичок в использовании Lib XML, и я не самый умный с Perl. К сожалению, xml не самый лучший. Как я могу извлечь отдельные узлы?

Ниже приведен пример XML и вывод. XML - это фрагмент из экспорта библиотеки iTunes.

<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

Любая помощь будет наиболее ценной

Ответы [ 2 ]

3 голосов
/ 25 марта 2020

Измените 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. ( Это было бы лучше.) Тот, кто его разработал, предназначался только для преобразования данных в другой формат перед использованием. Это делается следующим образом:

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;
    }
}
0 голосов
/ 27 марта 2020

Ваши входные данные не так легко обработать, как это было указано другими авторами.

Ваш код может быть следующим с предоставленным образцом входных данных.

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 '';
}

Образец плейлиста входных данных. 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
...