Welcome to php club

Переписанная с нуля поддержка XML

Краткое описание

Введение

В сегодняшнем интернете XML не модное слово, а широко распространенный и используемый стандарт. Поэтому поддержка XML в PHP5 была одним из приоритетных направлений развития. В PHP4 вам приходилось сталкиваться с несовместимой со стандартами, допускающей утечки памяти и неполной реализацией. Несмотря на то, что некоторые из этих недостатков были исправлены в PHP4.3, разработчики решили забыть старую реализацию и написать все с нуля для PHP5.
Далее мы рассмотрим, что может дать PHP5 в плане поддержки XML, и какие подводные и несовместимости с PHP4 он имеет.
Для примеров используется файл с именем articles.xml такого содержания:

XML в PHP4

PHP поддерживал XML почти с самого начала. Несмотря на то, что поддерживалась только SAX интерфейс, он как минимум позволял разобрать любой XML-документ без особых проблем. Дальнейшая поддержка XML заключалась в domxml расширении PHP4. Потом было добавлено расширение xslt с Sablotron-ом в качестве нижнего уровня. Во время разработки PHP4 в расширение domxml добавилась поддержка HTML, XSLT и проверка по DTD. К сожалению, т.к. расширения xslt и domxml никогда не выходили из экспериментальной стадии и меняли свой интерфейс не один раз, они не компилировались по умолчанию и часто не были установлены. Более того расширение domxml не реализовывало стандарт DOM, определенный W3С, а имело свою систему именования методов. В то время как попытки исправить данную ситуацию были предприняты в PHP4.3 вместе с кучей утечек памяти и других ошибок, расширение никогда не достигало стабильности, и все равно невозможно было исправить глубокие ошибки. К тому же только расширение, реализующее SAX интерфейс, компилировалось по умолчанию, а другие никогда не имели широкого распространения.
По всем этим причинам разработчики PHP, ответственные за XML начать все с нуля и следовать стандартам.

XML в PHP5

В PHP5 почти все, касающееся поддержки XML, было полностью переписано. Все XML-расширения теперь используют великолепную библиотеку libxml2 из проекта GNOME. Это позволило взаимодействовать разным XML-расширениям, т.к. разработчики теперь работают только с одной базовой библиотекой. Например, довольно сложное и сейчас сильно улучшенное управление памятью реализовывалось только один раз для всех XML-расширений.
В дополнение к хорошо известной SAX модели наследованной от PHP4, PHP5 поддерживает DOM, в соответствии со стандартами W3C и XSLT с помощью очень быстрого процессора из libxstl. Он также включает новое специфичное для PHP расширение Simple XML? и сильно улучшенное и совместимое со стандартами расширение SOAP. Понимая возрастающую XML, разработчики PHP решили компилировать по умолчанию больше XML-расширений. Это означает, что теперь вы получите SAX, DOM и Simple XML? компилируемыми по умолчанию, что приведет к установке данных расширений на большем количестве серверов в будущем. XSLT и SOAP, однако, по-прежнему требуют прямого включения при компиляции.

Поддержка потоков

Все XML-расширения теперь поддерживают потоки повсюду, даже если доступ к ним производит не напрямую из PHP. В PHP5 возможно обратиться к потоку, например, из директивы <xsl:include> или из <xi:xinclude>. В общем, доступ к потокам доступен всюду, где предполагается доступ к файлу.
(Потоки впервые появившиеся в PHP4.3 и улучшенные в PHP5, предназначены для обобщения доступа к файлам, сетевым ресурсам и других операций, которые разделяют общий набор функций. Также возможно создание пользовательских обработчиков потоков. Подробнее о них можно прочитать в документации к PHP5.)

SAX

SAX расшифровывается как “Simple API for XML”, что переводится как «Простой интерфейс для XML”. Это интерфейс, основанный на обратных вызовах для разбора XML-докуметов. Поддержка SAX появилась в PHP еще в третьей его версии и долго менялась с тех пор. Для PHP5 программный интерфейс не поменялся, так что старый код должен работать без проблем. Единственными отличием является нижележащая библиотека: expat в PHP4 и libxml2 в PHP5.

