Анализ XML с помощью SAX

Задача
Необходимо проанализировать XML-документ и отформатировать его на основе событий. Например, когда анализатор встречается с новым открывающим или закрывающим тегом элемента. Допустим, требуется преобразовать RSS-рассылку в HTML.

Решение
Используйте анализирующие функции XML-расширения PHP:

$xml = xml_parser_create();
$obj = new Parser_Object; // класс, принимающий участие в анализе
xml_set_object($xml,$obj);
xml_set_element_handler($xml, 'start_element', 'end_element');
xml_set_character_data_handler($xml, 'character_data');
xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, false);
$fp = fopen('data.xml', 'r')
or die("Can't read XML data.");
while ($data = fread($fp, 4096)) {
xml_parse($xml, $data, feof($fp))
or die("Can't parse XML data");
}
fclose($fp);
xml_parser_free($xml);

Обсуждение
Анализирующим XML-функциям для работы требуется библиотека expat. Но поскольку версии веб-сервера Apache 1.3.7 и выше комплектуются библиотекой expat, эта библиотека уже установлена на большинстве машин. Поскольку в PHP эти функции доступны по умолчанию, нет необходимости явным образом конфигурировать поддержку
XML в PHP.

Библиотека expat анализирует XML-документы и позволяет настроить анализатор таким образом, чтобы он вызывал нужные функции при столкновении с различными частями файла, такими как открывающий или закрывающий тег элемента или символьные данные
(текст внутри тегов).


Основываясь на имени тега, можно выбирать между форматированием или игнорированием данных. Это методика известна как анализ на основе событий и противостоит DOM XML, использующей анализатор на основе дерева.
Популярным видом API для анализа XML на основе событий является SAX (Simple API for XML – простой API для XML). Разработанный изначально для Java, SAX получил распространение и в других языках.

Функции XML в PHP поддерживают принятые в SAX соглашения. Более подробную информацию о последней версии SAX – SAX2 см. в книге Дэвида Браунела (David Brownell) «SAX2», O’Reilly.
PHP поддерживает два интерфейса для библиотеки expat: процедурный и объектно-ориентированный. Поскольку процедурный интерфейс вынуждает использовать глобальные переменные для выполнения сколько-нибудь существенной задачи, мы предпочитаем его объектно-ориентированную версию. С помощью объектно-ориентированного ин-терфейса можно связать объект с анализатором и взаимодействовать
с объектом во время обработки XML-документа. Такой подход позволяет использовать собственные свойства объекта вместо глобальных переменных.

Ниже приведен пример использования данной библиотеки, показывающий, как обрабатывать RSS-рассылку и преобразовывать ее в HTML.

Сценарий начинается со стандартного кода обработки XML, следующего за созданием объекта для специального анализа RSS:

$xml = xml_parser_create();
$rss = new pc_RSS_parser;
xml_set_object($xml, $rss);
xml_set_element_handler($xml, 'start_element', 'end_element');
xml_set_character_data_handler($xml, 'character_data');
xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, false);
$feed = 'http://pear.php.net/rss.php';
$fp = fopen($feed, 'r')
or die("Can't read RSS data.");
while ($data = fread($fp, 4096)) {
xml_parse($xml, $data, feof($fp))
or die("Can't parse RSS data");
}
fclose($fp);
xml_parser_free($xml);

После создания нового анализатора XML и экземпляра класса pc_RSS_parser задается конфигурация анализатора.


Для начала объект связывается с анализатором; это действие дает указание анализатору вызывать методы объекта, а не глобальные функции. Затем вызываются функции для указания имен методов xml_set_element_handler() и xml_set_character_data_handler(), которые анализатор должен вызывать, когда встречает элементы и символьные данные. Первым аргументом обеих функций является экземпляр анализатора, остальные аргументы – это имена самих вызываемых функций. В случае функ-
ции xml_set_element_handler() второй и третий аргументы – это функции, которые должны быть вызваны, соответственно, при встрече открывающего и закрывающего тегов. Функция xml_set_character_data_handler() принимает только один дополнительный аргумент – имя функции, которую нужно вызвать для обработки символьных данных.

Поскольку с нашим анализатором был связан объект, то в момент, когда анализатор обнаруживает строку data, он вызывает метод $rss->start_element() при достижении тега , метод $rss->character_data() – при достижении data и метод $rss->end_element() – при достижении тега . Анализатор нельзя сконфигурировать для автоматического вызова уникального метода для каждого конкретноготега – вы должны организовать обработку различных тегов самостоятельно.


Так, пакет PEAR XML_Transform предоставляет простой способ назначения обработчиков на основе тегов.
Последняя опция конфигурации анализатора XML запрещает автоматическое преобразование анализатором всех тегов в верхний регистр.

