IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  XML  >

XML для разработчиков Perl: Часть 2. Продвинутые методы XML-парсинга с использованием Perl

Обзор парсинга дерева и парсинга, управляемого событиями

developerWorks
Опции документа

Загрузить Adobe® Reader®

Опции документа, требующие включения JavaScript, не отображаются

Обсудить


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Джим Диксон, писатель, независимый писатель

28.04.2009

Данная серия статей адресована тем, кому нужно быстрое решение на XML и Perl. Во второй статье программистам на Perl предлагается рассмотреть два основных направления XML-парсинга: парсинг дерева и парсинг, управляемый событиями.

Введение

Для очень большого числа Perl-приложений в качестве инструмента для работы с XML достаточно XML::Simple, который рассматривался в части 1 данной серии статей (см. Ресурсы). XML::Simple умеет преобразовывать входящие XML-файлы в легко обрабатываемые структуры данных Perl и обратно преобразовывать такие структуры данных в XML. Однако нужно помнить о том, что этот метод работает не всегда.

XML::Simple - не самое лучшее решение, когда вам нужно создать представление XML-документа в памяти и затем выполнить поиск по нему или преобразовать сложным и непредсказуемым образом. В подобных ситуациях применяется парсинг дерева. Если XML-документ не умещается в памяти или является потоком неизвестной длины, использовать XML::Simple также нельзя. В этом случае следует использовать парсер, управляемый событиями. Парсеры, управляемые событиями, поначалу кажутся несколько необычными, но когда вы начнете использовать этот тип парсинга, SAX может стать вашим любимым инструментом.

Вся остальная статья будет посвящена этим двум продвинутым способам использования Perl для проведения парсинга XML.



В начало


Начало работы

Чтобы следовать за изложением в статье, вам потребуется несколько модулей Perl с открытым исходным кодом. Вы можете их получить одним из двух способов: если у вас Windows, используйте ppm; если UNIX® или Linux™ - обратитесь к CPAN (см. Ресурсы). Если вы не знаете об этих архивах, прочитайте о них в части 1 данной серии статей.

В листинге 1 показано, как можно управлять модулями на UNIX/Linux. Конечно, лучше работать как пользователь root, чтобы модули были доступны для всех пользователей в системе. У этих модулей есть зависимости, некоторые из которых могут отсутствовать в вашей системе. Если cpan сконфигурирован правильно (follow=yes), зависимости будут устанавливаться автоматически.


Листинг 1. Получение модулей, используемых в данной статье, при помощи CPAN
   
$ perl -MCPAN -e shell
cpan> install XML::LibXML XML::SAX::Base XML::SAX::ExpatXS XML::SAX::Writer
cpan> quit

Как видно из листинга 2, в Windows это даже проще. Опять же устанавливать все лучше с правами администратора.


Листинг 2. Получение модулей PPM
   
$ppm install XML::LibXML XML::SAX::Base XML::SAX::ExpatXS XML::SAX::Writer



В начало


Парсинг дерева

Для многих программистов может оказаться очень удобным отображать XML в виде древовидной структуры. Такое представление XML было формализовано как объектная модель документа (Document Object Model - DOM), и используется уже много лет; DOM Level 3 вышла в 2002 году.

DOM представляет XML-документ как дерево двунаправленных узлов, где первый потомок на каждом уровне присоединяется к своему родителю и параллельно к братьям. В дереве определен большой набор функций, реализованных на всех основных языках программирования.

Хотя по дереву DOM можно перемещаться по ссылкам, обычно гораздо более эффективным с точки зрения затрат времени программиста является использование протокола XPath. Этот подъязык позволяет выполнять перемещение к узлам, извлечение наборов узлов и так далее.

Ссылки на спецификацию DOM и на более подробные введения в спецификацию DOM, XPath и смежные протоколы вы найдете в разделе Ресурсы.

