Измените 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;
}
}