По умолчанию анализатор записывает теги большими буквами, поэтому теги и становятся одним и тем же элементом. Так как XML чувствителен к значению регистра, а большинство рассылок используют имена в нижнем регистре, эта функциональная возможность должна быть принудительно запрещена. После завершения конфигурирования анализатора ему передаются данные:

$feed = 'http://pear.php.net/rss.php';
$fp = fopen($feed, 'r')
or die("Can't read RSS data.");
while ($data = fread($fp, 4096)) {
xml_parse($xml, $data, feof($fp))
or die("Can't parse RSS data");
}
fclose($fp);

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

Хотя PHP и прекращает работу всех анализаторов при завершении запроса, можно закрыть анализатор вручную с помощью вызова функции xml_parser_free().

Теперь, когда основной процесс анализа определен, добавьте классы pc_RSS_item и pc_RSS_parser для обработки RSS-документа, как показано в примерах 12.1 и 12.2.

Пример 12.1. pc_RSS_item
class pc_RSS_item {
var $title = '';
var $description = '';
var $link = '';
function display() {
printf('

%s
%s

',
$this->link,htmlspecialchars($this->title),
htmlspecialchars($this->description));
}
}

Пример 12.2. pc_RSS_parser
class pc_RSS_parser {
var $tag;
var $item;
function start_element($parser, $tag, $attributes) {
if ('item' == $tag) {
$this->item = new pc_RSS_item;
} elseif (!empty($this->item)) {
$this->tag = $tag;
}
}
function end_element($parser, $tag) {
if ('item' == $tag) {
$this->item->display();
unset($this->item);
}
}
function character_data($parser, $data) {
if (!empty($this->item)) {
if (isset($this->item->{$this->tag})) {
$this->item->{$this->tag} .= trim($data);
}
}
}
}

Класс pc_RSS_item обеспечивает интерфейс доступа к отдельному экземпляру рассылки. Он скрывает детали отображения отдельного экземпляра от общего процесса анализа и облегчает переустановку данных для нового экземпляра с помощью вызова функции unset().

Метод pc_RSS_item::display() отвечает за вывод экземпляра RSS в формате HTML. Он вызывает функцию htmlspecialchars() для повторного кодирования всех нуждающихся в этом элементов, поскольку библиотека expat декодирует их в процессе анализа документа в обычные символы. Однако такая перекодировка повреждает рассылки, которые вместо простого текста размещают в названии и описании HTML-элементы.

Метод start_element(), класса pc_RSS_parser(), принимает три параметра: анализатор XML, имя тега и массив пар атрибут-значение (если таковые имеются) элемента. PHP автоматически применяет эти значения для обработчика, как часть процесса анализа.

Метод start_element() проверяет значение переменной $tag. Если оно равно item, следовательно, анализатор нашел новый экземпляр RSS и необходимо создать новый экземпляр класса pc_RSS_item. В противном случае с помощью функции empty() проверяется, пустое или нет значе-ние $this->item. Если оно не пусто, то анализатор находится внутри элемента item. В этом случае необходимо записать имя тега, чтобы метод character_data() знал, какому свойству присвоить его значение. Если значение $this->item пустое, то эта часть RSS-рассылки считается ненужной и игнорируется.

Когда анализатор находит закрывающий тег item, то соответствующий метод end_element() сначала печатает элемент RSS, а затем завершает работу после удаления объекта.

В заключение метод character_data() отвечает за присваивание значений title, description и link элементу RSS. Убедившись, что анализатор находится внутри элемента item, он проверяет, является ли текущий тег одним из свойств объекта pc_RSS_item. Без этой проверки, если бы анализатор встретил элемент, отличный от перечисленных выше
трех элементов, его значение также было бы назначено объекту. Фигурные скобки {} необходимы для указания порядка разыменования свойства объекта. Обратите внимание, что вместо прямого присваивания к свойству добавляется результат операции усечения пробелов - trim($data). Это делается для того, чтобы учесть случаи, в которых символьные данные, извлекаемые методом fread(), поступают блоками по 4096 байт. Это также позволяет удалить окружающие пробельные символы, найденные в RSS-рассылке.

Если запустить приведенный код для простой RSS-рассылки вида:



PHP Announcements
http://www.php.net/
All the latest information on PHP.

PHP 5.0 Released!
http://www.php.net/downloads.php
The newest version of PHP is now available.




то на выходе мы получим следующий HTML-текст:

PHP 5.0 Released!

The newest version of PHP is now available.



Оцените статью: (0 голосов)
0 5 0

Статьи из раздела PHP на эту тему:
Анализ XML с помощью DOM
Генерация XML вручную
Генерация XML с применением DOM
Обмен данными с помощью WDDX
Посылка SOAP-запросов

Вернуться в раздел: PHP / 12. XML