Благодаря тому, что XML довольно удобен для приложений с высоким уровнем взаимодействия, он постепенно становится стандартным форматом как для хранения данных, так и для хранения настроек приложения в конфигурационных файлах. В этой статье представлено описание программы, конфигурационные файлы которой создаются в формате XML, для демонстрации возможностей использования XML в приложениях. Демонстрационная программа написана на Perl и использует модули Perl, основанные на библиотеке LibXML2 проекта Gnome.
После краткого описания формата XML данная статья демонстрирует файл конфигурации в этом формате. Примеры кода показывают, как анализировать данный файл с использованием библиотеки LibXML2. Несмотря на то что пользователь может изменять параметры конфигурации прямым редактированием XML-файла, разработчику может потребоваться функциональность для модификации XML-файлов прямо в приложении, и в данной статье демонстрируется модификация документа XML на примерах добавления, удаления и изменения параметров; описана выгрузка XML-документа обратно в конфигурационный файл.
До начала реального использования LibXML2 необходимо первое знакомство с XML. Формат XML является текстовым форматом для хранения структурированных данных, доступных для любого языка программирования на любой платформе. Формат использует древовидную структуру хранения в тэгах, подобных HTML-тэгам.
Рассмотрим документ, показанный в листинге 1. Это упрощённый файл конфигурации из раздела Конфигурационный файл. Упрощения были сделаны для более наглядной демонстрации базовых концепций XML.
Листинг 1. Простой файл в формате XML
<?xml version="1.0" encoding="UTF-8"?> <files> <owner>root</owner> <action>delete</action> <age units="days">10</age> </files> |
Первой строкой листинга является декларация XML, которая говорит приложению, которое читает данный файл (XML-анализатору), с какой версией XML он имеет дело. Подавляющее большинство файлов, с которыми придётся иметь дело, написано с использованием версии 1.0, незначительное количество приложений использует версию 1.1. Также в декларации указана кодировка файла; большая часть XML-файлов написаны в UTF-8, но XML позволяет хранить данные в любой кодировке любого языка, даже без использования английского алфавита.
Далее идут элементы документа. Элемент начинается с открывающего тэга (такого как <files>) и заканчивается закрывающим тэгом (таким как </files>), отличающимся от открывающего тэга косой чертой (/).
Главной сущностью в объектной модели документа XML (DOM) являются объекты типа Node (узел). Моделью определены несколько типов Node, это Element (элемент) (такие как files или age в листинге 1), Attributes (атрибут) (units в листинге 1), и просто текст (как root или 10). Элементы могут иметь дочерние узлы. Например, элемент age имеет одного потомка, текстовый элемент 10. Элемент files имеет семь потомков, три из которых очевидны: это owner, action и age. Четверо остальных представляют собой пустые текстовые поля до и после элементов.
Анализаторы XML могут использовать данную иерархическую структуру для навигации по документу и для модификации содержимого документа. LibXML2 является одним из таких анализаторов, демонстрируемый пример приложения использует иерархическую структуру для работы с документом. На данный момент доступно большое количество анализаторов и библиотек для различных окружений. Такие скриптовые языки как Perl или Python имеют расширения, поддерживающие LibXML2, который можно считать лучшим XML-анализатором для UNIX.
Рассмотрим пример файла конфигурации.
Пример приложения читает набор действий для выполнения над определёнными файлами. Действия и файлы заданы в конфигурационном файле, который расположен где угодно в файловой системе. Подобный этому файл конфигурации может быть использован, например, для конфигурации планировщика cron. Данный XML-файл конфигурации определяет пути к каталогам, содержащим файлы, и действия, которые должны быть произведены на базе определённых конфигурируемых критериев, таких как владелец файла и дата создания файла (см. листинг 2).
Листинг 2. Конфигурационный файл
<?xml version="1.0" encoding="UTF-8"?>
<filesystem>
<path>
<dirname>/var</dirname>
<files>
<owner>root</owner>
<action>delete</action>
<age units="days">10</age>
</files>
<files>
<owner>any</owner>
<action>delete</action>
<age units="hours">96</age>
</files>
</path>
<path>
<dirname>/tmp</dirname>
<files>
<owner>any</owner>
<action>delete</action>
<age units="hours">24</age>
</files>
</path>
</filesystem>
|
В данном примере корневым элементом является элемент filesystem, который содержит два элемента path, содержащих имя каталога и один или несколько элементов files. Каждый элемент files задаёт действие, которое необходимо выполнить приложению, основываясь на владельце файла и возрасте файла, заданном в единицах, указанных в атрибуте units элемента age. Пробелы очень важны в XML-документе: с точки зрения структуры, каждый пустой элемент формирует отдельный текстовый узел типа Text.
В реальной среде хорошо написанное UNIX-приложение должно иметь возможность не только считывать данные и оперировать в зависимости от прочитанных значений, но и иметь возможность модифицировать данные по желанию пользователя.
Настало время обратить внимание на приложение, использующее данные в формате XML.
Далее в этой статье приведены и описаны примеры кода для анализа и управления файлами XML. Данные примеры читают и модифицируют конфигурационный файл, но продемонстрированные концепции можно использовать для любых других задач, встречающихся в повседневной жизни UNIX-разработчика. Базируясь на LibXML2, можно использовать данные концепции в любом создаваемом UNIX-приложении.
Статья демонстрирует работу с XML на базе библиотеки LibXML2 для Perl. Значительная часть документации в Интернет описывает программирование в Java™ или Microsoft® Visual Studio, очевидно, неинтересные пользователям и разработчикам UNIX, которым гораздо более полезен Perl. В листинге 3 показаны модули Perl, необходимые для анализа XML-документа.
Листинг 3. Используемые модули
XML::LibXML XML::LibXML::Common XML::NamespaceSupport XML::SAX |
Код в последующих разделах статьи – типичный инструментарий, который разделён по назначению на три части: анализ, модификация данных, экспортирование документа.
Во время загрузки и анализа данные в приложении будут скорее всего загружаться в переменные Perl типа списков или хеш-таблиц, однако каждый программист имеет свои предпочтения в выборе способа хранения данных; мы не настаиваем на использовании наших типов переменных. Демонстрируемый код просто печатает данные, демонстрируя корректность работы с данными.
Программа производит модификацию данных путём добавления, редактирования и удаления элементов документа XML. Как правило эта модификация будет результатом каких-либо действий пользователя.
На стадии экспортирования программа записывает документ обратно в файл конфигурации.
Первым этапом в чтении XML является загрузка данных и распознавание их в объект класса Document, после чего появляется возможность навигации по дереву DOM для получения доступа к выбранным узлам. В листинге 4 продемонстрированы эти действия.
Листинг 4. Пример загрузки и анализа файла example.xml
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file("example.xml");
$filesystem = $doc->getDocumentElement();
@nodes=$filesystem->childNodes;
foreach $node (@nodes) {
if($node->nodeType==ELEMENT_NODE) { # игнорируем текстовые узлы
# ищем только первое совпадение
@dirnames = $node->getElementsByTagName("dirname")->item(0);
foreach $dirname (@dirnames) {
print "dirname: " . $dirname->textContent . "\n";
# помещаем его в массив
}
# получаем всех потомков
@files = $node->getChildrenByTagName("files");
foreach $file (@files) {
foreach $values ($file->childNodes) {
# игнорируем текстовые узлы
if($values->nodeType!=XML_TEXT_NODE) {
if($values->nodeName() eq "age") {
# проверка атрибутов, в противном случае
# используем значение по умолчанию 'hours'
if($values->hasAttributes()) {
print $values->nodeName() . ": " . $values->textContent;
print " " . $values->attributes->item(0)->value();
print "\n";
} else {
print $values->nodeName() . ": " . $values->textContent;
print " hours\n";
}
# вычисляем расширенное значение по атрибуту units
# и сохраняем его в хеш-таблице, связав с $dirname.
} else {
print $values->nodeName() . ": " . $values->textContent;
print "\n";
# сохраняем его в хеш-таблице, связав с $dirname.
# может быть несколько элементов для каждого $dirname,
# так что, возможно, стоит использовать массив в хеш-таблице
}
}
}
}
}
}
|
Сначала в листинге 4 создаётся анализатор и загружается XML из файла в переменную XML::LibXML::Document. Данный объект содержит полное дерево XML и имеет методы, предназначенные для поиска, экспортирования, проверки и создания узлов. Некоторые из этих методов будут обсуждаться в следующих разделах статьи. Для получения корневого элемента дерева необходимо вызвать метод getDocumentElement(), и начав с верхнего узла, можно выполнять навигацию по всему дереву XML.
Главный цикл foreach проходит через каждый дочерний элемент внутри родительского элемента filesystem, исключая текстовые элементы. Метод getElementsByTagName() ищет в элементе потомков на любом уровне вложенности с заданным именем (dirname в данном случае) и возвращает объект типа NodeList. Каждый элемент path содержит только одно имя каталога в элементе dirname, поэтому используется только первое возвращённое данным вызовом значение dirname. Текстовые узлы исключаются из результатов поиска, так как узлы типа Text не поддерживают метод getElementsByTagName(), и обращение к нему вызовет фатальную ошибку Perl.
В одном элементе path возможны несколько элементов files, поэтому код обрабатывает их в цикле, используя метод getChildrenByTagName(), который похож на getElementsByTagName(), но ищет только прямых потомков элемента. Таким образом, можно прочитать элементы files и проанализировать далее на один уровень ниже для получения элементов owner, action и age. После получения этих элементов необходимо вызвать textContent для получения содержимого элемента. Данный метод удобно подменяет операции считывания элемента TEXT и выбора значения этого элемента. Вот как это выглядит:
print $values->nodeName() . ": " print $values->firstChild()->nodeValue(); |
В случае элемента age возможно чтение необязательного атрибута units, это осуществляется использованием функций hasAttributes() и Attributes. При отсутствии данного атрибута программа будет использовать значение по умолчанию, hour.
Далее освещается модификация данных: добавление, удаление и редактирование параметров.
На данный момент приведённый выше код представляет собой весьма полезный инструментарий разработчика. Пользователь может легко модифицировать поведение программы путём прямого редактирования конфигурационного файла, однако опытному разработчику стоит использовать XML-функции для редактирования документа прямо в программе. Например, в данной программе можно реализовать меню для модификации, добавления или удаления действия над файлом. Приведённый в листинге 5 код реализует модификацию документа.
Листинг 5. Модификация документа XML.
$newnode = $doc->createElement("path");
$newdirnode = $doc->createElement("dirname");
$newdirnode->appendText("/root");
$newfilesnode = $doc->createElement("files");
$newownernode = $doc->createElement("owner");
$newownernode->appendText("any");
$newactionnode = $doc->createElement("action");
$newactionnode->appendText("archive");
$newagenode = $doc->createElement("age");
$newagenode->appendText("30");
$newagenode->setAttribute("units","days");
$newfilesnode->addChild($newownernode);
$newfilesnode->addChild($newactionnode);
$newfilesnode->addChild($newagenode);
$newnode->addChild($newdirnode);
$newnode->addChild($newfilesnode);
$filesystem->addChild($newnode);
|
Код в листинге 5 создаёт и заполняет все подэлементы элемента path, затем добавляет созданный элемент в корневой элемент filesystem. Каждый элемент создается использованием метода createElement() класса XML::LibXML::Document (элементы создаются объектом класса Document). Данный метод возвращает пустой элемент, который не имеет связи с элементами документа. Затем можно заполнить элемент, используя метод appendText() класса XML::LibXML::Element. Данный метод является удобной альтернативой созданию нового элемента типа TEXT, его наполнения и добавления к элементу. Добавление атрибутов осуществляется вызовом метода setAttribute(), который автоматически создаёт новый узел типа ATTRIBUTE в случае отсутствия у модифицируемого элемента атрибута с таким именем.
После создания и наполнения элементов необходимо вызвать метод addChild() родительского элемента, задав параметром вызова новый дочерний элемент. В приведённом выше коде элемент $newownernode становится потомком элемента $newfilesnode. Все элементы расположены в документе в соответствии с порядком добавления. Если требуется задать другое расположение, то надо использовать методы insertAfter() или insertBefore().
Каждый элемент добавляется к родительскому до того момента, когда в конце концов элемент добавляется к реально существующему элементу в документе. Приведённый выше пример добавляет к корневому элементы filesystem. В случае, когда данный документ создаётся с нуля, необходимо вызывать addChild() к самому объекту Document для добавления корневого элемента, и только затем добавлять к полученному корню дочерние элементы.
Как объяснялось ранее, XML в листинге 2 написан в понятном для пользователя формате. Переносы строк и отступы (табуляция) делают формат более читабельным, каждый из этих символов (пробелы и символы табуляции) считывается анализатором XML как элемент типа TEXT. Пример в листинге 5 не добавляет никаких элементов типа TEXT, таким образом, вывод приложения не будет иметь никаких переносов строк или отступов от начала строки. Если необходим экспорт в текст более читабельного вида, с отступами, переносами и выравниванием, надо добавить элементы типа TEXT, используя либо класс XML::LibXML::Text, либо метод объекта Document createTextNode(). Данный метод возвращает элемент, который можно добавлять в дерево таким же образом, как и обычный элемент с данными.
Для изменения содержимого файла программой можно либо напрямую задать значение текстового узла методом nodeValue(), либо полностью заменить элемент следующим образом:
$newnode = $doc->createElement("owner");
$newnode->appendText("toor");
$oldnode->replaceNode($newnode);
|
Удалить элемент можно несколькими способами. Один – это простое удаление из структуры дерева, например, таким образом:
$file->unbindNode(); |
После нахождения элемента, который необходимо удалить, можно его удалить описанным выше способом, однако он будет удален из структуры дерева, но не из документа. Вызов этой функции не уничтожает структуру данных, пока не завершится работа программы. Если необходимо переместить элемент в другую часть дерева, то можно вызвать метод addNode() с этим элементом в качестве параметра для добавления в другое место документа. Для удаления элемента с уничтожением хранимых в нем данных необходимо вызывать функции removeChild() или removeChildNodes().
В некоторых языках программирования сохранение XML-документа в файл может быть довольно нудной процедурой, однако использование LibXML2 делает сохранение очень простой операцией:
$doc->toFile("example.xml");
|
Данная операция является самой простой из всех, совершаемых над документом. После завершения редактирования документа, хранящегося в памяти приложения, вызовом данного метода приложение записывает документ обратно в конфигурационный файл. Для сохранения XML в строковую переменную либо в дескриптор открытого файла необходимо использовать функции Perl toString() и, соответственно, toFH(), что даёт большую гибкость при проектировании собственных приложений.
Проект Gnome оказал ценную услугу предоставлением библиотек LibXML2 и сопутствующих модулей Perl. Данная статья освещает три важных этапа использования и обработки конфигурационных XML-файлов. Стоит отметить, что этап анализа может показаться самым сложным, так как требует рекурсии для распознавания всего дерева XML. Операции модификации документа XML в памяти несложны, а экспортирование модифицированной конфигурации в LibXML2 – весьма простая задача.
Несмотря на некоторые идеологические разногласия формата XML и парадигмы UNIX, XML предоставляет разработчикам мощные возможности для управления данными в приложениях. Древовидная структура предоставляет более гибкое представление данных, чем простой формат хранения базы данных. При разработке новых приложений или модификации старых использование стандартизованных файлов конфигурации в XML-формате может быть легко произведено с использованием библиотек LibXML2.
Научиться
- Config file processing with LibXML2: оригинал статьи (EN).
- LibXML: Web-cайт с информацией об анализаторе текста XML С и XML-инструментарии от проекта Gnome.(EN)
- XML tutorial (EN): статья об использовании XML в приложениях.
- Understanding DOM (EN) (июль 2003, developerWorks): статья с описанием объектной модели документа (DOM), которая позволяет разработчику работать с XML.
- Раздел XML на сайте developerWorks.
- XML parsers: обширный список XML-анализаторов.(EN)
- Команда IBM developerWorks проводит по всему миру сотни бесплатных технических консультаций.(EN)
Получить продукты и технологии
- Cpan.org: модули для PERL.(EN)
- XML toolkit: XML-инструментарий для Perl от developerWorks.(EN)
- IBM trial software: ознакомительные версии программного обеспечения для разработчиков, которые можно загрузить прямо со страницы сообщества developerWorks.(EN)
Обсудить
- Примите участие в обсуждении материала на форуме.
- Блоги developerWorks.(EN)
-
Примите участие в форумах AIX и UNIX:(EN)
- Управление кластерными системами
- Поддержка IBM
- Производительность — технический форум
- Виртуализация — технический форум