Многие модули Perl могут разбирать XML-документы в деревья DOM. Одним из лучших является XML::LibXML Петра Паяса (см. Ресурсы). Он является оболочкой для libxml2 проекта Gnome - многофункционального пакета, включающего парсер DOM, частичную реализацию XPath и реализацию SAX2 (описывается ниже).

В листинге 3 показан файл, с которым вы работали в части 1 (см. Ресурсы), где его разбирали с XML::Simple, меняли представление на структуру данных Perl и после этого использовали XML::Simple, чтобы обратно преобразовать его в XML в текстовой форме.


Листинг 3. Зоомагазин Рози, pets.xml
   
 <?xml version='1.0'?>
<pets>
  <cat>
    <name>Madness</name>
    <dob>1 February 2004</dob>
    <price>150</price>
  </cat>
  <dog>
    <name>Maggie</name>
    <dob>12 October 2002</dob>
    <price>75</price>
    <owner>Rosie</owner>
  </dog>
  <cat>
    <name>Little</name>
    <dob>23 June 2006</dob>
    <price>25</price>
  </cat>
</pets>

Этот файл очень легко разобрать с помощью XML::LibXML (см. листинг 4 и результат программы в листинге 5). Простая команда $parser->parse_file создает древовидную структуру XML для модели DOM. Мы определяем простую подпрограмму на Perl для прибавления подэлемента к узлу в дереве и затем используем это для создания поддерева, представляющего отдельное животное. Далее мы с помощью подпрограммы addPet() прибавляем в наш ассортимент пару новых животных: песчанку и хомяка.


Листинг 4. Разбор ассортимента магазина Рози с помощью XML::LibXML

#!/usr/bin/perl -w
use strict;
use XML::LibXML;

my $parser = XML::LibXML->new;
my $doc    = $parser->parse_file('pets.xml')
                or die "can't parse Rosie's stock file: $@";
my $root = $doc->documentElement();
sub addSubElm($$$) {
    my ($pet, $name, $body) = @_;
    my $subElm = $pet->addNewChild('', $name);
    $subElm->addChild( $doc->createTextNode($body) );
}
sub addPet($$$$) {
    my ($type, $name, $dob, $price) = @_;
    # addNewChild is non-compliant; could use addSibling instead
    my $pet = $root->addNewChild('', $type);
    addSubElm ( $pet, 'name', $name );
    addSubElm ( $pet, 'dob',  $dob  );
    addSubElm ( $pet, 'price', $price );
}    
addPet('gerbil',  'nasty', '15 February 2006', '5');
addPet('hamster', 'boris', '5 July 2006',      '7.00');

my @nodeList = $doc->getElementsByTagName('price');
foreach my $priceNode (@nodeList) {
    my $curPrice = $priceNode->textContent;
    my $newPrice = sprintf "%6.2f", $curPrice * 1.2;
    my $parent = $priceNode->parentNode;
    my $newPriceNode = XML::LibXML::Element->new('price');
    $newPriceNode->addChild ( $doc->createTextNode( $newPrice ) );
    $parent->replaceChild ( $newPriceNode, $priceNode );
}
print $doc->toString(1);        # pretty print

Чтобы показать свое мастерство в DOM, мы затем получаем список ссылок на узлы цен в дереве и увеличиваем каждую цену на 20%. Поскольку текст (цена) внутри элемента может быть представлена более чем одним текстовым узлом, самый простой способ сделать это – извлечь цену из узла, увеличить и переформатировать ее и после этого полностью заменить начальный узел, вместо того чтобы изменять его на месте. Это, конечно, значительно сложнее, чем такое же преобразование в коде Perl в первой части.


Листинг 5. Упорядоченные выходные данные парсера дерева
   
<?xml version="1.0"?>
<pets>
  <cat>
    <name>Madness</name> <dob>1 February 2004</dob> 
<price>180.00</price>
  </cat>
  <dog>
    <name>Maggie</name> <dob>12 October 2002</dob> <price> 
