使用 LibXML 和 XPath 查找带冒号的节点(本地命名空间)

Using LibXML and XPath To Find Node With Colon (Local Namespace)

我正在尝试从下面的 <Incoming> 中获取属性 @id1 XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Incomings xmlns:ns2="http://testme.org/foo/schema">
    <Incoming id1="6bbaec22" id2="928c2081">
        <ns2:Address>fubar@test.com</ns2:Address>
    </Incoming>
</Incomings>

我唯一可以传递的信息是电子邮件地址fubar@test.com

我正在使用 XML::LibXMLXML::LibbXML::XPathContext,如下所示:

my $dom = XML::LibXML->new->parse_file( $xml_file );  # XML contains as above
my $xpc = XML::LibXML::XPathContext->new( $dom->documentElement );
$xpc->registerNs('x', 'http://testme.org/foo/schema');

my $email = 'fubar@test.com';
my $xpath = "/x:Incomings/x:Incoming/x:ns2:Address[text()='$email']/../\@id1";
my @nodes = $xpc->findnodes( $xpath );

但它总是在 ns2:Address.

周围的 $xpath 中给我一个无效的表达式

上面我犯了什么错误?如果节点名称仅为 <Address>,则从我的 $xpath 语句中删除 ns2:,从而在 @nodes.[=21= 中提供正确的值]

谢谢!

尝试路径 "/Incomings/Incoming[x:Address = '$email']/@id1"。如果 Perl 字符串文字需要转义 \@id1,则保留它,即 "/Incomings/Incoming[x:Address = '$email']/\@id1".

我认为这里有两个问题 - 首先,xpath 表达式查找节点。您可以根据属性的存在和内容进行搜索,但 findnodes 会为您提供元素,而不是内容。

其次 - 您不能在 XML 中嵌套命名空间。 x:ns2:Address 无效。你真的需要在那里注册你的 x 命名空间吗?你可能根本不需要。 (例如,基于您的 XML 小片段)。

我可以提供替代方案吗?因为您正在使用 perl ,所以您实际上不一定需要通过 xpath 表达式完成所有操作。

我可能会想 findnodes 然后是 grep:

注意:使用 XML::Twig 进行说明 - 很确定在 XML::LibXML 中有非常相似的东西。

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig->new( 'pretty_print' => 'indented_a' )->parse( \*DATA );

my @elt_list = grep { $_->trimmed_text =~ m{fubar\@test.com} }
    ( $twig->findnodes('//ns2:Address') );

foreach my $elt (@elt_list) {
    print $elt -> parent -> att('id1');
}


__DATA__
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Incomings xmlns:ns2="http://testme.org/foo/schema">
    <Incoming id1="6bbaec22" id2="928c2081">
        <ns2:Address>fubar@test.com</ns2:Address>
    </Incoming>     
</Incomings>

我还要注意 - 你的 xpath 让你找到一个元素 - 而不是一个属性 - 所以你可以 select 在具有 id1 属性的元素上,像这样:

my @elt_list = ( $twig->findnodes("//ns2:Address[string()='$email']/../.[\@id1]") );

foreach my $elt (@elt_list) {
    print $elt -> att('id1');
}

取决于您希望 findnodes 搜索的具体程度。根据您在该片段中提供的内容,您已经走得太复杂了,可以简单地做:

use XML::Twig;

my $twig = XML::Twig->parsefile('your_file.xml'); 
print $twig -> findnodes('//Incoming',0)->att('id1'),"\n";

或:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;

my $xml = XML::LibXML->new->parse_file( 'sample2.xml' );
foreach my $node (  $xml -> findnodes( '//Incoming' ) ) {
   print $node ->getAttribute('id1'), "\n";
} 

或者用一点 grepping:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;

my $email = 'fubar@test.com';
my $xml = XML::LibXML->new->parse_file( 'sample2.xml' );
foreach my $node ( grep { $_ -> textContent =~ m{$email} } $xml -> findnodes( '//Incoming' ) ) {
   print $node ->getAttribute('id1'), "\n";
} 

如果您特别想使用那个 x 命名空间 - 这可行:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;

my $xml   = XML::LibXML->new->parse_file('sample2.xml');
my $xpc   = XML::LibXML::XPathContext->new( $xml->documentElement );
$xpc->registerNs( 'x', 'http://testme.org/foo/schema' );

my $email = 'fubar@test.com';
my ( $id1 ) = map { $_ -> getAttribute('id1') // () } $xpc->findnodes("/Incomings/Incoming/x:Address[text()='$email']/..");
print $id1,"\n";

(如果我模拟一些具有多个 'Incoming' 节点的 XML 到第一个具有正确电子邮件地址的 select 也可以工作。注意 // 是 perl 5.10 以上, 并且是 'defined' 的条件。您可以在旧版本上用 || 替换它,即 'true/false' - 唯一不同的地方是空字符串和零)

在两种情况下,您在错误的命名空间中搜索元素,在一种情况下。您使用的是两个没有意义的前缀。固定:

my $email = 'fubar@test.com';
my $xpath = "/Incomings/Incoming/x:Address[text()='$email']/../\@id1";
my @nodes = $xpc->findnodes($xpath);

我更愿意避免使用 ..。我会使用以下内容:

my $email = 'fubar@test.com';
my $xpath = "/Incomings/Incoming[x:Address/text()='$email']/\@id1";
my @nodes = $xpc->findnodes($xpath);