Прежде всего, я должен признать, что мне нравятся компьютерные стандарты. Если бы все следовали отраслевым стандартам, Интернет стал бы лучшим средством коммуникации. Использование стандартизированных форматов обмена данными делает возможным использование открытых и независимых от платформы вычислительных моделей. Вот почему я энтузиаст XML.
К счастью, мой любимый язык сценариев не только поддерживает XML, но и поддерживает его все чаще. PHP позволяет мне быстро публиковать XML-документы в Интернете, собирать статистическую информацию о XML-документах и преобразовывать XML-документы в другие форматы. Например, я часто использую возможности PHP по обработке XML для управления статьями и книгами, которые я пишу в XML.
В этой статье я расскажу о любом использовании встроенного в PHP парсера Expat для обработки XML-документов. На примерах я продемонстрирую метод обработки Expat. В то же время пример может показать вам, как:
Создать собственную функцию обработки.
Преобразование XML-документов в ваши собственные структуры данных PHP.
Введение Синтаксический анализатор Expat
XML, также называемый XML-процессором, позволяет программам получать доступ к структуре и содержимому XML-документов. Expat — это анализатор XML для языка сценариев PHP. Он также используется в других проектах, таких как Mozilla, Apache и Perl.
Что такое анализатор событий?
Существует два основных типа анализаторов XML:
Древовидные анализаторы: преобразуют XML-документы в древовидные структуры. Парсер этого типа анализирует всю статью, предоставляя API для доступа к каждому элементу результирующего дерева. Его общим стандартом является DOM (объектная модель документа).
Анализатор на основе событий: рассматривает XML-документы как серию событий. Когда происходит особое событие, синтаксический анализатор вызывает функцию, предоставленную разработчиком, для его обработки.
Анализатор на основе событий имеет представление XML-документа, ориентированное на данные, что означает, что он фокусируется на части данных XML-документа, а не на его структуре. Эти анализаторы обрабатывают документ от начала до конца и сообщают приложению о таких событиях, как начало элемента, конец элемента, начало данных объекта и т. д., через функции обратного вызова. Ниже приведен пример XML-документа для «Hello-World»:
<приветствие
>
Привет, мир
</greeting>
Анализатор на основе событий сообщит о трех событиях:
начальный элемент: приветствие
Начало элемента CDATA, значение: Hello World.
Конечный элемент: приветствие
В отличие от анализаторов на основе дерева, анализаторы на основе событий не создают структуру, описывающую документ. В элементах CDATA анализатор на основе событий не позволит вам получить информацию приветствия родительского элемента.
Однако он обеспечивает доступ более низкого уровня, что позволяет лучше использовать ресурсы и ускорить доступ. Таким образом, нет необходимости помещать весь документ в память; фактически весь документ может даже превышать фактическое значение памяти.
Expat — это такой парсер, основанный на событиях. Конечно, если вы используете Expat, он также может при необходимости генерировать полную древовидную структуру на PHP.
Приведенный выше пример Hello-World включает полный формат XML. Но он недействителен, поскольку с ним не связано ни DTD (определение типа документа), ни встроенное DTD.
Для Expat это не имеет значения: Expat — это анализатор, который не проверяет достоверность и, следовательно, игнорирует любые DTD, связанные с документом. Однако следует отметить, что документ все равно необходимо полностью отформатировать, иначе Expat (как и другие XML-совместимые парсеры) остановится с сообщением об ошибке.
Поскольку Exapt является анализатором, не проверяющим достоверность, его скорость и легкость делают его хорошо подходящим для интернет-приложений.
Компиляция Expat
Expat можно скомпилировать в версию PHP3.0.6 (или выше). Начиная с Apache 1.3.9, Expat включен в состав Apache. В системах Unix вы можете скомпилировать его в PHP, настроив PHP с опцией -with-xml.
Если вы скомпилируете PHP как модуль Apache, Expat будет включен в состав Apache по умолчанию. В Windows необходимо загрузить библиотеку динамической компоновки XML.
Примеры XML: XMLstats
Один из способов узнать о функциях Expat — это примеры. Пример, который мы собираемся обсудить, — это использование Expat для сбора статистики по XML-документам.
Для каждого элемента в документе будет выведена следующая информация:
сколько раз элемент используется в документе.
Количество символьных данных в этом элементе
родительский элемент элемента
дочерние элементы элемента
Примечание. В целях демонстрации мы используем PHP для создания структуры для сохранения родительского и дочернего элементов элемента.
, подготовленная
для создания экземпляра анализатора XML, — это xml_parser_create(). Этот экземпляр будет использоваться для всех будущих функций. Эта идея очень похожа на тег подключения функции MySQL в PHP. Перед анализом документа анализаторы на основе событий обычно требуют зарегистрировать функцию обратного вызова, которая будет вызываться при возникновении определенного события. Expat не имеет исключений. Он определяет следующие семь возможных событий:
объект XML-анализ функции описание
элемента xml_set_element_handler() начальные и конечные
символьные данные элемента xml_set_character_data_handler() начало символьных данных
внешний объект xml_set_external_entity_ref_handler() внешний объект неанализируемый
внешний объект xml_set_unparsed_entity_decl_handler ( ) Появление неразрешенной
инструкции обработки внешнего объекта xml_set_processing_instruction_handler() Появление объявления нотации инструкции обработки
xml_set_notation_decl_handler() Появление объявления нотации
default xml_set_default_handler() Другие события без указанной функции-обработчика
Все функции обратного вызова должны использовать экземпляр parser в качестве его первого параметра (кроме того есть и другие параметры).
Пример сценария в конце этой статьи. Следует отметить, что он использует как функции обработки элементов, так и функции обработки символьных данных. Функция обработчика обратного вызова элемента регистрируется через xml_set_element_handler().
Эта функция принимает три параметра:
экземпляр парсера
Имя функции обратного вызова, которая обрабатывает начальный элемент.
Имя функции обратного вызова, обрабатывающей закрывающий элемент.
Функция обратного вызова должна существовать в момент начала анализа XML-документа. Они должны быть определены в соответствии с прототипами, описанными в руководстве PHP.
Например, Expat передает три аргумента функции-обработчику начального элемента. В примере скрипта он определяется следующим образом:
function start_element($parser, $name, $attrs)
Первый параметр — это идентификатор парсера, второй параметр — имя начального элемента, а третий параметр содержит все атрибуты и значения массива элементов.
Как только вы начнете анализировать XML-документ, Expat вызовет вашу функцию start_element() и передаст параметры всякий раз, когда он встретит начальный элемент.
Опция XML «Сворачивание регистра»
использует функцию xml_parser_set_option() для отключения опции свертывания регистра. Эта опция включена по умолчанию, в результате чего имена элементов, передаваемые функциям-обработчикам, автоматически преобразуются в верхний регистр. Но XML чувствителен к регистру (поэтому регистр очень важен для статистических XML-документов). В нашем примере опция складывания корпуса должна быть отключена.
Разбор документа
После завершения всех приготовлений скрипт наконец-то может разобрать XML-документ:
пользовательская функция Xml_parse_from_file() открывает файл, указанный в параметре, и анализирует его размером 4 КБ.
xml_parse(), как и xml_parse_from_file(), вернет false при возникновении ошибки, то есть когда XML-документ не полностью отформатирован.
Вы можете использовать функцию xml_get_error_code(), чтобы получить числовой код последней ошибки. Передайте этот числовой код функции xml_error_string(), чтобы получить текстовую информацию об ошибке.
Выводит текущий номер строки XML, что упрощает отладку.
В процессе парсинга вызывается функция обратного вызова.
Описание структуры документа
При анализе документа с помощью Expat необходимо решить вопрос: как сохранить базовое описание структуры документа?
Как упоминалось ранее, сам анализатор событий не выдает никакой структурной информации.
Однако структура тегов является важной особенностью XML. Например, последовательность элементов <book><title> означает нечто иное, чем <figure><title>. Тем не менее, любой автор скажет вам, что названия книг и названия изображений не имеют ничего общего друг с другом, хотя оба они используют термин «название». Следовательно, чтобы эффективно обрабатывать XML с помощью синтаксического анализатора на основе событий, вы должны использовать свои собственные стеки или списки для хранения структурной информации о документе.
Чтобы отразить структуру документа, скрипт должен знать как минимум родительский элемент текущего элемента. Это невозможно с API Exapt. Он сообщает только о событиях текущего элемента без какой-либо контекстной информации. Поэтому вам необходимо построить собственную структуру стека.
В примере сценария используется структура стека «первым пришел — последним обслужен» (FILO). Через массив стек сохранит все начальные элементы. Для функции обработки начального элемента текущий элемент будет помещен на вершину стека функцией array_push(). Соответственно, функция обработки конечного элемента удаляет верхний элемент через array_pop().
Для последовательности <book><title></title></book> стек заполняется следующим образом:
начальный элемент book: назначьте «book» первому элементу стека ($stack[0]).
Заголовок начального элемента: назначьте «заголовок» вершине стека ($stack[1]).
Заголовок конечного элемента: удалите верхний элемент из стека ($stack[1]).
Заголовок конечного элемента: удалите верхний элемент из стека ($stack[0]).
PHP3.0 реализует этот пример, вручную управляя вложенностью элементов с помощью переменной $глубина. Это делает сценарий более сложным. PHP4.0 использует функции array_pop() и array_push(), чтобы сделать скрипт более кратким.
Сбор данных
Чтобы собрать информацию о каждом элементе, скрипту необходимо запомнить события для каждого элемента. Сохраните все различные элементы в документе, используя переменную глобального массива $elements. Элементы массива являются экземплярами класса элемента и имеют 4 свойства (переменные класса)
$count — количество раз, когда элемент был найден в документе.
$chars - Количество байтов символьных событий в элементе
$parents — родительский элемент
$childs — дочерние элементы
Как видите, сохранить экземпляры классов в массиве проще простого.
Примечание. Особенностью PHP является то, что вы можете пройти всю структуру класса с помощью цикла while(list() =each()), точно так же, как вы просматриваете весь соответствующий массив. Все переменные классов (и имена методов при использовании PHP3.0) выводятся в виде строк.
Когда элемент найден, нам нужно увеличить соответствующий счетчик, чтобы отслеживать, сколько раз он появляется в документе. Элемент count в соответствующем элементе $elements также увеличивается на единицу.
Нам также необходимо сообщить родительскому элементу, что текущий элемент является его дочерним элементом. Таким образом, имя текущего элемента будет добавлено к элементу в массиве $childs родительского элемента. Наконец, текущий элемент должен помнить, кто является его родителем. Таким образом, родительский элемент добавляется к элементу в массиве $parents текущего элемента.
Отображение статистики
Оставшийся код проходит через массив $elements и его подмассивы для отображения статистики. Это простейший вложенный цикл. Хотя он выводит правильные результаты, код не является кратким и не требует каких-либо специальных навыков. Это всего лишь цикл, который вы можете использовать каждый день для завершения своей работы.
Примеры сценариев предназначены для вызова из командной строки с использованием подхода PHP CGI. Таким образом, формат вывода статистических результатов является текстовым. Если вы хотите использовать скрипт в Интернете, вам необходимо изменить функцию вывода для генерации формата HTML.
Резюме
Exapt — это парсер XML для PHP. Будучи анализатором, основанным на событиях, он не создает структурное описание документа. Но обеспечивая низкоуровневый доступ, это позволяет лучше использовать ресурсы и ускорить доступ.
Как анализатор, который не проверяет достоверность, Expat игнорирует DTD, прикрепленные к XML-документам, но останавливается с сообщением об ошибке, если документ имеет неправильный формат.
Предоставление обработчиков событий для обработки документов
Создавайте собственные структуры событий, такие как стеки и деревья, чтобы воспользоваться преимуществами разметки структурированной информации XML.
Новые XML-программы появляются каждый день, а поддержка XML в PHP постоянно усиливается (например, была добавлена поддержка XML-парсера LibXML на основе DOM).
С помощью PHP и Expat вы можете подготовиться к будущим стандартам, которые будут действительными, открытыми и независимыми от платформы.
Пример
<?
/*************************************************** ***** ******************************
* Название: Пример анализа XML: Статистика информации о XML-документе.
* описывать
* В этом примере используется синтаксический анализатор PHP Expat для сбора и подсчета информации о XML-документе (например: количество вхождений каждого элемента, родительских элементов и дочерних элементов).
* XML-файл в качестве параметра./xmlstats_PHP4.php3 test.xml
* $Requires: Требования Expat: Expat PHP4.0 скомпилирован в режиме CGI.
************************************************* * ***************************/
// Первый параметр — XML-файл
$file = $argv[1]
// Инициализация переменных
$elements = $stack = массив();
$total_elements = $total_chars = 0
//Базовый класс элементов
;
элемент класса
{
вар $count = 0;
вар $символы = 0;
вар $parents = массив();
вар $childs = массив();
}
// Функция для анализа XML-файлов
функция xml_parse_from_file($parser, $file)
{
если(!file_exists($file))
{
die("Невозможно найти файл "$file".");
}
if(!($fp = @fopen($file, "r")))
{
die("Невозможно открыть файл "$file".");
}
while($data = fread($fp, 4096))
{
if(!xml_parse($parser, $data, feof($fp)))
{
возврат (ложь);
}
}
Fclose ($ fp);
возврат (истина);
}
// Функция вывода результата (блочная форма)
функция print_box($title, $value)
{
printf("n+%'-60s+n", "");
printf("|%20s", "$title:");
printf("%14s", $value);
printf("%26s|n", "");
printf("+%'-60s+n", "");
}
// Функция вывода результата (строчная форма)
функция print_line($title, $value)
{
printf("%20s", "$title:");
printf("%15sn", $value);
}
// Функция сортировки
функция my_sort($a, $b)
{
return(is_object($a) && is_object($b) ? $b->count - $a->count: 0);
}
функция start_element($parser, $name, $attrs)
{
global $elements, $stack;
// Элемент уже находится в глобальном массиве $elements?
if(!isset($elements[$name]))
{
// Нет - добавить экземпляр класса элемента
$element = новый элемент;
$elements[$name] = $element;
}
// Увеличиваем счетчик этого элемента на единицу
$elements[$name]->count++
// Есть ли родительский элемент?
if(isset($stack[count($stack)-1]))
{
// Да – назначить родительский элемент $last_element
$last_element = $stack[count($stack)-1]
// Если массив родительских элементов текущего элемента пуст, инициализируем его значением 0
if(!isset($elements[$name]->parents[$last_element]))
{
$elements[$name]->parents[$last_element] = 0;
}
// Увеличиваем счетчик родительского элемента этого элемента на единицу
$elements[$name]->parents[$last_element]++;
// Если массив дочерних элементов родительского элемента текущего элемента пуст, он инициализируется значением 0
if(!isset($elements[$last_element]-> дети[$имя]))
{
$elements[$last_element]->childs[$name] = 0;
}
// Добавляем единицу к счетчику дочернего элемента родительского элемента.
$elements[$last_element]->childs[$name]++;
}
//Добавляем текущий элемент в стек
array_push($stack, $name);
}
функция stop_element($parser, $name)
{
global $stack
// Удаляем верхний элемент из стека
array_pop ($ стек);
}
функция char_data($parser, $data)
{
global $elements, $stack, $length
// Увеличиваем количество символов текущего элемента;
$elements[$stack][count($stack)-1]]->chars += strlen(trim($data));
}
// Генерируем экземпляр парсера
$parser = xml_parser_create();
// Устанавливаем функцию обработки
xml_set_element_handler($parser, "start_element", "stop_element");
xml_set_character_data_handler($parser, "char_data");
// Анализируем файл
);
$ret = xml_parse_from_file($parser, $file);
если(!$рет)
{
die(sprintf("Ошибка XML: %s в строке %d",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)));
}
// Освобождаем парсер
xml_parser_free($parser);
// Освобождаем вспомогательный элемент
unset($elements["current_element"]);
unset($elements["last_element"]);
// Сортируем по количеству элементов
uasort($elements, "my_sort");
// Проходим по $elements для сбора информации об элементе
while(список($name, $element) = каждый($elements))
{
print_box("Имя элемента", $name
("Количество элементов", $element->count);
print_line("Количество символов", $element->chars);
printf("n%20sn", "* Родительские элементы");
// Проходим по родительскому элементу и выводим результат.
while(список($key, $value) = каждый($element->родители))
{
print_line($ключ, $значение);
}
если (count ($element->родители) == 0)
{
printf("%35sn", "[корневой элемент]");
}
// Проходим через дочерний элемент этого элемента и выводим результат
printf("n%20sn", "* Дочерние элементы");
while(list($key, $value) = каждый($element->childs))
{
print_line($ключ, $значение);
}
если (count ($element->childs) == 0)
{
printf("%35sn", "[нет детей]");
}
$total_elements += $element->count;
$total_chars += $element->chars;
}
// окончательный результат
print_box("Всего элементов", $total_elements);
print_box("Всего символов", $total_chars);
?>