Perl XML::LibXML $node->findnodes($xpath) 查找不应该的节点



这是我遇到的一些代码,我处理了一些XML,并在OO类的方法中,我从文档中重复的几个节点中的每一个中提取一个元素。每个节点的子树中应该只有一个这样的元素,但我的代码获取所有元素,就好像它在整个文档上运行一样。

因为我只期望得到 oine 元素,所以我只使用数组的第 0 个元素,这导致我的函数输出错误的值(并且对于文档中的所有项目都是一样的)

下面是一些说明问题的简化代码

$ cat t4.pl
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
my $xml = <<EndXML;
<Envelope>
  <Body>
    <Reply>
      <List>
        <Item>
          <Id>8b9a</Id>
          <Message>
            <Response>
              <Identifier>55D</Identifier>
            </Response>
          </Message>
        </Item>
        <Item>
          <Id>5350</Id>
          <Message>
            <Response>
              <Identifier>56D</Identifier>
            </Response>
          </Message>
        </Item>
      </List>
    </Reply>
  </Body>
</Envelope>
EndXML
my $foo = Foo->new();
my $parser = XML::LibXML->new();
my $doc    = $parser->parse_string( $xml );
my @list   = $doc->getElementsByTagName( 'Item' );
for my $item ( @list ) {
    my $id = get( $item, 'Id' );
    my @messages = $item->getElementsByLocalName( 'Message' );
    for my $message ( @messages ) {
        my @children = $message->getChildNodes();
        for my $child ( @children ) {
            my $name = $child->nodeName;
            if ( $name eq 'Response' ) {
                print "child is a Responsen";
                $foo->do( $child, $id );
            }
            elsif ( $name eq 'text' ) {
                # ignore whitespace between elements
            }
            else {
                print "child name is '$name'n";
            }
        }    # child
    }    # Message
}    # Item
# ..............................................
sub get {
    my ( $node, $name ) = @_;
    my $value   = "(Element $name not found)";
    my @targets = $node->getElementsByTagName( $name );
    if ( @targets ) {
        my $target = $targets[0];
        $value = $target->textContent;
    }
    return $value;
}
# ..............................................
package Foo;
sub new {
    my $self = {};
    bless $self;
    return $self;
}
sub do {
    my $self = shift;
    my ( $node, $id ) = @_;
    print '-' x 70, "n", ' ' x 12, $node->toString( 1 ), "n", '-' x 70, "n";
    my @identifiers = $node->findnodes( '//Identifier' );
    print "do() found ", scalar @identifiers, " Identifiersn";
    print "$id, ", $identifiers[0]->textContent, "nn";
}

这是输出

$ perl t4.pl
child is a Response
----------------------------------------------------------------------
            <Response>
              <Identifier>55D</Identifier>
            </Response>
----------------------------------------------------------------------
do() found 2 Identifiers
8b9a, 55D
child is a Response
----------------------------------------------------------------------
            <Response>
              <Identifier>56D</Identifier>
            </Response>
----------------------------------------------------------------------
do() found 2 Identifiers
5350, 55D

我期待

do() found 1 Identifiers

我期待最后一行是

5350, 56D

由于平台问题,我正在使用旧版本的XML::LibXML。

问:问题是否存在于更高版本中,还是我做错了什么?

来自 XPath 1.0 的文档

para

选择文档根目录的所有 para 后代

(强调我自己的)。所以你的电话

$node->findnodes( '//Identifier' )

忽略上下文节点$node并在文档中的任意位置搜索所有Identifier元素

要获取上下文节点的所有Identifier后代,您必须添加一个点,如下所示

$node->findnodes('.//Identifier');

但是由于$node始终是一个Response元素,而IdentifierResponse的直接子元素,因此您可以编写

$node->findnodes('Identifier');



你似乎有点束手束脚地写这个。我知道您已经以代码为例进行了删减,但是您真的需要单独的软件包吗?通过明智地应用 XPath,可以做很多事情。

最明显的变化是,你不需要遍历所有的孩子——你可以简单地挑选出你感兴趣的孩子。

这个重构的代码可能值得一读

use strict;
use warnings;
use XML::LibXML;
my $parser = XML::LibXML->new;
my $doc    = $parser->parse_fh(*DATA);
for my $item ( $doc->findnodes('//Item') ) {
    print "n";
    my ($id) = $item->findvalue('Id');
    printf "Item Id: %sn", $item->findvalue('Id');
    my @messages = $item->findnodes('Message');
    for my $message (@messages) {
        my ($response) = $message->findnodes('Response');
        printf "Response Identifier: %sn", $response->findvalue('Identifier');
    }
}
__DATA__
<Envelope>
  <Body>
    <Reply>
      <List>
        <Item>
          <Id>8b9a</Id>
          <Message>
            <Response>
              <Identifier>55D</Identifier>
            </Response>
          </Message>
        </Item>
        <Item>
          <Id>5350</Id>
          <Message>
            <Response>
              <Identifier>56D</Identifier>
            </Response>
          </Message>
        </Item>
      </List>
    </Reply>
  </Body>
</Envelope>

输出

Item Id: 8b9a
Response Identifier: 55D
Item Id: 5350
Response Identifier: 56D

我对代码的质量没有评论,但是在使用XML::LibXML之前学会了使用XML::DOM,我倾向于使用一些DOM语法。我一直在努力打败这个习惯,:)。
我提到这一点的原因是因为我看到你已经使用了相当于->item(0)从节点列表中获取第一个位置,就像你在 DOM 中一样。
XML::LibXML支持使用 ->item()但从 cpan 我可以看到 xpath 创建节点列表从 1 开始,不像 DOM 那样0。我很确定,如果您保持代码不变并查找第一个数组位置而不是第 0 个数组位置,您将获得所需的结果。
不清楚的是为什么->item(0)会像我的测试一样给您最后一个结果(它是否可能偏离数组值,以便您实际上返回第 -1 个数组值)

最新更新