为什么在使用命名空间时,XML :: LibXML没有为此xpath查询找到节点

时间:2010-11-03 01:32:00

标签: xml perl xpath libxml2

我正在尝试使用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解析器之间进行选择。它还允许您在MyContainer元素上定义xmlns属性,或者根据传递的参数将其保留。

我正在使用的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 个答案:

答案 0 :(得分:14)

这是常见问题解答。 XPath认为表达式中任何未加前缀的名称都属于“无名称空间”。

然后,表达式:

RootElement/MyContainer

选择属于“no namespace”的所有MyContainer元素,并且是属于“no namespace”的所有RootElement元素的子元素,并且是上下文的子元素(当前节点)。但是,整个文档中根本没有属于“无命名空间”的元素 - 所有元素都属于默认命名空间。

这解释了您获得的结果。 XML :: LibXML 是对的。

常见的解决方案是托管语言的API允许通过“注册”命名空间将特定前缀绑定到命名空间。然后可以使用如下表达式:

x:RootElement/x:MyContainer

其中x是注册名称空间的前缀。

在托管语言不提供注册命名空间的极少数情况下,请使用以下表达式:

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

答案 1 :(得分:7)

@Dmitre是对的。您需要查看XML::LibXML::XPathContext,它将允许您声明命名空间,然后您可以使用名称空间感知的XPath语句。我给出了一个在stackoverflow上使用它的例子 - 看看Why should I use XPathContext with Perl's XML::LibXML

答案 2 :(得分: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来获取要打印的相同节点。 / p>

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