Обработка XML с помощью SimpleXML в PHP 5
PHP 5 представляет несколько новых расширений для работы с XML файлами. Конструкции SimpleXML, такие как:
$doc->rss->channel->item->titleвыбирают элементы из документа. Если вы четко ориентируетесь в структуре вашего документа, то писать такие конструкции будет легко. Если же структура файла меняется, то можно использовать XPath для поиска элементов.
Первые шаги в SimpleXML
Рассмотрим работу с SimpleXML на примере создания простого просмотрщика RSS лент. RSS является основным форматом XML для публикации содержания, взятого из нескольких источников. Корневой элемент этого документа – rss, который содержит единственный элемент channel. Элемент channel содержит метаданные о содержимом, включая его заголовок, язык и URL. Он также содержит разнообразные текстовые элементы, вложенные в элементы item. Каждый элемент item имеет элемент link, содержащий URL, а также элементы title и description в которых находится читаемый текст. Области имен не используются. Типичный пример RSS канала:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="0.92">
<channel>
<title>Mokka mit Schlag</title>
<link>http://www.elharo.com/blog</link>
<language>en</language>
<item>
<title>Penn Station: Gone but not Forgotten</title>
<description>
The old Penn Station in New York was torn down before I was born.
Looking at these pictures, that feels like a mistake. The current site is
functional, but no more; really just some office towers and underground
corridors of no particular interest or beauty. The new Madison Square...
</description>
<link>http://www.elharo.com/blog/new-york/2006/07/31/penn-station</link>
</item>
<item>
<title>Personal for Elliotte Harold</title>
<description>Some people use very obnoxious spam filters that require you
to type some random string in your subject such as E37T to get through.
Needless to say neither I nor most other people bother to communicate with
these paranoids. They are grossly overreacting to the spam problem.
Personally I won't...</description>
<link>http://www.elharo.com/blog/tech/2006/07/28/personal-for-elliotte-harold/</link>
</item>
</channel>
</rss>
Скелет PHP скрипта для обработки ленты выглядит следующим образом:
<?php // Загрузите и проанализируйте XML-document ?>
<html xml:lang="en" lang="en">
<head>
<title><?php // Заголовок будет читаться из RSS ?></title>
</head>
<body>
<h1><?php // Заголовок снова будет читаться из RSS ?></h1>
<?php
// Здесь мы поместим цикл, чтобы включить заголвок каждого элемента и описание
?>
</body>
</html>
Анализ XML-документа
Первый шаг – анализ XML-документа и его сохранение во внутренних структурах SimpleXML. Это требует написания всего лишь одной строки кода, которая передает URL функции simplexml_load_file():
$rss = simplexml_load_file('http://example.ru/rss.xml');
Программисту файл будет доступен ввиде экземпляра класса SimpleXML, но данная переменная обеспечивает доступ не ко всему контенту (как в DOM API) , а указывает на корневой элемент документа.
Нахождение имени канала
Имя всего канала находится в дочернем элементе title элемента channel. Можно загрузить этот заголовок, обратившись к полям channel и title экземпляра класса SimpleXML, созданного на предыдущем этапе:
$title = $rss->channel->title;
Когда мы нашли заголовок, его можно вывести на страницу, например с помощью функции echo.
В принципе промежуточную переменную $title можно пропустить, но т.к. страница многократно использует это значение во многих местах, более удобно хранить ее.
После того, как мы нашли заголовок канала, необходимо получить доступ к элементам. Чтобы получить доступ к одному элементу канала нужно воспользоваться следующей конструкцией:
$rss->channel->item
Однако каналы обычно содержат больше, чем один элемент. Или их даже может не быть вовсе. Соответственно, этот оператор возвращает массив, обойти который можно в цикле foreach:
foreach ($rss->channel->item as $item) {
echo "<h2>". $item->title. "</h2>";
echo "<p>". $item->description. "</p>";
}
Можно так же добавить на страницу ссылки на публикации. Для этого необходимо использовать конструкцию вида: $item->link
В итоге у нас получилось:
<?php // Load and parse the XML document
$rss = simplexml_load_file('http://example.ru/rss.xml');
$title = $rss->channel->title;
?>
<html xml:lang="en" lang="en">
<head>
<title><?php echo $title; ?></title>
</head>
<body>
<h1><?php echo $title; ?></h1>
<?php
// Здесь мы поместим цикл, чтобы включить заголовок элемента и описание
foreach ($rss->channel->item as $item) {
echo "<h2><a href='". $item->link. "'>". $item->title. "</a></h2>";
echo "<p>". $item->description. "</p>";
}
?>
</body>
</html>
В принципе это - все, что нужно для разбора RSS лент в PHP. Конечно программа не с самой мощной функциональностью, но наглядно демонстрирует использование SimpleXML для разбора RSS лент.
Обработка ошибок
Не все XML файлы являются правильно сформированными. Иногда в них бывают синтаксические или семантически ошибки, которые могут привести к неправильному срабатыванию PHP скрипта. Как правило, программа записывает предупреждение в файл php-ошибок (но без детального сообщения об ошибке), и функция simplexml-load-file() выдает ошибку. Если же вы все-таки уверены в том, что XML файл правильно сформирован проверьте, что выдает данная функция. Сделать это можно следующим образом:
<?php
$rss = simplexml_load_file('http://example.ru/rss.xml');
if ($rss) {
foreach ($rss->xpath('//title') as $title) {
echo "<h2>". $title. "</h2>";
}
}
else {
echo "Упс! Ввод деформирован!";
}
?>
Другая ошибка связана с тем, что мы можем не точно знать структуру XML файла. Что происходит, например, с таким выражением $doc->rss->channel->item->title, когда элементная группа не имеет заголовка? Самое простое решение следующее: всегда обращаться с возвращаемым функцией значением как с массивом данных и заключать его в цикл. В этом случае вы защищены от факта наличия большего или меньшего количества элементов, чем вы ожидали. Однако, если вы знаете, что вы хотите первый элемент в документе, даже если там имеется больше чем один, вы можете запросить его через индекс, начинающийся с нуля. Например, для запроса заголовка первой группы элементов, вы можете написать:
$doc->rss->channel->item[0]->title[0]Если первая группа элементов отсутствует, или не имеет названия, она рассматривается так же, как и любой другой, находящийся за установленными рамками индекс, в массиве PHP. То есть результат равен нулю, который превращается в пустую строку, когда вы попытаетесь вставить его в выходной код HTML.
Вообще любой XML документ должен соответствовать некоторой XSchema или DTD. Однаков в API SimpleXML не включены средства проверки соответствия документа DTD или XSchema.
Работа с пространствами имен
Еще одним популярным форматом для обмена информацией между сайтами является Atom. Документ Atom может выглядеть следующим образом:
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"
xml:base="http://www.cafeconleche.org/today.atom">
<updated>2006-08-04T16:00:04-04:00</updated>
<id>http://www.cafeconleche.org/</id>
<title>Cafe con Leche XML News and Resources</title>
<link rel="self" type="application/atom+xml" href="/today.atom"/>
<rights>Copyright 2006 Elliotte Rusty Harold</rights>
<entry>
<title>Steve Palmer has posted a beta of Vienna 2.1, an open source
RSS/Atom client for Mac OS X.
</title>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml"
id="August_1_2006_25279" class="2006-08-01T07:01:19Z">
<p>
Steve Palmer has posted a beta of <a shape="rect"
href="http://www.opencommunity.co.uk/vienna21.php">Vienna
2.1</a>, an open source RSS/Atom client for Mac OS X. Vienna
is the first reader I've found acceptable for daily use; not
great but good enough. (Of course my standards for "good
enough" are pretty high.) 2.1 focuses on improving the user
interface with a unified layout that lets you scroll through
several articles, article filtering (e.g. read all articles
since the last refresh), manual folder reordering, a new get
info window, and an improved condensed layout.
</p>
</div>
</content>
<link href="/#August_1_2006_25279"/>
<id>http://www.cafeconleche.org/#August_1_2006_25279</id>
<updated>2006-08-01T07:01:19Z</updated>
</entry>
<entry>
<title>Matt Mullenweg has released Wordpress 2.0.4,
a blog engine based on PHP and MySQL.
</title>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml"
id="August_1_2006_21750" class="2006-08-01T06:02:30Z">
<p>
Matt Mullenweg has released <a shape="rect"
href="http://wordpress.org/development/2006/07/wordpress-204
/">Wordpress 2.0.4</a>, a blog engine based on PHP and
MySQL. 2.0.4 plugs various security holes, mostly involving
plugins.
</p>
</div>
</content>
<link href="/#August_1_2006_21750"/>
<id>http://www.cafeconleche.org/#August_1_2006_21750</id>
<updated>2006-08-01T06:02:30Z</updated>
</entry>
</feed>
По многим параметрам этот документ похож на RSS, однако здесь больше метаданных, а корневой элемент называется feed, вместо - rss. Элемент feed имеет списки вместо элементов. Элемент content заменяет элемент description. Гораздо важнее то, что документ Atom использует пространство имен, в то время как RSS нет. Таким образом, документ Atom может выводить реальное, не урезанное содержание XHTML.
Хотя имена элементов изменились, основные принципы обработки XML документа средствами SimpleXML остались теми же. Единственная разница в том, что вам необходимо указать пространство имен, т.е. универсальный идентификатор ресурса (URI), когда вы запрашиваете элемент с именем, таким же как и локальное имя. Этот процесс проходит в 2 этапа: сначала запрашиваются дочерние элементы в данном пространстве имен с помощью функции children. Затем запрашиваются элементы с правильным именем в этом пространстве имени.
Пример:
$children = $feed->children('http://www.w3.org/2005/Atom');$title = $children->title;
Обработка смешанного контента
В Atom контент любого списка может содержать полный текст фрагмента и не только сам текст, но и всю разметку. Это - повествовательная структура: слова в ряду предназначены для чтения людьми. Как и в большинстве данных подобного рода здесь смешанный контент. XML уже не упрощенный, и поэтому применение SimpleXML несколько усложняется, т.к. он не может корректно работать со смешанным контентом.
Если контент представляет собой корректный XHTML, то можно скопировать его в выходную строку не распознавая, с помощью функции asXML(). Сделать это можно следующим образом:
echo "<p>". $details->content->asXML(). "</p>";Результатом такой обработки может быть следующий текст:
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml" id="August_7_2006_31098" class="2006-08-07T09:38:18Z">
<p>Nikolai Grigoriev has released <a shape="rect" href="http://www.grigoriev.ru/svgmath">SVGMath 0.3</a>, a presentation MathML formatter that produces SVG written in pure Python and published under an MIT license. According to Grigoriev, "The new version can work with multiple-namespace documents (e.g. replace all MathML subtrees with SVG in an XSL-FO or XHTML document); configuration is made more flexible, and several bugs are fixed. There is also a stylesheet to adjust the vertical position of the resulting SVG image in XSL-FO." </p>
</div>
</content>
Это не чистый XHTML. Элемент content извлекается из Atom документа, и вам бы, на самом деле, лучше его не иметь. Даже хуже, он проникает в неправильное пространство имени, поэтому он не может быть правильно распознан. На практике это не сильно вредит, т.к. веб-браузеры в большинстве случаев игнорируют теги, которые они не могут распознать. Убрать теги можно с помощью строковых операций, например, следующим образом:
$description = $details->content->asXML();
>$tags = array('<content type="xhtml"'>", "</content>");
$notags = array("", "");
$description = str_replace($tags, $notags, $description);
Но даже после такой обработки текст может содержать теги, атрибуты или куски элементов CDATA. Обработка смешанного контента это не прерогатива SimpleXML, поэтому возможно в данном случае придется поискать другой инструмент.
Использование XPath
Такие выражения как $rss->channel->item->title великолепны, но только если вы четко представляете структуру XML документа. Однако на практике такое бывает далеко не всегда. Например, в XHTML элементы в заголовке (h1,h2,h3, и т.д.) могут быть дочерними от body, div, table и от нескольких других элементов. Более того, div, table, td и другие теги могут быть вложены друг в друга множество раз. В таком случае разумно использовать выражения XPath, такие как //h1 или //h1[contains('Ben')]. SimpleXML имеет этот набор функциональных возможностей через функцию xpath().
Все заголовки (заголовок канала и заголовки публикаций) можно получить следующим образом:
<html xml:lang="en" lang="en"><head>
<title>XPath Example</title>
</head>
<body>
<?php $rss = simplexml_load_file('http://example.ru/atom.xml');
foreach ($rss->xpath('//title') as $title) {
echo "<h2>". $title. "</h2>";
}
?>
</body>
</html>
SimpleXML поддерживает только строковые выражения XPath, задающие местонахождении файла и объединения этих выражений. Он не поддерживает выражения XPath, которые не возвращают множества узлов, таких как, например, count(//para).
Начиная с PHP версии 5.1, SimpleXML может создавать XPath-запросы относительно документов с пространствами имен. Как обычно в XPath, строковое выражение, задающее местонахождение файла, должно использовать префиксы пространств имен, даже если искомый документ использует пространство имен по умолчанию. Функция registerXPathNamespace() ассоциирует префикс с пространством имени URI для использования в следующем запросе.
Например все элементы title в Atom документе можно найти следующим образом:
$atom = simplexml_load_file('http://example.ru/atom.xml');$atom->registerXPathNamespace('atm', 'http://www.w3.org/2005/Atom');
$titles = $atom->xpath('//atm:title');
foreach ($titles as $title) {
echo "<h2>". $title. "</h2>";
}
Недостатком работы с XPath посредством SimpleXML является очень медленное вычисление XPath выражений. Т.е. поиск нужных участков документа занимает значительное время. Это ограничивает использование XPath в некоторых приложениях.
Некоторые итоги
SimpleXML - лучшее решение для PHP разработчика, если ему не нужно работать со смешанным контентом. Особенно хорошо SimpleXML работает с простыми данными в виде записей. Если структура документа не слишком запутанна, использование SimpleXML гораздо проще использования DOM API. Если вы не знаете структуру XML документа возможно использование XPath. Отсутствие проверки семантической валидности документа (соответсвия некоторому DTD или XSchema) и поддержки смешанного контента безусловно доставляет неудобства, не всегда препятствует работе. Если структура вашего документа заранее известна и не обладает большой вложенностью тега не откажите себе в удовольствии попробовать такое замечстельное новшество PHP 5, как SimpleXML.

