Базовый анализ строки XML с XML::Twig

Я использую XML::Simple более десяти лет, и он сделал все, что мне нужно, и я почти никогда не трогал Perl. Хотя сейчас мне нужно проанализировать строку XML, чтобы просто: получить все элементы, являющиеся дочерними элементами корня, и для каждого получить их тип элемента, атрибуты и содержимое (мне все равно, есть ли вложенные элементы, просто чтение содержимого в виде строки идеально). Я могу сделать все это с помощью XML::Simple, ИСКЛЮЧИТЬ, кроме того, мне нужно сохранить порядок, который Simple не может сделать, когда есть несколько типов элементов.

Я только что установил Twig, и это выглядит потрясающе для чего-то, на что я надеялся, что это будет быстрый сценарий. Маловероятно, что я когда-нибудь снова буду использовать Twig, это то, что Twig может сделать легко?

3 ответа

Решение

На простом уровне - XML::Twig - прохождение детей:

#!/usr/bin/perl

use strict;
use warnings; 

use XML::Twig;

my $twig = XML::Twig -> new -> parsefile ( 'myxml.xml' );

foreach my $element ( $twig -> root -> children ) { 
    print $element -> text; #element content. 
}

Извлечение атрибутов элемента выполняется с помощью:

 $element -> att('attributename');

Или вы можете получить ссылку на хеш с atts:

 my $attributes = $element -> atts();
 foreach my $key ( keys %$attributes ) {
     print "$key => ", $attributes -> {$key}, "\n";
 }

Что мне особенно нравится, так это то, что для XML, где у вас длинный список похожих элементов, где вы пытаетесь обработать - вы можете определить обработчик - он вызывается каждый раз, когда анализатор встречает и передает это подмножество XML,

sub process_book {
     my ( $twig, $book )  = @_;
     print $book -> first_child ('title'); 
     $twig -> purge; #discard anything we've already seen. 
}

my $twig = XML::Twig -> new ( twig_handlers => { 'book' => \&process_book } ); 
$twig -> parsefile ( 'books.xml' ); 

Пример XML:

<XML>
   <BOOK>
       <title>Elements of style</title>
       <author>Strunk and White</author>
   </BOOK>
</XML>

Код ниже должен дать вам достаточно информации, чтобы начать.

Несколько заметок:

  • разобрать файл использовать parsefile вместо parse
  • Вы также можете использовать 'level(1)' вместо '/root/*'
  • использование замыкания для вызова обработчика (process_elt), проходя $atts а также $strings это чистый способ сделать это, если вы хотите $atts а также $strings чтобы быть глобальными переменными, вы можете просто написать '/root/*' => \&process_elt и обработчик будет вызываться с веткой и элементом в качестве параметров
  • $t->purge бит предназначен для освобождения памяти, используемой только что обработанным элементом, это полезно, если файл слишком велик для размещения в памяти, в противном случае вам не нужно его использовать
  • DDP является Data::Printer, это только там, чтобы проверить вывод, вы можете использовать любой другой способ сделать это (Data::Dumper, YAML, печатает...)

Вот код:

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

my $atts    = []; # attributes
my $strings = []; # text content

XML::Twig->new( twig_handlers => 
                 { '/root/*' => sub { process_elt( @_, $strings, $atts); } })
         ->parse( \*DATA);

use DDP; p $atts; p $strings;

sub process_elt
  { my( $t, $elt, $strings, $atts)= @_;

    push @$atts, $elt->atts;

    my $string= $elt->text;
    if( $elt->tag eq 'e1')
      { $string=~ s{text}{modified}; }
    push @$strings, $string;

    $t->purge;
  }

__DATA__
<root>
  <e1 att_1="val_1_1" att2= "val_2_1">text content of element 1</e1>
  <e1 att_1="val_1_2" att2= "val_2_2">text content of element 2</e1>
  <e2 att_3="val_3_1" att2= "val_2_3">element with <sub_elt>sub element</sub_elt> inside</e2>
</root>

Я предпочитаю XML:: LibXML. это Reader не нужно хранить всю структуру в памяти, поэтому он может обрабатывать большие файлы:

#!/usr/bin/perl
use warnings;
use strict;

use XML::LibXML::Reader;

my $reader = 'XML::LibXML::Reader'->new( location => 'file.xml' );
while ($reader->read) {
    if (1 == $reader->depth
        and XML_READER_TYPE_ELEMENT == $reader->nodeType
       ) {
        my @info = ($reader->name);
        my $inner = $reader->readInnerXml;
        for my $idx (0 .. $reader->attributeCount - 1) {
            $reader->moveToAttributeNo($idx);
            push @info, $reader->name . '=' . $reader->value;
        }
        push @info, $inner;
        print "@info\n";
    }
}
Другие вопросы по тегам