Почему XML:: LibXML не находит узлов для этого запроса xpath при использовании пространства имен
Я пытаюсь выбрать узел с помощью запроса 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 FOUNDn";
}
этот скрипт позволяет вам выбрать между 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 ответов
это FAQ. XPath считает, что любое незафиксированное имя в выражении принадлежит "no namespace".
затем выражение:
RootElement/MyContainer
выбирает все MyContainer
элементы, которые принадлежат "нет пространства имен" и являются дочерними для всех RootElement
элементы, которые принадлежат "нет пространства имен" и являются дочерними элементами контекста (текущий узел). Однако во всем документе нет элементов, которые принадлежат "no namespace" -- все элементы принадлежат по умолчанию пространство имен.
это объясняет результат, который вы получаете. Модуль XML::в libxml is право.
общее решение заключается в том, что API языка хостинга позволяет привязать определенный префикс к пространству имен путем "регистрации" пространства имен. Тогда можно использовать выражение типа:
x:RootElement/x:MyContainer
здесь x
- префикс, с которым было зарегистрировано пространство имен.
в очень редких случаях, когда хостинг язык не предлагает регистрацию пространств имен используйте следующее выражение:
*[name()='RootElement']/*[name()='MyContainer']
@Dmitre прав. Вам нужно взглянуть на в xml::в libxml::XPathContext что позволит вам объявить пространство имен, а затем вы можете использовать операторы XPath с учетом пространства имен. Я привел пример использования этого некоторое время назад на StackOverflow - посмотри почему я должен использовать XPathContext с XML::LibXML Perl
использование 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();
}