DOM

DOM (Document Object Model — объектная модель документа) — стандарт доступа к элементам XML, организованным в виде дерева, определенный W3C. В PHP4 расширение domxml использовалось для этого. Основной его проблемой было не следование стандартным именам методов. Также оно долгое время имело утечки памяти, которые были исправлены только в PHP4.3.
Новое расширение, реализующее интерфейс DOM, которое так и называется dom, полностью основано на спецификации W3C включая именования методов и свойств. Если вы знакомы с реализацией DOM в других языках программирования, например Java Script?, то вам будет намного проще написать PHP-код с аналогичной функциональностью. Вам не придется все время смотреть в документацию, т.к. методы и параметры идентичны.
Как следствие полной совмести со стандартами W3C ваш старый код, основанный на domxml, не будет работать — интерфейс-то разный. Однако, если использовались «почти совместимые» методы из PHP4.3, то портирование не является большой проблемой — нужно только изменить методы для сохранения и загрузки и убрать подчеркивания из имен методов (стандарт DOM использует «верблюжий» синтаксис). Возможно, потребуют и другие незначительные изменения, но логика останется прежней.
Также следует отметить, что почти все функции, возвращающие массивы в PHP4, в PHP5 возвращают объект класса DomNodeList. Т.е. прямой доступ к элементам этого списка доступен не через квадратные скобки $list[0];, а через метод item(), также описанный в спецификации, — $list->item(0);.
Если же переписывание кода нежелательно по какой-либо причине, то в данном случае есть два выхода:
1) Расширение domxml было портировано под PHP5 и доступно в репозитарии PECL. Для продолжения работы старых скриптов его нужно только установить.
2) В интернете можно найти обертки для нового расширения dom, реализующие интерфейс domxml.
В принципе есть еще один способ — написать свою обертку. Но это путь мазохиста, т.к. зачем изобретать велосипед, тем более, что одна из реализаций (http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/domxml-php4-to-php5.php.txt) довольно хороша и поддается расширению.
Приведем, для лучшего понимания, небольшой пример работы с новым расширением:

<?php
$dom
= new DomDocument();
$dom->load("articles.xml");

// так
$titles = $dom->getElementsByTagName("title");
foreach (
$titles as $node) {
    echo
$node->textContent . "\n";
}

// или так
foreach ($dom->documentElement->childNodes as $articles) {
    
// если это элемент (nodeType == 1) и его имя "item", запускаем цикл
    
if ($articles->nodeType == 1 && $articles->nodeName == "item") {
        foreach (
$articles->childNodes as $item) {
            
// если это элемент и его имя "title", выводим его
            
if ($item->nodeType == 1 && $item->nodeName == "title") {
                echo
$item->textContent . "\n";
            }
        }
    }
}
?>

XPath

XPath это в некотором роде язык SQL для XML. C помощью XPath возможно получить список элементов отвечающих некоторым параметрам.
Поскольку поддержка XPath реализована в расширении dom и полагается на него, то все сказанное ранее про расширение dom относится и к поддержке XPath. Однако привести пример того, как теперь производится работа с XPath:

<?php
$dom
= new DomDocument();
$dom->load("articles.xml");

$xp = new domXPath($dom);
$titles = $xp->query("/articles/item/title");
foreach (
$titles as $node) {
    print
$node->textContent . "\n";
}
?>

Расширение классов

В PHP5 стало возможным расширять встроенные класса путем создания потомка. К их числу относится и встроенные классы расширения dom. Однако в данный момент реально можно расширить только класс DomDocument и тот с некоторыми оговорками. Остальные классы вы можете расширить, создать их экземпляры, но практически любое действие по их изменению вызывает исключение DomException с кодом ошибки NO_MODIFICATION_ALLOWED_ERR.
Вот как это делается:

<?php
class Articles extends DomDocument {
    function
__construct() {
        
//не забываем вызвать конструктор родителя
        
parent::__construct();
    }

