2010-11-03 15 views
6

Sto tentando di selezionare un nodo utilizzando una query XPath e non capisco perché XML :: LibXML non trovi il nodo quando ha un attributo xmlns. Ecco uno script per dimostrare il problema:Perché XML :: LibXML non trova nodi per questa query xpath quando si utilizza uno spazio dei nomi

#!/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"; 
} 

Questo script consente di scegliere tra il parser XML :: LibXML e l'XML :: XPath parser. Permette anche di definire un attributo xmlns sull'elemento MyContainer o di lasciarlo fuori a seconda degli argomenti passati.

L'espressione xpath che sto utilizzando è "RootElement/MyContainer". Quando eseguo la query utilizzando il parser XML :: LibXML senza il namespace che trova il nodo con nessun problema:

[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> 

Tuttavia, quando l'eseguo con lo spazio dei nomi in luogo non trova nessun nodo:

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

Contrasto questo con l'uscita quando si utilizza il XMLL :: XPath parser:

[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> 

Quale di queste implementazioni di parser è farlo "giusto"? Perché XML :: LibXML lo tratta in modo diverso quando utilizzo uno spazio dei nomi? Cosa posso fare per recuperare il nodo quando lo spazio dei nomi è a posto?

+0

Buona domanda, +1. Vedi la mia risposta per la spiegazione e per due possibili soluzioni. –

+0

@ikegami, Quindi deve essere utile sia agli utenti avanzati * che a quelli principianti. Non dovrebbero scoraggiarsi a fare domande. –

risposta

14

Questa è una FAQ. XPath considera qualsiasi nome non prefisso in un'espressione appartenente a "nessun spazio dei nomi".

Quindi, l'espressione:

RootElement/MyContainer 

seleziona tutti MyContainer elementi che appartengono a "no namespace" e sono i bambini di tutte RootElement elementi che appartengono a "no namespace" e sono figli del contesto (corrente nodo). Tuttavia, non ci sono elementi in tutto il documento che appartengono a "nessun spazio dei nomi" - tutti gli elementi appartengono allo spazio dei nomi predefinito.

Questo spiega il risultato che si sta ottenendo. XML :: LibXML è destro.

La soluzione comune è che l'API del linguaggio hosting consente un prefisso specifico ad essere vincolato allo spazio dei nomi da "registrare" uno spazio dei nomi. Poi si può usare un'espressione del tipo:

x:RootElement/x:MyContainer 

dove x è il prefisso con la quale lo spazio dei nomi è stato registrato.

Nelle rare occasioni in cui la lingua di hosting non offre spazi dei nomi che registrano, utilizzare la seguente espressione:

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

Con XML :: LibXML, si registrano gli spazi dei nomi utilizzando XML :: LibXML :: XPathContext. Questo è documentato in 'findnodes'. – ikegami

+0

@ikegami, Uno non dovrebbe sapere come tutti gli host XPath possibili implementano la registrazione dei prefissi dei namespace. La risposta corretta a questa domanda generale e ricorrente (se vogliamo che la risposta serva non solo agli utenti di una particolare implementazione XPath) dovrebbe spiegare cosa sta accadendo e consentire agli utenti di guardare nella loro particolare documentazione per i dettagli definiti dall'implementazione. –

+0

Questo potrebbe essere, ma l'OP ha chiesto come farlo in XML :: LibXML, quindi perché mi stai offendendo dicendogli il poco che ti è sfuggito dalla tua risposta? – ikegami

7

@Dmitre è giusto. È necessario dare un'occhiata a XML::LibXML::XPathContext che consente di dichiarare lo spazio dei nomi e quindi è possibile utilizzare le istruzioni XPath dello spazio dei nomi. Ho dato un esempio di utilizzo qualche tempo fa su StackOverflow - dare un'occhiata a Why should I use XPathContext with Perl's XML::LibXML

+0

+1 per le informazioni dettagliate. –

+0

Grazie per il puntatore alla domanda XPathContext. Sospettavo che potesse aiutarmi e tentare di usarlo senza sapere cosa stavo facendo senza successo. Vedrò se gli esempi ci aiuteranno. – benrifkah

1

Utilizzo di XML :: LibXML 1.69.

Forse questa è una cosa XML: LibXML 1.69 ma la parte strana è che posso usare il normale XPath e findnodes() e il codice sottostante stampa i nodi.

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(); 
} 

Ma se cambio lo spazio dei nomi a qualcosa di diverso da "http://www.w3.org/2000/xmlns/", quindi utilizzando XML :: :: LibXML XPathContext è necessario per ottenere gli stessi nodi stampare.

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

Rimuovi la riga '$ parser-> recover_silently (1);' nel primo esempio e riceverai il messaggio di errore 'namespace error: il riutilizzo del nome dello spazio dei nomi xmlns è vietato'. Se si utilizza l'opzione 'recover', la dichiarazione dello spazio dei nomi sarà semplicemente ignorata. Se usi 'recover_silently' nemmeno un messaggio di errore verrà stampato. Ecco perché di solito è una cattiva idea. – nwellnhof

Problemi correlati