2010-11-03 9 views
6

Estoy intentando seleccionar un nodo usando una consulta XPath y no entiendo por qué XML :: LibXML no encuentra el nodo cuando tiene un atributo xmlns. Aquí hay un script para demostrar el problema:¿Por qué XML :: LibXML no encuentra nodos para esta consulta xpath al usar un espacio de nombres?

#!/usr/bin/perl 

use XML::LibXML; # 1.70 on libxml2 from libxml2-dev 2.6.16-7sarge1 (don't ask) 
use XML::XPath; # 1.13 
use strict; 
use warnings; 

use v5.8.4; # don't ask 

my ($xpath, $libxml, $use_namespace) = @ARGV; 

my $xml = sprintf(<<'END_XML', ($use_namespace ? 'xmlns="http://www.w3.org/2000/xmlns/"' : q{})); 
<?xml version="1.0" encoding="iso-8859-1"?> 
<RootElement> 
    <MyContainer %s> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 
</RootElement> 
END_XML 

my $xml_parser 
    = $libxml ? XML::LibXML->load_xml(string => $xml, keep_blanks => 1) 
    :   XML::XPath->new(xml => $xml); 

my $nodecount = 0; 
foreach my $node ($xml_parser->findnodes($xpath)) { 
    $nodecount ++; 
    print "--NODE $nodecount--\n"; #would use say on newer perl 
    print $node->toString($libxml && 1), "\n"; 
} 

unless ($nodecount) { 
    print "NO NODES FOUND\n"; 
} 

Este script le permite elegir entre el analizador XML :: LibXML y el XML :: XPath analizador. También le permite definir un atributo xmlns en el elemento MyContainer o dejarlo apagado según los argumentos que se pasen.

La expresión xpath que estoy usando es "RootElement/MyContainer". Cuando ejecuto la consulta utilizando el analizador XML :: LibXML sin el espacio de nombres se encuentra el nodo sin ningún problema:

ben[email protected]:~$ ROC/ECG/libxml_xpath.pl 'RootElement/MyContainer' libxml 
--NODE 1-- 
<MyContainer> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 

Sin embargo, cuando lo ejecuto con el espacio de nombres en su lugar se encuentra ningún nodo:

[email protected]:~$ ROC/ECG/libxml_xpath.pl 'RootElement/MyContainer' libxml use_namespace 
NO NODES FOUND 

Contraste esto con la salida cuando se utiliza el analizador XMLL :: XPath:

[email protected]:~$ ROC/ECG/libxml_xpath.pl 'RootElement/MyContainer' 0 # no namespace 
--NODE 1-- 
<MyContainer> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 
[email protected]:~$ ROC/ECG/libxml_xpath.pl 'RootElement/MyContainer' 0 1 # with namespace 
--NODE 1-- 
<MyContainer xmlns="http://www.w3.org/2000/xmlns/"> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 

¿Cuál de estas implementaciones del analizador lo está haciendo "derecho"? ¿Por qué XML :: LibXML lo trata de manera diferente cuando uso un espacio de nombres? ¿Qué puedo hacer para recuperar el nodo cuando el espacio de nombres está en su lugar?

+0

Buena pregunta, +1. Vea mi respuesta para una explicación y para dos posibles soluciones. –

+0

@ikegami, tiene que ser útil tanto para usuarios avanzados * como para principiantes. No deben desanimarse para hacer preguntas. –

Respuesta

14

Esta es una pregunta frecuente. XPath considera que cualquier nombre no prefijado en una expresión pertenece a "sin espacio de nombres".

Entonces, la expresión:

RootElement/MyContainer 

selecciona todos MyContainer elementos que pertenecen a "ningún espacio de nombres" y son los niños de todas RootElement elementos que pertenecen a "ningún espacio de nombres" y son elementos secundarios del contexto (actual nodo). Sin embargo, no hay ningún elemento en todo el documento que pertenezca a "sin espacio de nombres": todos los elementos pertenecen al espacio de nombres predeterminado.

Esto explica el resultado que está obteniendo. XML :: LibXML es a la derecha.

La solución común es que la API del lenguaje de alojamiento permite que un prefijo específico se vincule con el espacio de nombres "registrando" un espacio de nombres. A continuación, se puede utilizar una expresión como:

x:RootElement/x:MyContainer 

donde x es el prefijo con el que el espacio de nombre ha sido registrado.

En las raras ocasiones en las que el idioma de alojamiento no ofrece espacios de nombres que registran, utilizar la siguiente expresión:

*[name()='RootElement']/*[name()='MyContainer'] 
+0

Con XML :: LibXML, registra espacios de nombres utilizando XML :: LibXML :: XPathContext. Esto está documentado en 'findnodes'. – ikegami

+0

@ikegami, no se debe saber cómo todos los hosts XPath posibles implementan el registro de los prefijos del espacio de nombres. La respuesta correcta a esta pregunta general y recurrente (si queremos que la respuesta sirva no solo para los usuarios de una implementación particular de XPath) debe explicar qué está sucediendo y permitir que los usuarios busquen en su documentación particular los detalles definidos por la implementación. –

+0

Eso puede ser, pero OP preguntó sobre cómo hacerlo en XML :: LibXML, entonces ¿por qué te tomas a mal que le cuente lo poco que te perdiste de tu respuesta? – ikegami

7

@Dmitre es correcto. Debe echarle un vistazo al XML::LibXML::XPathContext que le permitirá declarar el espacio de nombres y luego puede usar las instrucciones XPath con espacio de nombres. Di un ejemplo del uso de esto hace algún tiempo en stackoverflow - echar un vistazo a Why should I use XPathContext with Perl's XML::LibXML

+0

+1 para obtener información detallada. –

+0

Gracias por el puntero a la pregunta XPathContext. Sospeché que podría ayudarme e intenté usarlo sin saber lo que estaba haciendo sin éxito. Veré si los ejemplos allí ayudarán. – benrifkah

1

Usando XML :: LibXML 1.69.

Quizás sea una cosa XML :: LibXML 1.69 pero la parte extraña es que puedo usar el XPath normal y findnodes() y el código a continuación imprime los nodos.

use strict; 
use XML::LibXML; 

my $xml = <<END_XML; 
<?xml version="1.0" encoding="iso-8859-1"?> 
<RootElement> 
    <MyContainer xmlns="http://www.w3.org/2000/xmlns/"> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 
</RootElement> 
END_XML 

my $parser = XML::LibXML->new(); 

$parser->recover_silently(1); 

my $doc = $parser->parse_string($xml); 

my $root = $doc->documentElement(); 

foreach my $node ($root->findnodes('MyContainer/MyField')) { 
    print $node->toString(); 
} 

Pero si cambio el espacio de nombres a algo distinto de "http://www.w3.org/2000/xmlns/", a continuación, utilizando XML :: :: LibXML XPathContext es necesario para obtener los mismos nodos imprimir.

use strict; 
use XML::LibXML; 

my $xml = <<END_XML; 
<?xml version="1.0" encoding="iso-8859-1"?> 
<RootElement> 
    <MyContainer xmlns="http://something.org/2000/something/"> 
    <MyField> 
     <Name>ID</Name> 
     <Value>12345</Value> 
    </MyField> 
    <MyField> 
     <Name>Name</Name> 
     <Value>Ben</Value> 
    </MyField> 
    </MyContainer> 
</RootElement> 
END_XML 

my $parser = XML::LibXML->new(); 

$parser->recover_silently(1); 

my $doc = $parser->parse_string($xml); 

my $root = $doc->documentElement(); 

my $xpc = XML::LibXML::XPathContext->new($root); 

$xpc->registerNs("x", "http://something.org/2000/something/"); 

foreach my $node ($xpc->findnodes('x:MyContainer/x:MyField')) { 
    print $node->toString(); 
} 
+0

Elimine la línea '$ parser-> recover_silently (1);' en el primer ejemplo y obtendrá el mensaje de error 'namespace error: la reutilización del nombre de espacio de nombres xmlns está prohibida'. Si usa la opción 'recover', la declaración del espacio de nombres simplemente será ignorada. Si usa 'recover_silently', ni siquiera se imprimirá un mensaje de error. Es por eso que generalmente es una mala idea. – nwellnhof

Cuestiones relacionadas