    function
addArticle($title) {
        
$item = $this->createElement("item");
        
$titlespace = $this->createElement("title");
        
$titletext = $this->createTextNode($title);
        
$titlespace->appendChild($titletext);
        
$item->appendChild($titlespace);
        
$this->documentElement->appendChild($item);
    }
}
$dom = new Articles();
$dom->load("articles.xml");
$dom->addArticle("XML in PHP5");
echo
$dom->save("newfile.xml");
?>

HTML

В PHP5 появилась возможность, которую многие хотели увидеть еще в PHP4, реализованная в libxml2 — поддержка HTML. Это означает, что теперь возможно не только загрузить правильно сформированный XML, но даже неверно сформированный HTML и получить из него обычный объект DomDocument, с которым можно использовать все возможности DOM, XPath и Simple XML?.
Данная возможность очень полезна, когда нужно получить доступ к содержимому сайта неподконтрольного вам. (Вопросы о воровстве этого самого содержимого оставим в стороне.) С помощью XPath, XSLT и Simple XML? можно сэкономить кучу времени по сравнению с моделью SAX или регулярными выражениями. Это особенно полезно, если HTML-документ плохо структурирован, что является довольно частой проблемой.
В придачу ко всем полезным функциям по чтению HTML DOM-дерево может быть также сохранено в виде HTML версии 4.0, что бывает полезно, когда какое-нибудь ПО не понимает XHTML.
Прочитаем что-нибудь с сайта php.net:

<?php
$dom
= new DomDocument();
$dom->loadHTMLFile("http://www.php.net/");
$title = $dom->getElementsByTagName("title");
echo
$title->item(0)->textContent;
?>

Проверка на правильность (валидация)

Проверка на правильность XML-документов становится все более и более необходимой. Например, когда вы получаете XML-документ на стороне, то должны вначале проверить его на соответствие некоторым требованиям, прежде чем его обработать. К счастью не нужно писать свой PHP-код для проверки, т.к. возможно использовать один из трех распространенных стандартов для этого: DTD, XML Schema или Relax NG?.
DTD — стандарт появившийся во времена SGML и не имеет поддержки некоторых новых возможностей XML, например, пространств имен. Также поскольку он основан на XML, его довольно трудно разбирать и/или трансформировать.
XML Schema — стандарт определенный W3C. Он очень обширный и имеет почти все, что может понадобиться для валидации XML-документов. Однако в этом и его недостаток: он крайне сложен как в восприятии, так и в реализации.
Relax NG? — ответ на сложный стандарт XML Schema, созданный независимой группой. Все большее количество программный продуктов поддерживают его, т.к. он значительно проще в реализации, чем XML Schema.
Если у вас нет старых Schema-документов или очень сложных XML-документов, используйте Relax NG?-документы. Их проще написать, проще прочитать и все больше количество инструментов поддерживают его. Существует даже программа Trang, которая автоматически создает Relax NG?-документ на основе XML-документа. Более того только Relax NG? (и устаревший DTD) полностью поддерживаются libxml2, хотя полная поддержка XML Schema ожидается в скором времени.
Действия необходимые для валидации документов довольно просты.

<?php
$dom
->validate ('articles.dtd');
$dom->relaxNGValidate ('articles.rng');
$dom->schemaValidate ('articles.xsd');
?>

В настоящее время все эти методы просто возвращают истину или ложь. Ошибки выводятся в виде предупреждений PHP. Очевидно, что не лучший способ обратной связи и он будет улучшен в одной из версий после PHP5.0.0.

SimpleXML

Simple XML? — последнее дополнение в области поддержки XML в PHP. Цель Simple XML? — простой доступ к XML-документам, используя стандартные свойства объектов и итераторы. Это расширение имеет относительно мало методов, но, однако, довольно мощное.
Приведем для наглядности небольшой пример, который выводит все заголовки статей:

<?php
$sxe
= simplexml_load_file("articles.xml");
foreach (
$sxe->item as $item) {
    echo
$item->title . "\n";
}
?>