90.00</price>
    <owner>Rosie</owner>
  </dog>
  <cat>
    <name>Little</name> <dob>23 June 2006</dob> <price> 
30.00</price>
  </cat>
  <gerbil>
    <name>nasty</name><dob>15 February 2006</dob><price>  
6.00</price>
  </gerbil>
  <hamster>
    <name>boris</name><dob>5 July 2006</dob><price>  
8.40</price>
  </hamster>
</pets>

Это традиционный подход, когда вы работаете с XML, используя стандартный парсер дерева. Исходный вариант в виде текста в XML-формате преобразуется в дерево DOM. Чтобы перемещаться по дереву и обходить узлы, можно следовать по связям от одного узла к другому или использовать команды типа XPath для получения наборов ссылок на узлы. Далее узлы можно редактировать, используя эти ссылки. После этого можно записать дерево обратно на диск или просто распечатать.

Для небольших и несложных деревьев использование XML::Simple обычно дешевле в плане расходов на разработку. Однако если XML- документ сколько-нибудь сложный, наличие таких методов как getElementsByTagName, склоняет чашу весов на сторону XML::LibXML. Хотя этот метод может работать медленнее, чем хорошо сделанный Perl и XML::Simple, он не требует написания и отладки кода.



В начало


Парсинг на основе событий: SAX

Простой программный интерфейс приложения для XML (Simple API for XML - SAX) подходит к вопросу парсинга совсем по другому. Этот метод изначально предполагает более высокие непроизводительные издержки. SAX представляет документ как серию событий и требует, чтобы вы указали ему, как на каждое из них реагировать. В число подобных событий входят start_document, end_document, start_element, end_element и символы. Полный список приведен в документе "Связывание Perl SAX 2.1" в разделе Ресурсы. Для любого документа программист на Perl должен предоставить набор методов-обработчиков, по одному для каждого типа событий.

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

Хотя XML::LibXML и имеет интерфейс SAX, он остается парсером DOM, следовательно, считывает весь документ в память и затем предоставляет к нему интерфейс, ориентированный на события. Зачастую это может быть удобно, однако он не работает с документами, которые не умещаются в памяти или являются потоками XML, как Jabber/XMPP. Поэтому мы будем использовать XML::SAX::ExpatXS. Этот модуль является оболочкой для испытанного парсера expat, написанного Джеймсом Кларком, и работает очень надежно и быстро.

Предположим, что у нас новый зоомагазин, похожий на тот, что был рассмотрен в части 1. В листинге 6 представлена часть инвентарной книги магазина.


Листинг 6. Большой зоомагазин Лиззи, pets2.xml
   
 <stock>
<item type="iguana" cost="124.42" location="stockroom" age="1"/>
<item type="pig" cost="15" location="floor" age="0.5"/>
<item type="parrot" cost="700" location="cage" age="6"/>
<item type="pig" cost="117.50" location="floor" age="3.2"/>
</stock>

Чтобы разобрать этот файл с помощью SAX2, нам потребуется что-нибудь для обработки событий, порожденных парсером. Самый простой обработчик событий - это программа записи, которая выводит какой-нибудь текст для каждого события. Код в листинге 7 разбирает наш новый XML.


Листинг 7. Разбор pets2.xml с помощью SAX
   
#!/usr/bin/perl -w
#use strict;
use XML::SAX::ParserFactory;
use XML::SAX::Writer;
my $writer = XML::SAX::Writer->new;

$XML::SAX::ParserPackage = "XML::SAX::ExpatXS";
my $parser = XML::SAX::ParserFactory->parser(Handler => $writer);

eval { $parser->parse_file('pets2.xml') };
die "can't parse Lizzie's stock file: $@"   if $@;

После этого XML выдает результат, показанный в листинге 8.


Листинг 8. Результаты парсера SAX
   
