【问题标题】:Using LibXML and XPath To Find Node With Colon (Local Namespace)使用 LibXML 和 XPath 查找带冒号的节点(本地命名空间)
【发布时间】:2015-08-26 08:43:10
【问题描述】:

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

<?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::LibXML 和XML::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 中给我一个无效的表达式。

我在上面犯了什么错误?如果节点名称仅为&lt;Address&gt;,则从我的$xpath 语句中删除ns2:,从而为我提供@nodes 中的正确值。

谢谢!

【问题讨论】:

    标签: xml perl xpath libxml2


    【解决方案1】:

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

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

    我可以提供其他选择吗?因为您使用的是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 可以让您找到一个元素 - 而不是一个属性 - 所以您可以选择具有 id1 属性的元素,如下所示:

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

    取决于您希望对findnodes 搜索的具体程度。根据您在该 sn-p 中提供的内容,您已经过于复杂,可以简单地执行以下操作:

    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";
    

    (如果我使用多个“传入”节点模拟一些 XML 以选择具有正确电子邮件地址的第一个节点,也可以使用。注意 // 是 perl 5.10 及更高版本,并且是“已定义”的条件。您可能会替换在旧版本上使用||,即“真/假”——唯一不同的地方是空字符串和零)

    【讨论】:

    • 感谢您提供详细的答案并显示替代方案。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 谢谢@ikegami - 如果我能接受两个解决方案,我会这样做,因为你的回答解决了我原来的问题。
      猜你喜欢
      • 2011-05-04
      • 1970-01-01
      • 2020-05-13
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 2012-11-10
      • 1970-01-01
      相关资源
      最近更新 更多