Удивлены размерами кода? Не забывайте, что это SimpleXML. Что это код делает? Вначале он загружает файл articles.xml в виде SimpleXML объекта. Дальше он получает все элементы с именем item, используя для этого свойство $sxe->item. В итоге $item->title дает нам содержимое элемента title. Доступ к атрибутам также можно легко получить: $item->title['id'].
Как можно видеть за этой простотой скрывается некоторая «магия», к тому же имеются несколько путей, чтобы достичь нужного эффекта. Например, $item->title[0] возвращает то же самое, что и в примере. Однако не следует думать, что $sxe->item->title вернет все заголовки в документе – это не XPath. Расширение Simple XML? первое, использующее большинство возможностей доступных в Zend Engine 2?. Оно также является тестовой площадкой для этих новых возможностей.
Кроме традиционного метода «цикл по всем узлам», показанным в примере, существует также и интерфейс XPath, который предоставляет еще более легкий доступ к индивидуальным узлам.
<?php
$sxe
= simplexml_load_file("articles.xml");
foreach (
$sxe->xpath('/articles/item/title') as $item) {
    echo
$item . "\n";
}
?>

Вероятно, что этот код не короче, чем предыдущий, но при более сложных и глубоко вложенных XML-документах XPath вместе с Simple XML? сэкономит множество времени.

Запись в SimpleXML

Возможно не только чтение и анализ, но и модификация Simle XML?-документов.

<?php
$sxe
->item->title = "XML in PHP5"; // новый текст для элемента title
$sxe->item->title['id'] = 34; // новый атрибут для элемента title
$xmlString = $sxe->asXML (); // возвращает SimpleXML-объект как XML строку
echo $xmlString;
?>

Взаимодействие с Dom Document?

Поскольку Simple XML? также основан на libxml2, возможно конвертировать объекты SimpleXML в объекты DomDocument и наоборот без большой потери скорости, т.к. документ внутренне не копируется. Используя данный механизм возможно использование лучшего из обоих методик, используя ту, которая лучше подходит к конкретной задаче. Для этого используются следующие методы:

<?php
$sxe
= simplexml_import_dom ($dom);
$dom = dom_import_simplexml ($sxe);
?>

XSLT

XSLT — язык для трансформации XML-документов в другие XML-документы. Сам XSLT-документ является XML-документом и принадлежит к семейству функциональных языков, имеющих разное назначение по сравнению с процедурными и объектно-ориентированными языками типа PHP.
В PHP4 имелось 2 различных XSLT процессора: Sablotron (в наиболее широко используемом и известном расширении xslt) и libxslt (в расширении domxml). Два интерфейса были несовместимыми друг с другом и их возможности также различались.
В PHP5 поддерживается только процессор libxslt, реализованный в новом расширении xsl. Выбор пал на libxslt потому, что она основана на libxml2 и потому прекрасно вписывается в идею поддержку XML в PHP5.
Теоретическая возможность портировать интерфейс к Sablotron-у существует, однако, к сожалению никто этого пока не сделал. Потому если скрипты использовался Sablotron, то для PHP5 их нужно перенести под процессор libxslt. Процессор libxslt, за исключением поддержки Java Script?, по возможностям эквивалентен Sablotron-у. Даже специфичные для Sablotron-а обработчики схем могут быть реализованными через более мощные и переносимые потоки PHP. К тому же libxslt является одной из быстрых из доступных реализаций XSLT, что может привести к некоторому бесплатному увеличению скорости: увеличение быстродействия может достигать двух раз.
Как и со всеми другими расширениями, обсуждаемыми ранее, возможен обмен XML-документами между расширениями dom и xsl, реально даже нужен: расширение xsl не содержит интерфейса для загрузки и сохранения XML-документов, но использует их из расширения DOM.
Вам не нужно знать много методов для начала работы с XSLT трансформации, так же не существует никаких W3C стандартов для этого. Поэтому интерфейс был взят из Mozilla.
Для примеров необходима таблица стилей XSLT. Пусть это будет файл articles.xsl следующего содержания

