2010-11-03 59 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的解析器之间进行选择。它还允许您在MyContainer元素上定义xmlns属性或根据传递的参数将其关闭。

我使用的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 

对比这与使用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。请参阅我的回答以获得解释和两种可能的解决方案 – 2010-11-03 03:07:23

+0

@ikegami,因此必须对高级*和*新手用户都有用。他们不应该气馁地提问。 – 2014-03-27 14:32:56

回答

14

这是一个常见问题解答。 XPath认为表达式中没有前缀的名称属于“no namespace”。

然后,表达式:

RootElement/MyContainer 

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

这解释了你所得到的结果。 XML :: LibXML 没错。

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

x:RootElement/x:MyContainer 

其中x是与该命名空间已被注册的前缀。

在极少数情况下,其中的主导语言不提供注册名称空间,使用下面的表达式:

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

使用XML :: LibXML,您可以使用XML :: LibXML :: XPathContext注册名称空间。这在'findnodes'中有记录。 – ikegami 2014-03-27 13:32:51

+0

@ikegami,人们不应该知道所有可能的XPath主机如何实现注册命名空间前缀。对于这个普遍的和重新发生的问题(如果我们希望答案不仅仅服务于特定的XPath实现的用户)的正确答案应该解释发生了什么并且允许用户查看他们的特定文档中的实现定义的细节。 – 2014-03-27 14:36:08

+0

可能是这样,但是OP询问了如何在XML :: LibXML中完成它,那么为什么你对我进行冒犯,告诉他你从答案中错过了一点点? – ikegami 2014-03-27 14:39:43

7

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

+0

+1的详细信息。 – 2010-11-03 12:28:05

+0

感谢指向XPathContext问题的指针。我怀疑它可以帮助我,并试图在不知道自己在做什么而没有取得任何成功的情况下使用它。我会看看那里的例子会有帮助。 – benrifkah 2010-11-03 17:09:11

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);',您将收到错误消息'命名空间错误:禁止重复使用xmlns命名空间名称。如果使用'recover'选项,名称空间声明将被忽略。如果你使用'recover_silently',甚至不会打印出错信息。这就是为什么它通常是一个坏主意。 – nwellnhof 2014-03-27 10:42:55

相关问题