xml perl xpath xml-libxml

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