Затем вызовем его из PHP-скрипта:

<?php
/* загружаем xml-файл и таблицу стилей в качестве DomDocument-ов */
$xsl = new DomDocument();
$xsl->load("articles.xsl");
$inputDom = new DomDocument();
$inputDom->load("articles.xml");

/* создаем процессор и импортируем таблицу стилей */
$proc = new XsltProcessor();
$xsl = $proc->importStylesheet($xsl);
$proc->setParameter(null, "titles", "Titles");

/* трансформируем и выводим xml-документ */
$newDom = $proc->transformToDoc($inputDom);
echo
$newDom->saveXML();
?>

Приведенный пример вначале загружает таблицу стилей XSLT articles.xsl с помощью DOM-метода load(). Потом он создает объект класса XsltProcessor, который импортирует таблицу стилей для дальнейшего выполнения, устанавливает параметр titles в значение “Titles” (параметры могут быть установлены с помощью метода setParameter($namespaceURI, $name, $value)). В конце он начинает трансформацию с помощью метода transformToDoc($inputDom), который возвращает новый DomDocument.
Данный интерфейс имеет преимущество, т.к. позволяет произвести несколько трансформаций XSLT, используя только одну таблицу стилей — просто загрузив и повторно используя ее, т.к. transformToDoc() может быть применен к различным XML-документам.
Кроме метода transformToDoc() существует еще два метода трансформации: transformToXML($dom), которая возвращает строку, и transformToURI($dom, $uri), который сохраняет трансформированный документ в файл или поток PHP. Следует заметить, что если используются возможности XSLT типа <xsl:output method="html"> или indent="yes", то невозможно воспользоваться transformToDoc(), т.к. DomDocument не поддерживает этого. Эти директивы будут использоваться только, если вывод производится напрямую в строку или файл.

Вызов PHP-функций

Одна из последних возможностей добавленных в расширение XSL — вызовы любой PHP-функции из таблицы стилей XSLT. Хотя это не приветствуется многими приверженцами XML/XSLT, может быть очень полезно в некоторых случаях. Поскольку XSLT крайне ограничен, когда дело доходит до функций. Даже вывод даты на разных языках очень сложно реализовать, но с помощью данной возможности это также не сложно, как и с помощью PHP. Приведем пример добавления функции в XSLT.
PHP-файл с функцией dateLang() и ее использованием:

<?php
function dateLang() {
    return
strftime("%A");
}

// Загружаем документы
$xsl = new DomDocument();
$xsl->load("datetime.xsl");
$inputDom = new DomDocument();
$inputDom->load("today.xml");

$proc = new XsltProcessor();
$proc->registerPhpFunctions();

$xsl = $proc->importStylesheet($xsl);

// Транформируем и выводим XML документ
$newDom = $proc->transformToDoc($inputDom);

echo
$newDom->saveXML();
?>

Таблица стилей XSLT (datetime.xsl), которая будет вызывать данную функцию:

Минимальный XML-файл (today.xml), чтобы прогнать его через таблицу стилей (хотя c articles.xml результат будет тот же):

Приведенная таблица стилей, вместе с PHP-скиптом и любым загруженным XML-файлом, выведет текущий день недели на языке, определенном текущей локалью. Возможно добавлять больше параметров в php:function(), которые будут переданы PHP функции. Кроме того, существует функция php:functionString(), которая автоматически конвертирует все параметры в строки, так что вам не нужно их конвертировать в функции.
Следует отметить, что перед трансформацией необходимо вызвать метод registerPhpFunctions(), иначе вызовы PHP-функций не будут работать по соображениям безопасности. Более гибкая система доступа, например, запрет доступа только к указанным функциям, пока не доступна, но будет несложно реализовать ее в одной из будущих версий PHP5.

Проблемы с обратной совместимостью

Практически полное отсутствие обратной совместимости.

Дополнительные сведения

Описание в оригинальной документации: http://www.php.net/xml http://www.php.net/dom http://www.php.net/xsl http://www.php.net/simplexml


 
Один комментарий. [Показать комментарии/форму]