Выпуск 2. Апрель 2013
← От редактора | Содержание | Удобное логирование с Log::Any →Преобразование XML в Perl-структуры с помощью XML::Simple
XML::Simple не рекомендуется к использованию для работы с XML в современном Perl, однако бывают ситуации, когда выбор уже сделан и необходимо поддерживать имеющийся код. Для таких случаев и была написана данная статья.
Преобразование XML в Perl-структуры и обратно довольно часто встречающаяся задача. Конечно, в большинстве таких случаев XML-файлы имеют простую структуру. Однако, решение совсем не тривиально.
XML::Simple
— очень популярный модуль времен, когда XML::LibXML
был большим и слишком сложным. С тех пор утекло много воды, появился Modern Perl, интерфейс XML::LibXML
существенно упростился, а XML::Simple
, наоборот, перестал рекомендоваться к использованию, причем самим автором модуля.
Не рекомендуется также использовать библиотеку для работы с большими или сложными XML. Модуль предоставляет простой интерфейс и достаточное кол-во настроек обработки — но платой за это является не совсем очевидная работа в сложных случаях. Проще говоря, приходится изрядно потрудиться, чтобы заставить его выдавать нужный результат на сложных структурах данных.
Создание XML
Мы будем рассматривать задачу создания XML-документа, потому что с разбором заданной структуры никаких проблем нет, и результат там практически всегда логичен и понятен.
Итак, на сервер приходит запрос списка объектов и их значений. На выходе мы хотим получить следующий результат:
<?xml version="1.0" encoding="UTF8"?>
<cats>
<cat name='Daisy'>4</cat>
<cat name='Abby'>5</cat>
</cats>
Попробуем построить документ, воспользуясь XML::Simple:
use XML::Simple;
my $xs = XML::Simple->new();
my $hashref = {
cats => {
cat => [
{
name => 'Daisy',
value => 4
},
{
name => 'Abby',
value => 5
},
]
}
};
say $xs->XMLout($hashref);
… на выходе получилось совсем не то, что нужно. Декларации XML-документа нет, все обернуто в <opt>
:
<opt>
<cats>
<cat name="Daisy" value="4" />
<cat name="Abby" value="5" />
</cats>
</opt>
После более детального ознакомления с документацией, напишем следующий код:
my $hashref = {
cat => [
{
name => 'Daisy',
content => 4
},
{
name => 'Abby',
content => 5
},
]
};
say $xs->XMLout(
$hashref,
XMLDecl => '<?xml version="1.0" encoding="UTF8"?>',
RootName => 'cats',
);
То, что нужно!
Итак, мы принудительно указали декларацию XML-документа, задали корневой элемент, а так же изменили value
на content
— это ключевое слово в имени ключа заставило XML::Simple
перенести значения объектов в содержимое тегов, а не в атрибуты. Как видно, довольно много опцией для такого простого файла.
Изменив value
на content
, мы использовали возможности преобразования данных которые нам предоставляет модуль. Однако, это не всегда удобно, хотя и, зачастую, повышает читаемость исходных данных. В большинстве же случаев можно обойтись стандартным поведением: по умолчанию модуль преобразовывает структуру вида { key => 'value' }
в xml вида <key attr=>"value" />
, а структуру {key => ['value']}
в <key>value</key>
. Т.е. каждая простая строка будет отображена в виде атрибута, а массив будет превращен во вложенные элементы.
Как же нужно изменить код, чтобы добавить новые значения?
<?xml version="1.0" encoding="UTF8"?>
<cats>
<cat name='Daisy'>
<age>4</age>
<weight>3.5</weight>
</cat>
<cat name='Abby'>
<age>5</age>
<weight>6</weight>
</cat>
</cats>
Просто добавив поля:
{
name => 'Daisy',
weight => 4,
age => 3.5,
},
Но мы получим не совсем то, что ожидалось:
<cat name="Daisy" age="3.5" weight="4" />
Более логичным было бы сделать следующим образом:
{
name => 'Daisy',
content => {
weight => 4,
age => 3.5,
},
},
Но на самом деле правильный вариант:
name => 'Daisy',
weight => {
content => 4,
},
age => {
content => 3.5,
},
Что в целом объяснимо — мы превращаем age
в структуру с ключевым словом content
, которое подставляет свое значение в содержимое тегов. Однако, из примера выше видно, что если в content
мы подставим структуру — то все особенные свойства сразу исчезнут и это будет обычный тег. Но что делать, если в исходных данных есть такое название столбца и вы хотите чтобы он был атрибутом, а не проявлял свои особенные свойства в самый неподходящий момент? Делаем так:
$xs->XMLout($hashref ,
XMLDecl => '<?xml version="1.0" encoding="UTF8"?>',
RootName => 'cats',
ContentKey => ''
);
И теперь ни один из тегов не будет рассмативаться как содержимое, а только как атрибуты. Если нужно, наоборот, добавить свои теги — то просто указываем их в качестве списка. Еще один важный момент — указанные элементы затрут список по умолчанию, так что в него нужно включать все теги, которые необходимы.
Также можно вообще запретить использование тегов в качестве атрибутов:
$xs->XMLout($hashref , NoAttr => 1);>
<cat>
<name>Abby</name>
<age>5</age>
<weight>6</weight>
</cat>
Разбор XML
После генерации XML-документа рассмотрим особенности разбора. В этом случае все несколько проще:
my $ref = $xs->XMLin($xml_file);
Если мы возьмем сгенерированный выше XML и передадим его парсеру — то получим не ту структуру из которой XML создавался. Так как тег name
заставит парсер создать структуру вида:
'cat' => {
'Daisy' => {
'weight' => '4',
'age' => '3.5'
},
'Abby' => {
'weight' => '6',
'age' => '5'
}
}
Чтоб этого избежать задаем опцию KeyAttr => ''
:
my $ref = $xs->XMLin($xml_file, KeyAttr => '');
'cat' => [
{
'weight' => '4',
'name' => 'Daisy',
'age' => '3.5'
},
{
'weight' => '6',
'name' => 'Abby',
'age' => '5'
}
]
Следующий нюанс — как видно выше, список объектов представляет собой массив хешей с атрибутами. Но что будет, если объект только один? Список превратится в хеш и нам придется отслеживать этот момент для правильной обработки. К сожалению, опции для нормального решения при разборе нет. Использование ForceArray => qw/cat/
приводит к вот таким последствиям:
'cat' => [
{
'weight' => [
'4'
],
'name' => [
'Daisy'
],
'age' => [
'3.5'
]
}
]
Опция GroupTags => { cats => 'cat' }
тоже не приводит к нужному эффекту. Причем ни в случае одного элемента, ни в случае нескольких.
Выводы
В целом, XML::Simple
реализует простой интерфейс к XML (что логично следует из названия модуля). Проблема в том, что этот интерфейс реализует большое кол-во опций и это приводит к нелогичному поведению и запутанности при работе с XML со сложной структурой. Так что, если нужно быстро создать или разобрать простой XML небольшого объема, то можно смело использовать этот модуль. Он достаточно быстр и стабилен. Но если необходимо работать со сложными структурами и большими объемами — то лучше использовать XML::LibXML
и аналоги. При более высоком пороге вхождения они дадут более предсказуемое поведение и высокую скорость обработки данных.
← От редактора | Содержание | Удобное логирование с Log::Any →