<?xml version='1.0'?><stock>
  <item cost='124.42' location='stockroom' type='iguana' age='1' />
  <item cost='15' location='floor' type='pig' age='0.5' />
  <item cost='700' location='cage' type='parrot' age='6' />
  <item cost='117.50' location='floor' type='pig' age='3.2' />
</stock>

Несколько моментов, на которые следует обращать внимание, используя ExpatXS:

  • Убедитесь в том, что все ваши инструментальные средства - это либо SAX, либо SAX2: не пытайтесь использовать их вместе. Если вы используете XML::Handler::YAWriter вместо XML::SAX::Writer в листинге 7, то вы не будете получать никаких сообщений об ошибке, но результатом будет по большей части мусор. Поскольку ExpatXS - это парсер SAX2, с ним нужно использовать записывающую программу SAX2.
  • Чтобы проверить парсер на ошибки, заключите парсер в оболочку eval и проверьте $@, а не $!.
  • Прежде чем использовать обработчики, их нужно создать. Важно понимать, что хотя с точки зрения программиста SAX-парсер представляет собой конвейер (данный вопрос подробнее рассматривается ниже), ), движущийся слева направо, инициализация производится справа налево. То есть конвейер выглядит как P > W, поэтому инициализировать нужно в обратном порядке: сначала W, потом P.


В начало


Драйверы и фильтры

Именно здесь вступает в игру SAX. SAX определяет поток событий: парсер создает последовательность событий и передает каждое из них обработчику. Представьте себе абстрактный модуль, который может работать как парсер и/или как обработчик. Как и парсер он может создавать события SAX. Но он также является обработчиком, который может взаимодействовать с любым стандартным событием SAX, просто "переодев шляпу"; затем он может принять роль парсера и отправить событие следующему обработчику. То есть он определяет набор методов по умолчанию, которые только передают события. Модуль, обрабатывающий эти методы – это XML::SAX::Base.

Чтобы определить все возможные обработчики событий SAX, программист расширяет XML::SAX::Base и переопределяет все интересующие его методы. Остальные события просто передаются дальше. Такие обработчики событий можно соединять в цепочки, создавая конвейеры, подобно командной строке UNIX. Есть четко установленные интерфейсы обработчиков и четко определенное содержание: XML.

Более того, этот подход можно применить на обоих концах канала. На одном конце канала генератором является парсер SAX2, который принимает XML-документ и создает события. На самом деле генератором может быть любой источник событий SAX. Например, вы можете создать модуль, который считывает таблицу в базе данных и выводит поток событий SAX. (Это реализовано в XML::Generator::DBI.)

Другой конец канала обычно получает события SAX и создает документ. XML::SAX::Writer как раз это и делает. Однако обработчик может точно так же вводить информацию в базу данных (XML::SAX::DBI).

Все это дает два больших преимущества. Во-первых, это стимулирует создание обработчиков SAX, выполняющих простые преобразования потока событий. Это сработало; уже сейчас существует сотни модулей Perl с открытым кодом, реализующих связывание SAX 2.1 (см. Ресурсы). Во-вторых, это значит, что разработчикам достаточно создать обработчики, которые обеспечивают тот минимум функциональных возможностей, который нужен для решения конкретной задачи, но отсутствует в существующих обработчиках. Оба преимущества фактически заменяют дорогое временя программистов заменяют дешевыми машинными ресурсами.



В начало


Подробнее о XML::SAX::Base

Разработка обработчиков с помощью XML::SAX::Base Кипа Хэмптона состоит из двух простых этапов. Во-первых, обработчик должен расширить базовый класс. Во-вторых, если есть необходимость, программист должен переопределить базовые методы. При этом можно или отменить событие, или вызвать метод, переопределенный в базовом классе. Важно, чтобы обработчик вызывал методы в суперклассе, а не методы в переопределяющем модуле (см. листинг 9).


Листинг 9. Использование XML::SAX::Base

