2010-11-03 2 views
6

Я пытаюсь выбрать узел с помощью запроса XPath, и я не понимаю, почему XML :: LibXML не находит узел, когда он имеет атрибут xmlns. Вот скрипт для демонстрации вопрос:Почему XML :: LibXML не находит узлов для этого запроса xpath при использовании пространства имен

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

Этот сценарий позволяет выбирать между XML :: Libxml парсер и XML :: XPath парсера. Он также позволяет вам определить атрибут xmlns в элементе MyContainer или опустить его в зависимости от переданных аргументов.

Я использую выражение xpath, которое я использую: RootElement/MyContainer. При запуске запроса с использованием XML :: Libxml анализатора без имен он находит узел без проблем:

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

Однако, когда я запускаю его с пространством имен в месте, которое он не находит узлов:

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

Contrast это с выходом при использовании в 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> 

Какой из этих реализаций парсера делает это «правильно»? Почему XML :: LibXML относится к нему по-разному, когда я использую пространство имен? Что я могу сделать, чтобы получить узел, когда пространство имен находится на месте?

+0

Хороший вопрос, +1. См. Мой ответ для объяснения и двух возможных решений. –

+0

@ikegami, поэтому должен быть полезен как для продвинутых, так и для начинающих пользователей. Им не следует отчаиваться, чтобы задавать вопросы. –

ответ

14

Это FAQ. XPath считает любое неподписанное имя в выражении принадлежащим «без пространства имен».

Тогда выражение:

RootElement/MyContainer 

выбирает все MyContainer элементов, которые не принадлежат «нет имен» и являются детьми всех RootElement элементов, которые не принадлежат «нет имен», и являются дочерними элементами контекста (текущими узел). Однако в целом документе нет элементов, которые принадлежат к «без пространства имен» - все элементы принадлежат пространству имен по умолчанию.

Это объясняет результат, который вы получаете. XML :: LibXML is право.

Общее решение заключается в том, что API языка хостинга позволяет привязать определенный префикс к пространству имен путем «регистрации» пространства имен. Тогда можно использовать выражение типа:

x:RootElement/x:MyContainer 

где x является префиксом, с которым пространство имен было зарегистрировано.

В очень редких случаях, когда язык хостинга не предлагает регистрирующие пространств имен, используйте следующее выражение:

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

С XML :: LibXML вы регистрируете пространства имен, используя XML :: LibXML :: XPathContext. Это задокументировано в 'findnodes'. – ikegami

+0

@ikegami, Не следует знать, как все возможные хосты XPath реализуют регистрацию префиксов пространства имен. Правильный ответ на этот общий и вновь возникающий вопрос (если мы хотим, чтобы ответ служил не только пользователям конкретной реализации XPath), должен объяснить, что происходит, и позволить пользователям смотреть в своей конкретной документации на детали, определяемые реализацией. –

+0

Это может быть, но ОП задал вопрос о том, как это сделать в XML :: LibXML, так почему вы обижаетесь на меня, говоря ему немного, что вы пропустили из своего ответа? – ikegami

7

@Dmitre прав. Вам нужно взглянуть на XML::LibXML::XPathContext, который позволит вам объявить пространство имен, а затем вы можете использовать операторы XPath с пространством имен. Я привел пример использования этого некоторое время назад в stackoverflow - посмотрите на Why should I use XPathContext with Perl's XML::LibXML

+0

+1 для получения подробной информации. –

+0

Спасибо за указатель на вопрос XPathContext. Я подозревал, что это может помочь мне и попытаться использовать его, не зная, что я делаю без успеха. Я посмотрю, помогут ли примеры там. – benrifkah

1

Использование XML :: LibXML 1.69.

Возможно, это XML :: LibXML 1.69, но странная часть заключается в том, что я могу использовать обычные XPath и findnodes(), а нижеприведенный код печатает узлы.

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

Но если изменить пространство имен на что-то другое, чем «http://www.w3.org/2000/xmlns/», а затем с помощью XML :: LibXML :: XPathContext требуется, чтобы получить те же узлы печатать.

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

Удалите строку '$ parser-> recover_silently (1);' в первом примере, и вы получите сообщение об ошибке 'namespace error: повторное использование имени пространства имен xmlns запрещено '. Если вы используете опцию «восстановить», объявление пространства имен просто будет проигнорировано. Если вы используете 'recover_silently', даже сообщение об ошибке не будет напечатано. Вот почему это обычно плохая идея. – nwellnhof

Смежные вопросы