Почему XML :: LibXML не находит узлы для этого запроса xpath при использовании пространства имен - PullRequest
6 голосов
/ 03 ноября 2010

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

#!/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 без пространства имен, он находит узел без проблем:

benb@enkidu:~$ 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>

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

benb@enkidu:~$ ROC/ECG/libxml_xpath.pl 'RootElement/MyContainer' libxml use_namespace
NO NODES FOUND

Сравните это с выводом при использовании синтаксического анализатора XMLL :: XPath:

benb@enkidu:~$ 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>
benb@enkidu:~$ 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 обрабатывает это по-разному, когда я использую пространство имен? Что я могу сделать, чтобы получить узел, когда пространство имен на месте?

Ответы [ 3 ]

14 голосов
/ 03 ноября 2010

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

Затем выражение:

RootElement/MyContainer

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

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

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

x:RootElement/x:MyContainer

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

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

*[name()='RootElement']/*[name()='MyContainer']
7 голосов
/ 03 ноября 2010

@ Дмитрий прав.Вам нужно взглянуть на XML :: LibXML :: XPathContext , который позволит вам объявить пространство имен, а затем вы сможете использовать операторы XPath с учетом пространства имен.Я привел пример использования этого некоторое время назад в stackoverflow - взгляните на Почему я должен использовать XPathContext с Perl XML :: LibXML

1 голос
/ 03 ноября 2010

Использование 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();
}
...