Примечание: из-за того, что модули для работы с XML в PHP пока находятся в состоянии "экспериментальных" - примеры приведенные в статье могут не работать в некоторых версиях PHP из-за различий в наборе функций, реализуемых этими модулями. Примеры писались и тестировались на PHP 4.0.6.
Работа с XML-данными в PHP
Общая информация об XML
XML (eXtensible Markup Language, расширяемый язык разметки) - достаточно новая технология (первый вариант спецификации XML увидел свет в 1998-м году) и поэтому многим она либо незнакома, либо знакома лишь поверхностно. Но самое главное - как правило люди не видят способов применения XML в своей повседневной работе. Из-за этого многие люди, особенно не являющиеся профессиональными разработчиками (т.к. по последним опросам более половины профессоналов используют XML-технологии в своей работе) обходят стороной эту технологию стороной, пытаясь взамен разработать что-то свое.
Поэтому прежде, чем начать описание того, как работать с XML-данными в PHP и как использовать их в ваших программах, необходимо, хотя бы вкратце описать, что же такое XML. Хочу сразу предупредить вас, что подробное рассмотрение непосредственно XML-технологий выходит за рамки нашего повествования и всем, кто захочет узнать больше об XML как таковом - должен будет сделать это самостоятельно. В интернете есть огромное количество информации об XML, но начинать нужно с самого полного и правильного документа, касающегося XML - его спецификации (http://www.w3.org/TR/REC-xml). Кроме того советую вам посетить сайт http://xmlhack.ru - там вы найдете множество интересной информации об XML на русском языке и ссылки на другие ресурсы сети, посвященных XML.
Итак, что же такое XML? Как уже было сказано ранее - это расширяемый язык разметки. Под термином "язык разметки" понимается любой язык, который позволяет определять назначение, свойства, способ отображения и т.п. частей документа путем добавления в текст документа "тегов" - специальных конструкций, распознаваемых парсером языка. Чтобы не запутать вас этим ужасным определением приведу простой и всем понятный пример - язык HTML, который является ничем иным как языком разметки (HTML - Hyper Text Markup Language, язык разметки гипертекстовых документов). Однако при всей своей мощи язык HTML является все-таки ограниченным, поскольку определяет фиксированный набор тегов, каждый из которых может использоваться лишь для одной, предназначенной ему цели, что, согласитесь, не подходит в качестве метода для решения любой задачи представления информации. Хотя использование CSS1 и особенно CSS2 отчасти позволяет расширить рамки этого языка по крайней мере в части его визуального отображения, но этот метод также имеет свои недостатки:
-
Возможность задания фактически любого визуального отображения для любого тега значительно осложняет работу программ, занимающихся анализом HTML документов. Например роботы поисковых машин в общем случае уже не могут утверждать, что тег <h1> имеет больший вес, чем тег <p> в данном конкретном документе, потому что используя CSS я могу добиться прямо противоположных результатов визуального отображения того же документа.
-
Реализация CSS (и особенно CSS2) в современных браузерах оставляет желать много лучшего. Наилучших результатов здесь добились авторы Gecko - движка для браузеров, на котором построены браузеры Netscape 6, Mozilla, Galeon и другие. Opera 6 тоже выглядит достаточно хорошо в плане поддержки стандартов CSS1 и CSS2, Internet Explorer на фоне их выглядит довольно бледно - у него наблюдаются явные проблемы с реализацией некоторых частей спецификации CSS2 (что, впрочем, неудивительно - ребята из Редмонда всегда отличались оригинальным взглядом на вопрос поддержки в своих программах спецификаций, в разработке которых они же сами и принимали непосредственное участие).
И помимо всего этого у HTML есть еще один огромный недостаток - используя этот язык можно описать, как должен выглядеть документ, но нельзя описать то, какая информация содержится в этом документе. Это порождает целую массу проблем. Одной из самых серьезных можно назвать проблему поиска информации, потому что те же поисковые машины вынуждены лишь делать предположения о том, что документ, который они нашли в той или иной мере соответствует запросу. Но как ни хороши алгоритмы работы современных поисковых машин - в результатах их работы все равно много "мусора".
Язык XML появился на свет во многом благодаря существованию описанных выше проблем, когда стало ясно, что необходим язык, настолько же простой и понятный как HTML, но способный хранить в себе любую информацию в структурированном виде, имеющий синтаксис, который бы позволил легко писать программы для работы с документами XML, имеющий встроенные средства проверки корректности структуры и информации, описанной в документе и другие параметры, облегчающие создание, обработку и использование данных, описанных в документах XML. Нужно сказать, что к моменту создания XML подобный язык уже существовал (это язык SGML - Standard Generalized Markup Language), но этот язык настолько сложен, что широкое использование его значительно затруднено. Надо сказать, что со своей задачей разработчики XML справились просто превосходно - спецификация языка XML 1.0 соответствует всем требованиям. Однако перейдем от слов к делу и посмотрим на пример XML-документа, например, описывающего книгу:
<book isbn="0395489326">
<author>John R. R. Tolkien</author>
<title>Lord of the rings</title>
<category>Fantasy</category>
<price currency="USD">9.95</price>
</book>
Как видите - содержимое этого документа говорит само за себя и даже человек, который впервые видит этот документ поймет что значат те или иные данные в нем. В этом и состоит основная прелесть XML - хорошо составленный XML документ содержит в себе информацию, которую одинаково легко сможет распознать как челоек, так и программа. При этом создать такой документ очень легко - спецификация XML никак не органичивает количество, структуру и имена тегов и аттрибутов, из которых состоит XML-документ. Фактически спецификация XML описывает лишь набор основных структур языка и соглашения об их синтаксисе. Все остальное вы свободны задать так, как вам необходимо. Кроме того спецификация XML позволяет вам самому разработать любой язык разметки, который нужен именно вам, и формально описать его структуру. Т.е. в принципе любой язык разметки текстовых документов может быть описан на языке XML (в том числе, кстати, и язык HTML). Однако это отдельная очень большая тема и здесь мы ее рассматривать не будем.
Все сказанное выше было необходимо для того, чтобы вы поняли основную идею - с помощью XML вы можете описать любые данные в структурированном виде и затем легко обрабатывать эти данные, в том числе и на PHP, о чем и пойдет речь далее.
Способы обработки XML данных в PHP
В PHP существуют 2 модуля работы с XML, реализующие 2 разных стандарта обработки XML-данных: SAX (Simple API for XML) и DOM (Document Object Model). Необходимо вкратце пояснить, что представляют собой эти стандарты.
Стандарт SAX не является стандартом W3C, официальным сайтом для него является http://www.saxproject.org. Этот стандарт описывает метод парсинга XML документов для получения данных из них. Т.е. этот метод обработки XML-документов позволит вам только прочитать данные из XML-документа и не более того. Создавать и изменять XML-документы с его помощью вы не сможете.
SAX основан на т.н. событийном программировании. Его особенностью является то, что вы предоставляете парсеру XML набор собственных функций, которые будут заниматься обработкой различных типов XML-данных (элементов (тегов), текста и т.п.), а парсер затем будет сам вызывать ваши функции в процессе обработки XML-документа, передавая им найденные данные. Важной особенностью здесь является порядок вызова ваших функций, который вы должны будете учесть при их написании: ваши функции будут вызываться в той же последовательности, в которой соответствующие даные располагаются в XML-документе. В следующем разделе мы рассмотрим пример чтения данных из XML-документа с использованием SAX.
Другим стандартом для обработки XML-данных является DOM - стандарт W3C. В отличие от SAX этот метод позволяет вам производить любые операции с XML-данными в очень удобной форме - представляя XML-документ как дерево объектов. Далее мы рассмотрим пример того, как можно работать с XML через DOM из PHP. Чтение XML-данных в PHP с использованием SAX
Для того, чтобы показать, как использовать имеющиеся в PHP модули для работы с XML-данными возьмем реальный пример - колонка новостей, хранящаяся в XML файле.
Файл news.xml, в котором хранятся новости:
<?xml version="1.0"?>
<newsLine>
<news date="1.1.2002">
<title>title 1</title>
<text>news text 1</text>
</news>
<news date="5.1.2002">
<title>title 2</title>
<text>news text 2</text>
</news>
<news date="10.1.2002">
<title>title 3</title>
<text>news text 3</text>
</news>
</newsLine>
Файл test.php, выполняющий парсинг news.xml для получения новостей:
<?php
$news = array(); // В этом массиве будут храниться новости,
// полученные из XML файла
$currentNews = null; // Текущая новость. Используется в процессе
// импорта данных
$index = null; // Текущий индекс в массиве новостей.
// Используется в процессе импорта данных
// Функции, описанные ниже, являются обработчиками различных типов
// XML-данных и будут вызываться парсером в процессе разбора.
// Функция для обработки начальных тегов XML
// На входе:
// - указатель на SAX парсер
// - имя XML тега
// - массив аттрибутов
function saxStartElement($parser,$name,$attrs)
{
global $currentNews,$index;
switch($name)
{
case 'newsLine':
// Тег newsLine содержит все новости. Мы должны подготовить
// массив $news для приема новостей из XML файла.
$news = array();
break;
case 'news':
// Каждая новость находится в теге news. Подготавливаем массив
// $currentNews для приема этой новости
$currentNews = array();
// Если у новости есть дата - сохраняем ее в массиве
if (in_array('date',array_keys($attrs)))
$currentNews['date'] = $attrs['date'];
break;
default:
// Все остальные теги, которые могут встретиться в XML файле
// находятся внутри тега <news>, поэтому мы просто запоминаем
// их название с тем, чтобы знать, какие именно данные мы
// обрабатываем.
$index = $name;
break;
};
}
// Функция для обработки конечных тегов XML
// На входе:
// - указатель на SAX парсер
// - имя XML тега
function saxEndElement($parser,$name)
{
global $news,$currentNews,$index;
if ((is_array($currentNews)) && ($name=='news'))
// Если в данный момент у нас есть массив $currentNews (т.е.
// мы обрабатываем содержимое новости) и имя закрывающего
// тега - "news", то это значит, что данные для этой новости
// кончились и мы можем поместить готовую новость в массив
// новостей.
{
$news[] = $currentNews;
// Уничтожаем массив текущей новости, чтобы показать, что
// в данный момент мы не занимаемся получением данных для
// новости.
$currentNews = null;
};
// В любом случае закрытие тега означает, что символьные
// данные, получаемые парсером не нужно помещать куда-либо.
$index = null;
}
// Функция для обработки символьных данных
// На входе:
// - указатель на SAX парсер
// - символьные данные XML
function saxCharacterData($parser,$data)
{
global $currentNews,$index;
// Мы принимаем только данные для новостей, помещенные в
// какой-нибудь тег. Все остальные символьные данные
// (как правило это пустое пространство, использованное
// для форматирования) мы опускаем за ненадобностью.
if ((is_array($currentNews)) && ($index))
$currentNews[$index] = $data;
}
// Создаем SAX парсер, который будет использоваться для
// обработки XML-данных.
$parser = xml_parser_create();
// Регистрируем функции для обработки различных типов
// XML-данных:
// - начальный и конечный тэги XML
xml_set_element_handler($parser,'saxStartElement','saxEndElement');
// - символьные данные
xml_set_character_data_handler($parser,'saxCharacterData');
// Также существуют аналогичные функции для регистрации
// обработчиков других типов XML-данных.
// Убираем case folding, в этом случае имена тэгов будут
// передаваться обработчикам в оригинальном виде. Если case
// folding включен, то все имена тегов будут переведены
// в верхний регистр.
xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,false);
// Получаем содержимое XML-файла с новостями.
$xml = join('',file('news.xml'));
// Производим парсинг (разбор) полученного XML-файла.
// В процессе разбора парсер будет вызывать описанные нами
// функции и в результате мы получим массив $news,
// содержащий новости из XML-файла.
if (!xml_parse($parser,$xml,true))
// Парсер возвращает значение FALSE, если произошла
// какая-либо ошибка. В этом случае мы также прекращаем
// выполнение скрипта и возвращаем ошибку.
die(sprintf('Ошибка XML: %s в строке %d',
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)));
// Уничтожаем парсер, освобождая занятые им ресурсы
xml_parser_free($parser);
?>
В результате работы файла test.php мы получим массив $news , содержащий все новости из файла news.xml. Если, к примеру сделать print_r($news) , чтобы посмотреть содержимое массива, то мы увидим примерно следующее:
Array
(
[0] => Array
(
[date] => 1.1.2002
[title] => title 1
[text] => news text 1
)
[1] => Array
(
[date] => 5.1.2002
[title] => title 2
[text] => news text 2
)
[2] => Array
(
[date] => 10.1.2002
[title] => title 3
[text] => news text 3
)
)
Для того, чтобы потом вывести новости на странице, нам будет необходимо всего-лишь обработать элементы массива, например так:
<html>
<head>
<title>Новости</title>
</head>
<body>
<table width="100%" border="1">
<?php
foreach($news as $n)
{
?>
<tr>
<td width="90%"><b><?php echo $n['title']; ?></b></td>
<td><?php echo $n['date']; ?></td>
</tr>
<tr>
<td colspan="2"><?php echo $n['text']; ?><br><br></td>
</tr>
<?php
};
?>
</table>
</body>
</html>
Однако, я думаю, вы уже заметили очевидные неудобства этого метода обработки XML-документов. В первую очередь это очевидная сложность в написании обработчиков для различных типов XML-данных. Ведь вам необходимо самостоятельно заботиться о том, чтобы передать другим обработчикам информацию о том, что делать с той или иной информацией, которую они получили. Несложно догадаться, что в случаях более сложной структуры XML документа эти проверки станут еще более сложными и легко могут стать источником ошибок. Кроме того очевидно, что набор обработчиков различен для каждой отдельно взятой струтруры XML-документа и при изменении этой структуры код обработчиков необходимо переписывать.
Все эти недостатки делают область применения SAX достаточно ограниченной, тем более в условиях того, что существует другой, гораздо более мощный и удобный стандарт обработки XML-документов - DOM. О нем и пойдет речь в следующем разделе.
Обработка XML-данных в PHP с использованием DOM
Стандарт DOM обладает двумя основными достоинствами:
-
Он не привязан к какой-то конкретной платформе или языку программирования. Используя DOM можно работать с XML-данными совершенно одинаково на C++, Java, JavaScript и т.д.
-
Он позволяет выполять все операции по обработке XML-данных. Т.е. вы можете не только читать их, но и модифицировать содержимое XML-документа, вставляя туда новые теги, удаляя и изменяя их.
К сожалению в отношении PHP первое высказывание не относится т.к. единственная имеющаяся реализация интерфейса DOM для PHP не является таковой в полной мере, поскольку имеет совершенно другой интерфейс, что, конечно же не очень хорошо. Кроме того имеющийся интерфейс (по крайней мере пока) не поддерживает многие вещи, подчас являющиеся концептуальными для стандарта DOM. Но даже в таком виде этот интерфейс намного удобнее в использовании чем SAX и, я думаю, вы сами убедитесь в этом, посмотрев пару примеров, приведенных ниже:
Для начала попробуем реализовать ту же функцианальность, что и в примере, рассмотренном в предыдущем разделе (чтение колонки новостей из XML файла) но с использованием интерфейса DOM:
<?php
// Массив, в котором будут сохранены новости из XML файла
$news = array();
// Читаем содержимое XML файла в строковую переменную
$xml = join('',file('news.xml'));
// Создаем дерево DOM XML на основе имеющегося у нас XML файла.
// Заметьте, что это заняло у нас всего лишь одну строчку кода!
$xml = xmldoc($xml);
// Теперь в переменной $xml у нас находится дерево объектов,
// представляющее собой наш XML файл. Для того, чтобы начать
// обход этого дерева - необходимо получить объект родительской node (тега).
// Для нашего XML файла это будет node <newsList>
$root = $xml->root();
// Получаем массив потомков родительской node (в нашем случае это
// массив <news>)
$nodes = $root->children();
// Начинаем обработку каждой node в массиве
foreach($nodes as $node)
{
// Если текущая node - это одна из node <news>, то продолжаем ее обработку,
// чтобы получить информацию об этой новости
if ($node->name=='news')
{
// Создаем новый массив, куда будем собирать иинформацию о текущей новости
$currentNews = array();
// Получаем дату этой новости (она хранится в аттрибуте 'date')
$currentNews['date'] = $node->get_attribute('date');
// Остальная информация хранится во вложенных node <title> и <text>,
// поэтому мы должны взять список вложенных nodes и обработать его.
$content = $node->children();
foreach($content as $contentNode)
{
// Проверяем: если node, которую мы сейчас обрабатываем, является тегом
// (т.е. имеет тип XML_ELEMENT_NODE) и ее имя - одно из тех, которые мы
// ищем - добавляем информацию из этой node в массив с информацией о новости
if (($contentNode->type==XML_ELEMENT_NODE) &&
(in_array($contentNode->name,array('title','text'))))
$currentNews[$contentNode->name] = $contentNode->content;
};
// Сохраняем текущую новость в массиве новостей
$news[] = $currentNews;
};
};
?>
Как видите - код получился гораздо более простой и понятный. Кроме того он намного меньше зависит от структуры исходного XML файла и легче поддается модификации. Результат работы, естественно, тот же самый, что и для варианта с работй через SAX.
Однако и это не самый простой вариант обработки XML-документов, особенно для больших документов со сложной структурой. Основной проблемой в этом случае становится процесс доступа к данным, т.к. необходимо "вручную" пройти весь путь по XML-дереву до необходимых данных. К счастью из этой ситуации есть простой и элегантный выход и имя ему XPath. Это язык, также разработанный W3C и специально предназначенный для получения данных из деревьев DOM XML. К сожалению ввиду того, что имеющийся у нас XML-документ имеет очень простую структуру - я не могу продемонстрировать большинство интересных конструкций XPath на нем, но после прочтения спецификации вы поймете, что это очень мощный и удобный инструмент. Например выражение //category[@id=5]/product[position()=3]/name вернет вам имя продукта, расположенного под номером 3 в категории, имеющей id=5 , причем положение самой node <category> может быть произвольным внутри дерева XML. Здорово, не правда ли?
Посмотрим, как может выглядеть код предыдущего примера, если использовать XPath для получения всех node <news> :
<?php
// Сначала все то же самое...
$news = array();
$xml = join('',file('news.xml'));
$xml = xmldoc($xml);
// А теперь, вместо того, чтобы добираться до необходимых данных
// "вручную" мы воспользуемся XPath.
// Инициализируем
$xml->xpath_init();
// Создаем новый контекст
$ctx = xpath_new_context($xml);
// Вычисляем XPath-выражение, результатом которого являются все node <news>
$nodes = xpath_eval($ctx,'//news');
// Теперь нам остается только обработать полученные nodes.
foreach($nodes->nodeset as $node)
{
// Сама обработка осталась такой же, правда за ненадобностью исчезла
// проверка имени обрабатываемой node.
$currentNews = array();
$currentNews['date'] = $node->get_attribute('date');
$content = $node->children();
foreach($content as $contentNode)
{
if (($contentNode->type==XML_ELEMENT_NODE) &&
(in_array($contentNode->name,array('title','text'))))
$currentNews[$contentNode->name] = $contentNode->content;
};
$news[] = $currentNews;
};
?>
Как видите - с использованием XPath обработка XML стала еще проще. Но это еще не все. Как я уже говорил раньше - интерфейс DOM позволяет вам модифицировать дерево XML. Воспользуемся этим, чтобы добавить еще одну новость в XML-файл:
<?php
// Данные для новости, которая будет добавлена в XML файл
$newsToAdd = array(
'date' => '17.1.2002',
'title' => 'title 4',
'text' => 'news text 4'
);
// Загружаем имеющийся XML файл и создаем DOM-дерево
$xml = join('',file('news.xml'));
$xml = xmldoc($xml);
// Получаем reference на корневую node (<newsLine>)
// поскольку мы должны добавить новость к ней.
$root = $xml->root();
// Создаем новую node для новости
$newsNode = $root->new_child('news',null);
// Сохраняем дату новости в аттибуте 'date'
$newsNode->setattr('date',$newsToAdd['date']);
// Создаем вложенные nodes <title> и <text>
$newsNode->new_child('title',$newsToAdd['title']);
$newsNode->new_child('text',$newsToAdd['text']);
// Добавление новости завершено. Получаем текст XML файла
// и сохраняем измененный файл на диске.
$text = $xml->dumpmem();
$fp = fopen('news.xml','w');
fwrite($fp,$text);
fclose($fp);
?>
По-моему этот пример не нуждается в каких-то особых комментариях - структура создаваемого XML достаточно четко видна из того, что и к какой node добавляется. Запустив этот пример и посмотрев на файл news.xml после окончания его работы вы увидете, что в нем появилась та новость, которую мы добавляли.
Заключение
На сегодня все. Но вопрос о работе с XML в PHP еще далеко не исчерпан. Впереди еще одна, не менее важная часть - работа с XSLT и применение связки XML+XSLT для генерации HTML страниц. Но для того, чтобы понимать материал, который будет излагаться далее по этому вопросу вам необходимо иметь представление о технологиях, о которых пойдет речь (XML, XSLT, XPath). Часть ссылок на материалы по этим технологиям уже была дана в этой статье, я могу также порекомендовать вам посетить замечательный ресурс, содержащий множество документации по этим и другим технологиям - http://www.zvon.org.
Александр Грималовский (Flying)
|