13

これが私が問題を抱えているいくつかのコードです。私はいくつかのXMLを処理し、OOクラスのメソッドで、ドキュメント内で繰り返されるいくつかのノードのそれぞれから要素を抽出します。各ノードのサブツリーにはそのような要素が1つしかないはずですが、私のコードは、ドキュメント全体で動作しているかのようにすべての要素を取得します。

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 Response\n";
                $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, " Identifiers\n";

    print "$id, ", $identifiers[0]->textContent, "\n\n";
}

これが出力です

$ 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を使用しています。

Q:問題はそれ以降のバージョンに存在しますか、それとも何か間違ったことをしていますか?

4

2 に答える 2

22

XPath1.0のドキュメントから

// paraは、ドキュメントルートのすべてのpara子孫を選択します

(私自身を強調します)。だからあなたの電話

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

コンテキストノードを無視し、ドキュメント内の任意の場所ですべての要素$nodeを検索していますIdentifier

コンテキストノードのすべてのIdentifier子孫を取得するには、次のようにドットを追加する必要があります

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

しかし、$nodeは常にResponse要素であり、あなたIdentifierの直接の子であるため、Response

$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: %s\n", $item->findvalue('Id');

    my @messages = $item->findnodes('Message');

    for my $message (@messages) {
        my ($response) = $message->findnodes('Response');
        printf "Response Identifier: %s\n", $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
于 2012-08-14T15:23:03.397 に答える
0

コードの品質についてはコメントしていませんが、使用するXML::DOM前に使用方法を学んだのでXML::LibXML、DOM構文の一部を使用する傾向があります。私は私からこの習慣を打ち負かそうとしてきました:)。私がこれに言及する理由は、DOMの場合と同じように、ノードリストから最初の位置を取得する
のと同等の機能を使用していることがわかるためです。の使用をサポートしていますが、cpanから、xpathがDOMとは異なるところから始まるノードリストを作成していることがわかります。コードをそのままにして、0番目ではなく1番目の配列位置を探すと、希望する結果が得られると確信しています。 はっきりしないのはその理由です->item(0)
XML::LibXML->item()10
->item(0)私のテストから得られたように見える最後の結果が得られます(おそらく配列値からオフセットされているため、実際には-1番目の配列値が返されます)

于 2014-04-08T15:59:27.487 に答える