package XyzHandler;
  use base qw(XML::SAX::Base); # extend it

  sub start_element {          # override methods as necessary
    my $self = shift;
    my $data = shift;          # parameter is a reference to a hash
    # transform/extract/copy data
    $self->SUPER::start_element($data);
  }



В начало


Заключение

В этой статье (второй из трех в серии) я представил краткий обзор очень сложного мира парсинга XML.

- -

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

После этого я представил SAX и парсинг на основе событий - метод, который вам следует применять в случае, если ваш XML-документ слишком велик или является непрерывным потоком. Выясняется, что инструментальные средства, разработанные для работы в таких условиях, ведут к абсолютно другому стилю программирования, весьма плодотворному: конвейеру SAX.

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



Ресурсы

Научиться

Получить продукты и технологии
  • XML::LibXML - один из лучших модулей Perl для парсинга XML-документов.(EN)

  • Спецификация Document Object Model: узнайте подробнее об этом не зависящем от языка и платформы интерфейсе, который позволяет программам и сценариям быстро получать доступ и обновлять содержимое, структуру и стиль документов. (EN)

  • Perl SAX 2.1 binding: в этом документе описана версия SAX, которая используется модулями Perl.(EN)

  • Perl: получите самую новую версию и испытайте ее в действии.(EN)

  • Огромная Perl-библиотека CPAN: в Comprehensive Perl Archive Network можно найти ссылки на все модули, отмеченные в данной статье.(EN)

  • PPM, Perl Package Manager для Windows: получите средства, которые позволят вам устанавливать, перемещать, обновлять и другими способами управлять использованием распространенных модулей Perl CPAN (таких как Tk и DBI) в ActivePerl.(EN)

  • XML::Simple Гранта Маклина: попробуйте модуль XML::Simple - простой API поверх встроенного модуля XML-парсинга.(EN)

  • Спецификация XML: полное описание расширяемого языка разметки (Extensible Markup Language - XML).(EN)

  • Введение в XML (Дуг Тидвелл (Doug Tidwell), developerWorks, август 2002 г.) (EN): в качестве более плавного введения в XML ознакомьтесь с этим пособием, где описано, как появился XML, какое значение он имеет для будущего электронной торговли, а также представлены различные программные интерфейсы и стандарты XML и два примера того, как компании решают проблемы при помощи XML.

  • XPath 1.0: получите спецификацию языка для навигации по дереву DOM. (EN)

  • Спецификация XSLT 1.0: узнайте о преобразовании одного XML-документа в другой. (EN)

  • Пробуем писать древовидный XML с Perl: работа с древовидными моделями документов (Паранд Даругар, developerWorks, июль 2000 г.) (EN): хорошее введение в древовидный парсинг XML в Perl.

  • Ознакомительное программное обеспечение от IBM: сиспользуйте в своем следующем проекте ознакомительное программное обеспечение, которое можно загрузить прямо с developerWorks.(EN)


Обсудить


Об авторе

Джим Диксон (Jim Dixon) - независимый разработчик, недавно вернувшийся в Сан Франциско, где он консультирует начинающие компании, занимающиеся Web 2.0, по вопросам Perl и Ruby. До этого он семь лет работал ведущим техническим специалистом работающего в Великобритании и США Интернет-провайдера, а также много разрабатывал программное обеспечение на Java/J2EE.




Выскажите мнение об этой странице


Пожалуйста, найдите минутку и заполните форму, чтобы повысить уровень сервиса.



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


UNIX – зарегистрированная торговая марка Open Group в Соединенных Штатах и других странах. Linux – торговая марка Linus Torvalds в Соединенных Штатах и других странах. Названия других компаний, продуктов и сервисов могут быть торговыми марками или знаками обслуживания других организаций. Другая компания, продукт или название услуги могут быть торговыми марками или знаками обслуживания, принадлежащими иным физическим или юридическим лицам.

IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты