Какой самый быстрый способ извлечь значения нескольких элементов из файлов XML в Perl? - PullRequest
3 голосов
/ 14 марта 2010

У меня есть куча файлов XML размером примерно 1-2 мегабайта. На самом деле, больше, чем куча, есть миллионы. Они все правильно сформированы, и многие даже проверены на соответствие их схеме (подтверждено с помощью libxml2).

Все они были созданы одним и тем же приложением, поэтому они имеют единый формат (хотя это может теоретически измениться в будущем).

Я хочу проверить значения одного элемента в каждом файле из скрипта Perl. Скорость важна (я хотел бы занять меньше секунды на файл), и, как уже было отмечено, я уже знаю, что файлы правильно сформированы.

Мне очень хочется просто «открыть» файлы в Perl и сканировать до тех пор, пока я не увижу искомый элемент, взять значение (которое находится рядом с началом файла) и закрыть файл.

С другой стороны, я мог бы использовать синтаксический анализатор XML (который мог бы защитить меня от будущих изменений в форматировании XML), но я подозреваю, что это будет медленнее, чем хотелось бы.

Кто-нибудь может порекомендовать подходящий подход и / или парсер?

Заранее спасибо.

Обновление

Вот структура / сложность данных, которые я пытаюсь извлечь:

<doc>
  ...
  <someparentnode attrib="notme" attrib2="5">
    <node>Not this one</node>
  </someparentnode>
  <someparentnode attrib="pickme" attrib2="5">
    <node>This is the data I want</node>
  </someparentnode>
  <someparentnode attrib="notme" 
     attrib2="reallyreallylonglineslikethisonearewrapped">
    <node>Not this one either and it may be 
      wrapped too.</node>
  </someparentnode>
  ...    
</doc>

Иерархия идет на несколько уровней глубже, но я думаю, что это покрывает то, что я пытаюсь сделать.

Ответы [ 3 ]

8 голосов
/ 14 марта 2010

2 автономных опций с поддержкой XML (которые я написал, поэтому я могу быть предвзятым; -): xml_grep (входит в XML :: Twig ) и xml_grep2 App :: xml_grep2 ).

Вы бы написали xml_grep -t '*[@attrib="pickme"]' *.xml или xml_grep2 -t '//*[@attrib="pickme"]' *.xml (опция -t дает вам результат в виде текста вместо XML). Также в обоих случаях все документы будут проанализированы, но в следующей версии xml_grep будет добавлена ​​возможность ограничить количество результатов для файла и прекратить анализ каждого файла, как только будет достигнуто это число.

В противном случае, если вам нужна скорость и если код должен быть интегрирован, вы можете использовать XML :: Twig с обработчиком, запускаемым для нужного элемента (элементов), и вызовом finish_now, когда вы нашел его, который прервет разбор и позволит перейти к следующему файлу.

XML :: LibXML также является опцией, хотя тогда вам придется полностью проанализировать каждый документ и использовать XPath (легко, но может быть медленнее), использовать SAX (может быть быстрее, но болезненно кодировать) или использовать команду pull. Парсер (вероятно, лучший вариант, но я никогда не использовал его).

Обновление после вашего обновления: код с XML :: Twig будет выглядеть так:

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

use XML::Twig;

my $twig= XML::Twig->new( twig_handlers => { '*[@attrib="pickme"]' => \&pickme });

foreach my $file (@ARGV)
  { $twig->parsefile( $file); }

sub pickme
  { my( $twig, $node)= @_;
    print $node->text, "\n";
    $twig->finish_now;
  }
0 голосов
/ 15 марта 2010

Если вы хотите сделать это быстро , я бы порекомендовал вам использовать XML :: Bare вместо XML :: Simple или XML :: Twig.

Я использую его для анализа нескольких XML-файлов размером 2-5 МБ, и это ускорение невероятно: 0,2 секунды против 4 минут, в некоторых случаях. Подробности здесь: http://darkpan.com/files/xml-parsing-perl-gripes.txt.

0 голосов
/ 14 марта 2010

Awk

awk 'BEGIN{
 RS="</doc>"
 FS="</someparentnode>"
}

{
  for(i=1;i<=NF;i++){
     if( $i~/pickme/){
        m=split($i,a,"</node>")
        for(o=1;o<=m;o++){
          if(a[o]~/<node>/){
            gsub(/.*<node>/,"",a[o])
            print a[o]
          }
        }
     }
  }
}' file

Perl

#!/usr/bin/perl
$/ = '</doc>';
$FS = '</someparentnode>';
while (<>) {
    chomp;
    @F = split $FS,;
    for ($i=0;$i<=$#F; $i++) {
        if ($F[$i] =~ /pickme/) {
            $M=(@a=split('</node>', $F[$i]));
            for ($o=0; $o<$M; $o++) {
                if ($a[$o]=~/<node>/) {
                    $a[$o] =~ s/.*<node>//sg;
                    print $a[$o];
                }
            }
        }
    }
}

выход

$ perl script.pl file
This is the data I want

$ ./shell.sh
This is the data I want
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...