Создание XMLданных при помощи XML Simple
Создание XML-данных при помощи XML::Simple
Упоминание «записать его на диск» возвращает нас обратно к методу создания XML-данных, который мы обещали показать. Вторая функция из XML: : Simple принимает ссылку на структуру данных и генерирует XML-данные: rootname определяет имя корневого элемента, мы могли бы использовать XMLdecl. чтобы добавить объявление XML print XMLout($queue, rootname =>"queue"),
В результате получаем (отступы сделаны для удобства чтения):
<queue> <account name="bobf" type="staff"
password="password" status="to_be_created"
fullname="Bob Fate" id="24-9C57" />
<account nanie="wendyf" type="faculty"
password="password"
status="to_be_created"
fullname="Wendy Fate" id="50-9057" />
</queue>
Мы получили отличный XML-код, но его формат несколько отличается от формата наших файлов с данными. Данные о каждой учетной записи представлены в виде атрибутов одного элемента <account> </ accojnt>, a не в виде вложенных элементов. В XML: :Simple есть несколько правил, руководствуясь которыми, он преобразовывает структуры данных. Два из них можно сформулировать так (а остальные можно найти в документации): «отдельные значения преобразуются в XML-атрибуты», а «ссылки на анонимные массивы преобразуются во вложенные XML-элементы».
Чтобы получить «верный» XML-документ («верный» означает «в том же стиле и того же формата, что и наши файлы данных»).
Кошмар, не правда ли? Но у нас есть варианты для выбора. Мы можем:
- Изменить формат наших файлов данных. Это похоже на крайнюю меру.
- Изменить способ, которым XML: :Simple анализирует наш файл. Чтобы получить такую структуру данных (Рисунок 3.6), мы могли бы использовать функцию XMLin() несколько иначе:
Squeue = XMLin("<queue>",Squeuecontents."</queue>",
forcearray=>1, keyattr => [""]):
Но если мы перекроим способ чтения данных, чтобы упростить запись, то потеряем семантику кэшей, упрощающих поиск и обработку данных. - Выполнить некую обработку данных после чтения, но до записи. Мы могли бы прочитать данные в нужную нам структуру (так же, как делали это раньше), применить эти данные в нужном месте, а затем преобразовать структуру данных в один из вариантов, получаемых модулем XML: : Simple, перед тем как записать ее.
Вариант номер 3 кажется более разумным, так что последуем ему. Вот подпрограмма, которая принимает одну структуру данных (Рисунок 3.5), и преобразует ее в другую структуру данных (Рисунок 3.6). Объяснение примера будет приведено позже:
sub TransforuiForWrite{ my $queueref = shift;
my Stoplevel = scalar each %$queueref;
foreach my $user (keys %{$queueref->{$toplevel}}){
my %innerhash =map {$_, [$queueref->
{$toplevel}{$user}{$J] }
keys %<$queueref->{Stoplevel}{$user}};
$innerhash{'login'} = [$user];
push @outputarray, \%innerhash; }
Soutputref = { Stoplevei => \@outnui.array};
return $outputref:
}
Теперь подробно рассмотрим подпрограмму TrarsformForWate().
Если вы сравните две структуры (Рисунок 3.5, Рисунок 3.6), то заметите в них кое-что общее: это внешний хэш, ключом которого в обоих случаях является account. В следующей строке видно, как получить имя этого ключа, запрашивая первый ключ из хэша, на который указывает $que-ueref:
my Stoplevel = scalar each :6$i;eref:
Интересно взглянуть на закулисную сторону создания этой структуры данных:
my %innernabh =
mар {$_. [$queuer-ef-><$toplevel){$use'-}{$_ M 1
keys %{$queueref->{$toplevelf{$user}};
В этом отрывке кода мы используем функцию "iap(), чтобы обойти все ключи, найденные во внутреннем хэше для каждой записи (т. е. login, type, password и status). Ключи возвращаются в такой строке:
keys %{$queueref->{$toplevel}{$user}};
Просматривая ключи, можно с помощью тар вернуть два значения для каждого из них: сам ключ и ссылку на анонимный массив, содержащий его значение:
шар {$_, [$queueref->{$topleve]}{$user}{$_n }
Список, возвращаемый тар(), выглядит так:
(login,[bobf], type,[staff], password,[password]...)
Он имеет формат ключ-значение, где значения хранятся как элементы анонимного массива. Этот список можно присвоить хэшу %innerhash, чтобы заполнить внутреннюю хэш-таблицу для получаемой структуры данных (my %innerhash =). Кроме того, к хэшу следует добавить ключ login, соответствующий рассматриваемому пользователю:
$innerhash{'login'} = [$user];
Структура данных, которую мы пытаемся создать, - это список подобных хэшей, поэтому после того как будет создан и определен внутренний хэш, необходимо добавить ссылку на него в конец списка, т. к. он и представляет получаемую структуру данных:
push @outputarray. \%innerhash:
Такую процедуру следует повторить для каждого ключа login из первоначальной структуры данных (один на каждую запись об учетной записи). После того как это будет сделано, у нас появится список ссылок на хэши в той форме, которая нам нужна. Мы создаем анонимный хэш с ключом, совпадающим с внешним ключом из первоначальной структуры данных, и значением, равным нашему списку хэшей. Ссылку на этот анонимный хэш можно возвратить обратно вызывающей программе. Вот и все:
Soutputref = { Stoplevel => \SO'..tputarray}: return Soutputre?:
Теперь, располагая &TransforrnForWrite(), мы можем написать программу для чтения, записи наших данных и работы с ними:
Squeue = XMLin("<queue>".$queuecontents."</queue>",keyattr => ["login"]);
print OUTPLITFILE XMLojt(Ti ansfonnFor Write($queuu), г ootname => "queue");
Записанные и прочитанные данные будут иметь один и тот же формат.
Перед тем как закрыть тему чтения и записи данных, следует избавиться от несоответствий:
1. Внимательные читатели, наверное, заметили, что одновременное использование XML: : Write г и XML: : Simple в одной и той же программе для записи данных в очередь может оказаться непростым делом. Если записывать данные при помощи XML: : Simple, то они будут вложены в корневой элемент по умолчанию. Если же применять XML: : Write г (или просто операторы print) для записи данных, вложения не произойдет, т. е. нам придется опять прибегнуть к хаку "<queue>". Squeuecontents. "</queue>". Возникает неудачный уровень синхронизации чтения-записи между программами, анализирующими и записывающими данные в XML-формате.
Чтобы избежать этой проблемы, надо будет использовать продвинутую возможность модуля XML: .'Simple: если XMLoutO передать параметр rootname с пустым значением или значением undef, то возвращаются данные в XML-формате без корневого элемента. В большинстве случаев так поступать не следует, потому что в результате образуется неправильный (синтаксически) документ, который невозможно проанализировать. Наша программа позволяет этим методом воспользоваться, но такую возможность не стоит применять необдуманно.
2. И хотя в примере этого нет, мы должны быть готовы к обработке ошибок анализа. Если файл содержит синтаксически неверные данные, то анализатор не справится и прекратит работу (согласно спецификации XML), остановив при этом и всю программу в случае, если вы не примете мер предосторожности. Самый распространенный способ справиться с этим из Perl - заключить оператор анализа в eval() и затем проверить содержимое переменной $@ после завершения работы анализатора. Например:
eval {$p->parse("<queue>".Squeuecontents."</queue>")};
if ($@) { сделать что-то для обработки ошибки перед выходом.. };
Другим решением было бы применение известного модуля из разряда XML: : Checker, т. к. он обрабатывает ошибки разбора аккуратнее.
Низкоуровневая библиотека компонентов
Теперь, когда мы умеем отследить данные на всех этапах, включая то, как они получаются, записываются, читаются и хранятся, можно перейти к рассмотрению их использования глубоко в недрах нашей системы учетных записей. Мы собираемся исследовать код, который действительно создает и удаляет пользователей. Ключевой момент этого раздела заключается в создании библиотеки повторно используемых компонентов. Чем лучше вам удастся разбить систему учетных записей на подпрограммы, тем проще будет внести лишь небольшие изменения, когда придет время переходить на другую операционную систему или что-либо менять. Это предупреждение может показаться ненужным, но единственное, что остается постоянным в системном администрировании, - это постоянные изменения.