Perl XML:: LibXML $ node-> findnodes($ xpath) encuentra nodos que no deberían
xml-libxml (2)
Aquí hay un código con el que tengo problemas, proceso algunos XML y en un método en una clase OO extraigo un elemento de cada uno de los nodos que se repiten en el documento. Solo debería haber un elemento de este tipo en el subárbol para cada nodo, pero mi código obtiene todos los elementos como si estuviera operando en el documento como un todo.
Debido a que solo esperaba obtener un elemento de oleo, solo uso el elemento zeroth de una matriz, esto hace que mi función arroje el valor incorrecto (y es el mismo para todos los elementos del documento)
Aquí hay un código simplificado que ilustra el problema
$ cat t4.pl
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
my $xml = <<EndXML;
<Envelope>
<Body>
<Reply>
<List>
<Item>
<Id>8b9a</Id>
<Message>
<Response>
<Identifier>55D</Identifier>
</Response>
</Message>
</Item>
<Item>
<Id>5350</Id>
<Message>
<Response>
<Identifier>56D</Identifier>
</Response>
</Message>
</Item>
</List>
</Reply>
</Body>
</Envelope>
EndXML
my $foo = Foo->new();
my $parser = XML::LibXML->new();
my $doc = $parser->parse_string( $xml );
my @list = $doc->getElementsByTagName( ''Item'' );
for my $item ( @list ) {
my $id = get( $item, ''Id'' );
my @messages = $item->getElementsByLocalName( ''Message'' );
for my $message ( @messages ) {
my @children = $message->getChildNodes();
for my $child ( @children ) {
my $name = $child->nodeName;
if ( $name eq ''Response'' ) {
print "child is a Response/n";
$foo->do( $child, $id );
}
elsif ( $name eq ''text'' ) {
# ignore whitespace between elements
}
else {
print "child name is ''$name''/n";
}
} # child
} # Message
} # Item
# ..............................................
sub get {
my ( $node, $name ) = @_;
my $value = "(Element $name not found)";
my @targets = $node->getElementsByTagName( $name );
if ( @targets ) {
my $target = $targets[0];
$value = $target->textContent;
}
return $value;
}
# ..............................................
package Foo;
sub new {
my $self = {};
bless $self;
return $self;
}
sub do {
my $self = shift;
my ( $node, $id ) = @_;
print ''-'' x 70, "/n", '' '' x 12, $node->toString( 1 ), "/n", ''-'' x 70, "/n";
my @identifiers = $node->findnodes( ''//Identifier'' );
print "do() found ", scalar @identifiers, " Identifiers/n";
print "$id, ", $identifiers[0]->textContent, "/n/n";
}
Aquí está la salida
$ perl t4.pl
child is a Response
----------------------------------------------------------------------
<Response>
<Identifier>55D</Identifier>
</Response>
----------------------------------------------------------------------
do() found 2 Identifiers
8b9a, 55D
child is a Response
----------------------------------------------------------------------
<Response>
<Identifier>56D</Identifier>
</Response>
----------------------------------------------------------------------
do() found 2 Identifiers
5350, 55D
Yo estaba esperando
do() found 1 Identifiers
Estaba esperando que la última línea sea
5350, 56D
Estoy usando una versión anterior de XML :: LibXML debido a problemas con la plataforma.
P: ¿Existe el problema en versiones posteriores o estoy haciendo algo mal?
No tengo ningún comentario sobre la calidad del código, pero aprendí a usar XML :: DOM antes de usar XML :: LibXML. Tengo la tendencia a usar parte de la sintaxis DOM. He estado tratando de vencer este hábito fuera de mí :).
La razón por la que menciono esto es porque veo que ha usado el equivalente de -> elemento (0) para obtener la primera posición de una lista de nodos como lo haría en DOM.
XML :: LibXML admite el uso de -> item () pero desde cpan puedo ver que xpath crea listas de nodos comenzando en 1 no 0 como DOM. Estoy bastante seguro de que si dejas el código tal como está y buscas la posición de la primera fila, no la 0ª, obtendrás el resultado que deseas.
Lo que no está claro es por qué -> el ítem (0) te da el último resultado, como parece hacerlo, de mi prueba (¿es tal vez compensado por un valor de matriz para que de hecho hayas devuelto el valor de la matriz -1)?
De la documentación de XPath 1.0
// para selecciona todos los para descendientes de la raíz del documento
(énfasis mío) Entonces tu llamada
$node->findnodes( ''//Identifier'' )
ignora el nodo de contexto nodo $node
y busca todos los elementos Identifier
en cualquier parte del documento
Para obtener todos los descendientes Identifier
del nodo de contexto, debe agregar un punto, como este
$node->findnodes(''.//Identifier'');
pero como $node
es siempre un elemento de Response
y el Identifier
es un hijo directo de Response
, puedes escribir
$node->findnodes(''Identifier'');
Parece que te has atado un poco escribiendo esto. Sé que ha cortado el código como ejemplo, pero ¿ realmente necesita el paquete por separado? Se puede hacer mucho con la aplicación juiciosa de XPath.
El cambio más obvio es que no necesita pasar por todos los niños, simplemente puede seleccionar los que le interesan.
Este código refactorizado puede valer la pena leer
use strict;
use warnings;
use XML::LibXML;
my $parser = XML::LibXML->new;
my $doc = $parser->parse_fh(*DATA);
for my $item ( $doc->findnodes(''//Item'') ) {
print "/n";
my ($id) = $item->findvalue(''Id'');
printf "Item Id: %s/n", $item->findvalue(''Id'');
my @messages = $item->findnodes(''Message'');
for my $message (@messages) {
my ($response) = $message->findnodes(''Response'');
printf "Response Identifier: %s/n", $response->findvalue(''Identifier'');
}
}
__DATA__
<Envelope>
<Body>
<Reply>
<List>
<Item>
<Id>8b9a</Id>
<Message>
<Response>
<Identifier>55D</Identifier>
</Response>
</Message>
</Item>
<Item>
<Id>5350</Id>
<Message>
<Response>
<Identifier>56D</Identifier>
</Response>
</Message>
</Item>
</List>
</Reply>
</Body>
</Envelope>
salida
Item Id: 8b9a
Response Identifier: 55D
Item Id: 5350
Response Identifier: 56D