Приветствую.
Решил заделиться имеющимися знаниями и навыками работы с перловым модулем для работы с XML документами под названием XML::Twig. В этом документе я постараюсь осветить самые основные моменты использования данного модуля. Заранее хочу сказать что я не претендую на "последнюю инстанцию" и для полной точности нужно обращаться к официальной документации
Начнемс. Для примера возьмем xml файл следующего содержания:
<document pubdate="2008-06-06">
<chapter>
<section userlevel="1"><title>Атрибуты подпрограммы:</title>
<list>
<item>locked</item>
<item>method</item>
<item>lvalue</item>
</list>
</section>
<section userlevel="2">
<para>Разыменовывающие префиксы:</para>
<list type="bullet">
<item><bold>Скаляр</bold> $ </item>
<item><bold>Массив</bold> @ </item>
<item><bold>Хеш</bold> % </item>
</list>
</section>
<para>"Выполнять линейный просмотр в ассоциативном массиве —
все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</chapter>
</document>
назовем его example.xml.
Хочу сразу напомнить что в начало скрипта нужно поместить директиву: use XML::Twig, дабы подключить сам модуль. Создадим обьект XML::Twig и откроем вышеуказаный файл:
my $twig = XML::Twig->new();
$twig->parsefile("example.xml");
возьмем корневой элемент:
my $root = $twig->root;
для радующего глаз вывода воспользуемся методом set_pretty_print со стилем indented:
$root->set_pretty_print('indented');
в данном случае элемент $root сейчас содержит весь контент содержимого файла example.xml. Чтобы посмотреть содержимое переменной $root и вообще любого обьекта XML::Twig нужно воспользоваться методом print(), т. е. $root->print. Вызывать print можно только после парсинга, т е в нашем случае после $twig->parsefile("example.xml"), ибо как не трудно догадаться выводить ему будет просто нечего.
Замена тега
Чтобы изменить тег <chapter> на <part> нужно:
my $chapter = $root->first_child('chapter');
$chapter->set_tag('part');
Метод first_child возвращает первого "ребенка" элемента $root с названием part. После используем метод set_tag чтобы задать тегу part новое название — chapter. После этих манипуляций документ примет вид:
<document pubdate="2008-06-06">
<part>
....................................................
</part>
</document>
Обработка потомков
К примеру есть задача найти все теги <section> в теге <part> и удалить их, оставив при этом их содержание. Воспользуемся методом children:
foreach my $section ( $part->children('section') ) {
$section->erase;
}
метод children возвращает список детей элемнта, в данном случае детей под названием section. Если в скобках ничего не указывать, т е $part->children(), то метод вернет список всех детей, т е (section, section, para). Метод erase более подробно описывается ниже.
после этого документ примет вид:
<document pubdate="2008-06-06">
<chapter>
<title>Атрибуты подпрограммы:</title>
<list>
<item>locked</item>
<item>method</item>
<item>lvalue</item>
</list>
<para>Разыменовывающие префиксы:</para>
<list type="bullet">
<item><bold>Скаляр</bold> $ </item>
<item><bold>Массив</bold> @ </item>
<item><bold>Хеш</bold> % </item>
</list>
<para>"Выполнять линейный просмотр в ассоциативном массиве — все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</chapter>
</document>
Работа с атрибутами
Удаление атрибутов
Предположим что нужно удалить атрибуты у root'ового документа, для этого воспользуемся методом del_atts, т е $root->del_atts, после чего тег <document pubdate="2008-06-06"> станет просто <document>.
Добавление атрибутов
Добавим к тегу
my $para = $chapter->first_child('para');
$para->set_att('attention' => '1');
Результат:
<document pubdate="2008-06-06">
<chapter>
................................
<para attention="1">"Выполнять линейный просмотр в ассоциативном массиве — все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</chapter>
</document>
Извлечение содержимого тега
Для извлечения содержимого тега <title> нужно воспользоваться методом text:
my $title = $section->first_child('title');
$title_content = $title->text;
print $title_content,"\n";
Результатом будет строка: "Атрибуты подпрограммы:"
Метод text возвращает содержимое тега исключая любые другие теги. Т е если к примеру document_title будет:
<document_title>
<title1>Title 1</title1>
<title2>Title 2</title2>
</document_title>
и выполнить $document_title->text вывод будет: Title 1Title 2
Удаление тега
Для удаления тегов есть два метода, erase и delete. Erase удаляет сам тег, оставляя все содержимое. Delete также удаляет сам тег, но в отличие от erase также удаляет все его содержимое. К примеру:
my $section = $para->first_child('section');
$section->erase;
Результат будет:
<document pubdate="2008-06-06">
<document_title>Some title</document_title>
<part>
<title>Атрибуты подпрограммы:</title>
<list>
<item>locked</item>
<item>method</item>
<item>lvalue</item>
</list>
<section userlevel="2">
..............................................................
Если использовать delete, т е:
$section->delete;
Результат будет:
<document pubdate="2008-06-06">
<document_title>Some title</document_title>
<part>
<section userlevel="2">
<para>Разыменовывающие префиксы:</para>
<list type="bullet">
<item><bold>Скаляр</bold> $ </item>
<item><bold>Массив</bold> @ </item>
<item><bold>Хеш</bold> % </item>
</list>
</section>
<para>"Выполнять линейный просмотр в ассоциативном массиве — все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</part>
</document>
Как видно от первой секции ничего не осталось.
Сохранение в файл
Для сохранения в файл в XML::Twig есть метод с незамысловатым названием print_to_file. Пример использования:
my $twig = XML::Twig->new();
$twig->parsefile("example.xml");
my $root = $twig->root;
$root->set_pretty_print('indented');
my $part = $root->first_child('part');
my $section = $part->first_child('section');
$section->delete;
$twig->print_to_file("result.xml");
Измененный контент example.xml будет сохранен в файл result.xml.
Метод get_xpath
К примеру есть необходимость заменить все теги <item> на <listitem>. Для этой цели можно использоваться методом get_xpath. Сей замечательный метод возвращает список всех тегов которые удовлетворяют значению в скобках, '//' используется для того чтобы получить всех потомков.
my @item_collector = $root->get_xpath("//item");
foreach (@item_collector) {
$_->set_tag('listitem');
}
После последней манипуляции документ будет выглядить так:
<document pubdate="2008-06-06">
<document_title>Some title</document_title>
<part>
<section userlevel="1">
<title>Атрибуты подпрограммы:</title>
<list>
<listitem>locked</listitem>
<listitem>method</listitem>
<listitem>lvalue</listitem>
</list>
</section>
<section userlevel="2">
<para>Разыменовывающие префиксы:</para>
<list type="bullet">
<listitem><bold>Скаляр</bold> $ </listitem>
<listitem><bold>Массив</bold> @ </listitem>
<listitem><bold>Хеш</bold> % </listitem>
</list>
</section>
<para>"Выполнять линейный просмотр в ассоциативном массиве —
все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</part>
</document>
Все теги <item> содержащиеся в элементе $root были изменены на <listitem>.
Дополнительные примеры:
1) Нужно поменять все теги list в зависимости от атрибута, если list без атрибутов, то изменить его на orderlist, а если с атрибутом type и последний равен bullet, то изменить его на bulletlist при этом удалив атрибуты. Воспользуемся вышеупомятым методом get_xpath:
my @list_collector = ($root->get_xpath("//list"));
foreach my $list (@list_collector) {
if ($list->has_no_atts) {
$list->set_tag('orderlist');
}
if ($list->has_atts && $list->att('type') eq 'bullet') {
$list->set_tag('bulletlist');
$list->del_atts;
}
}
Результат:
<document pubdate="2008-06-06">
<chapter>
<section userlevel="1">
<title>Атрибуты подпрограммы:</title>
<orderlist>
<item>locked</item>
<item>method</item>
<item>lvalue</item>
</orderlist>
</section>
<section userlevel="2">
<para>Разыменовывающие префиксы:</para>
<bulletlist>
<item><bold>Скаляр</bold> $ </item>
<item><bold>Массив</bold> @ </item>
<item><bold>Хеш</bold> % </item>
</bulletlist>
</section>
<para>"Выполнять линейный просмотр в ассоциативном массиве —
все равно что пытаться забить кого-нибудь до смерти заряженным Узи" Ларри Уолл</para>
</chapter>
</document>
2)Скопировать все теги section с содержимым в отдельный файл, предварительно заключив в тег document. В этом примере воспользуемся модулем FileHandle, как результат в начале нужно прописать use FileHandle. Если сей модуль не установлен, в Linux' е его можно поставить командой: cpan install FileHandle
my @section_collector = $root->get_xpath("//section");
my $counter = 0;
foreach my $section (@section_collector) {
#Создаем новый пустой тег document
my $document = XML::Twig::Elt->new( document => '' );
$document->set_att( 'docref', "document$counter" );
#вставляем элемент $section с содержимым в тег document
$section->move( first_child => $document );
#сохраняем элемент document в файл
my $fh = FileHandle->new();
$fh->open(">result$counter.xml");
$document->print($fh,"indented");
$fh->close;
$counter++;
}
в результате в текущем каталоге появятся два файла: result0.xml и result1.xml
PS: Если вы нашли ошибки/неточности, сообщите пожалуйста. Обоснованная критика принимается :)
9 comments:
Спасибо за описание, очень толковое! Есть пару вопросов:
1. Много ли памяти кушает этот парсер при разборе хмл документа?
2. Какие обработчики ошибок с ним лучше использовать, как получить ошибки?
Та не за что, я старался :)
1)Увы не могу сказать, ибо работать приходилось только с XML::Twig'ом
2)Да как-то не думал об этом... он и сам по себе выдает достаточно внятные сообщения об ошибках, во всяком случае у меня никаких проблем с дебагом не возникало.
Жаль, что не уделено внимание созданию файлов или частей XML.
"Чтобы что-то распарсить, надо что-то запаковать" :)
Создание XML.
Создаём новый Twig:
my $twig = XML::Twig->new(
pretty_print => 'indented',
empty_tags => 'expand',
);
указав красивый вывод и завершение тегов в стиле HTML (т.е. всегда использовать закрываюший тег).
Создаём корневой элемент:
my $root = XML::Twig::Elt->new(
'entry' => {
'xmlns' => 'http://www.w3.org/2005/Atom',
'xmlns:media' => 'http://search.yahoo.com/mrss/',
'xmlns:gphoto' => 'http://schemas.google.com/photos/2007',
}
);
Связываем созданный элемент с корнем нашего Twig:
$twig->set_root($root);
Создаём другой элемент
my $title = XML::Twig::Elt->new('title' => {'type' => 'text'}, 'Title');
Добавляем его в качестве потомка корневого:
$title->paste(last_child => $root);
Печатаем на экран, что мы получили:
$twig->print();
Освобождаем память:
$twig->purge();
На выходе получим нечто типа
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gphoto="http://schemas.google.com/photos/2007" xmlns:media="http://search.yahoo.com/mrss/">
<title type="text">Title</title>
</entry>
@andy_shev
Прошу прощения что сразу не отреагировал на ваш комментарий. Есть несколько дополнений:
1) новый элемент можно создать от любого объекта твига методом new, к примеру
my $document = $title->new('document'=>{'docref'=>"document"},'example')
результатом будет тег
<document docref="document">example</document>
2) также есть метод insert_new_elt, приведу пример использования:
$document->insert_new_elt('first_child','text'=>{'align'=>'center','color'=>'grey'},'Here is the text');
результатом будет
<document><text align="center" color="grey">Here is the text</text></document>
Как только появится время - расширю статью в направлении геренации XML
@Stas
Спасибо за дополнения.
Однако, не от любого объекта получается, как Вы указали, а от объекта типа XML::Twig::Elt либо я что-то не так делаю.
Вот только что написал маленький пример, XML::Twig::Elt я там не испльзовал:
#!/usr/bin/perl
use warnings;
use strict;
use XML::Twig;
my $t = XML::Twig->new();
$t->set_pretty_print('indented');
$t->parse('<document><first_element/></document>');
my $root = $t->root;
my $first_element = $root->first_child('first_element');
$first_element->insert_new_elt('after',
'second_element','Second Element');
$root->print;
результат будет:
<document>
<first_element/>
<second_element>Second Element</second_element>
</document>
и еще один:
#!/usr/bin/perl
use warnings;
use strict;
use XML::Twig;
my $t = XML::Twig->new();
$t->set_pretty_print('indented');
$t->parse('<document><first_element/>
</document>');
my $root = $t->root;
my $first_element = $root->first_child('first_element');
my $second_element = $first_element->new('second_element','Second Element text');
$second_element->move('last_child',$root);
$root->print;
результат будет:
<document>
<first_element/>
<second_element>Second Element text</second_element>
</document>
XML::Twig::Elt я нигде не использовал
Мне кажется эксперимент не чистый.
Я говорю о создании документа, а не о расширении существующего.
@ andy_shev
Вы писали:
Однако, не от любого объекта получается, как Вы указали, а от объекта типа XML::Twig::Elt либо я что-то не так делаю.
Я подразумевал объекты либо XML::Twig либо XML::Twig::Elt
Post a Comment