按如下方式更改 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 非常混乱,但那是因为我们正在处理一个可怕的模式。
Fixed:
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;
}
}