В статье рассматриваются основы работы с объектами типа XMLReader.
PHP - очень интересный язык программирования, предоставляющий массу возможностей, что называется, прямо из коробки. В последних версиях PHP (начиная с 5.1) в стандартную поставку вошел класс XMLReader, предназначенный для чтения файлов XML. В принципе, объект-то достаточно понятный, однако я столкнулся с одной неприятной особенностью, что и побудило меня написать данную статью. Но обо всем по порядку.
Итак, создаем объект класса XMLReader:
1 |
$reader = new XMLReader(); |
Далее нужно открыть файл. Для этого существуют два метода: Open
и XML
. Следуя букве описания языка, первый метод предназначен для открытия XML-файлов, а второй - для передачи XML-содержимого в виде строки. Однако довольно часто второй метод используется и для открытия файлов. Воспользуемся, например, Open
:
1 2 3 |
В принципе, проверить результат открытия не повредит, хотя, сдается мне, пустой файл открывается с положительным результатом. Так что будьте внимательны. Еще один момент - поддерживается протокол http, поэтому вы можете разбирать файл, который может быть расположен вообще на другом сервере.
Теперь, собственно, переходим к разбору. Во-первых, если есть схема документа, то можно сразу же проверить его на соответствие схеме. Первый вариант - это когда в соответствии с полным стандартом XML схема указана в заголовке XML-файла. Тогда валидацию можно произвести так:
1 2 3 4 |
$reader ->setParserProperty(XMLReader::VALIDATE, true); if (! $reader ->isValid()) { die ( 'Неправильный XML' ); } |
Подобный код нужно вызывать после открытия документа (методом Open или XML), но до первого считывания (методом Read).
Другой вариант - это воспользоваться форматом описания схем RELAX NG, особенно если XML-документ достаточно простой. Тем более что такую схему можно описать в виде строки прямо в php-скрипте - необязательно иметь ее в виде файла. К сожалению, более-менее внятной спецификации Relax NG на русском языке я с ходу не нашел, пожалуй, можно обратиться к широко рекламируемым в последнее время пособиям от IBM, в частности к статье Валидация XML-документов. В этом случае для установки схемы Relax NG используются методы setRelaxNGSchema
, если схема сохранена в виде файла, и setRelaxNGSchemaSource
, если схема передается в виде строки.
Теперь самое время приступить к чтению. В общем-то, в самом общем виде это цикл while ($reader->read())
. Если же быть чуть более конкретным, то у нас будет серия вложенных циклов, в которых мы будем смотреть на текущий так называемый тип узла (node) и его имя, и, в зависимости от этого, предпринимать какие-либо действия, например, открывать вложенный цикл. И так далее. Также мне представляется удобным сформировать из содержимого XML-файла ассоциативный массив, с которым потом и работать после считывания. Это, по идее, уменьшает вероятность ошибок и увеличивает гарантию того, что XML мы считали правильно.
Что же касается типов узлов, то их довольно много, наиболее важные из них:
- XMLReader::ELEMENT - начало элемента (<element>)
- XMLReader::ATTRIBUTE - атрибут элемента (<element hereisattribute="123">)
- XMLReader::END_ELEMENT - конец элемента (</element>)
- XMLReader::TEXT - содержимое элемента (в общем-то, это существенная часть между <element> и </element>, хотя в этой области также обитают, например XMLReader::WHITESPACE - незначащие пробелы - и т.п.)
Итак, набросок кода цикла по XML файлу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
$xmlarr = array (); $idx = 0; while ( $reader ->read()) { if (( $reader ->nodeType == XMLReader::ELEMENT) && ( $reader ->name == 'Element1' )) { // считываем атрибуты $xmlarr [ $idx ][ 'Attr1' ] = $reader ->getAttribute( 'Attr1' ); while ( $reader ->read()) { // разбираем вложенные элементы if (( $reader ->nodeType == XMLReader::ELEMENT) && ( $reader ->name == 'Element11' )) { while ( $reader ->read()) { if ( $reader ->nodeType == XMLReader::TEXT) { // получаем значение из свойства $reader->value; $xmlarr [ $idx ][ $reader ->name] = $reader ->value; } elseif (( $reader ->nodeType == XMLReader::ELEMENT) && ( $reader ->name == 'Element111' )) { // еще один вложенный элемент while ( $reader ->read()) { if ( $reader ->nodeType == XMLReader::TEXT) /* и т.д. */ { $xmlarr [ $idx ][ 'Element11' ][ $reader ->name] = $reader ->value; } elseif (( $reader ->nodeType == XMLReader::END_ELEMENT) && ( $reader ->name == 'Element111' )) { break ; } } } elseif (( $reader ->nodeType == XMLReader::END_ELEMENT) && ( $reader ->name == 'Element11' )) { break ; } } } elseif (( $reader ->nodeType == XMLReader::ELEMENT) && ( $reader ->name == 'Element12' )) { while ( $reader ->read()) { if ( $reader ->nodeType == XMLReader::TEXT) { // ... = $reader->value; } elseif (( $reader ->nodeType == XMLReader::END_ELEMENT) && ( $reader ->name == 'Element12' )) { break ; } } } elseif (( $reader ->nodeType == XMLReader::END_ELEMENT) && ( $reader ->name == 'Element1' )) { $idx += 1; break ; } } } } |
Итак, мы пользовались такими свойствами, как nodeType
(тип узла), name
(наименование узла) и value
(значение), а также методом getAttribute для получения значения атрибута по заранее известному имени. Если же атрибуты заранее неизвестны, то тогда по аналогии открывается вложенный цикл с проверкой $reader->nodeType == XMLReader::ATTRIBUTE
. В общем-то, здесь самое сложное не запутаться во вложенных циклах
А теперь, та самая неприятная особенность, с которой я столкнулся и из-за которой, в немалой степени, я и засел за написание данной статьи. Дело в том, что при использовании самозакрывающихся элементов (например, <element1 Attr1="error" />
), в отличие, например, от парсера, который использует 1С: Предприятие 8 (видимо, какую-то Windows-библиотеку), не распознает /> как nodeType == XMLReader::END_ELEMENT
. Так что мой совет - избегать подобного рода самозакрывающиеся элементы. Хотя я и не отрицаю возможности, что у меня были устаревшие библиотеки PHP (в частности libxml), поэтому в последних версиях, возможно, эта ошибка (я склонен все-таки считать эту особенность именно ошибкой) исправлена.
Ну что ж, надеюсь, данной статьей я, так сказать, ввел вас в курс дела, ну а для более серьезного изучения вопроса я, пожалуй, снова сошлюсь на IBM-овское пособие - Синтаксический анализ XML в PHP, однако нужно иметь в виду, что та статья довольно-таки обширная, и там рассматривается данный вопрос с точки зрения организации AJAX.
P.S. Не забудьте закрыть XML-файл
1 |
$reader ->close(); |
Замечания и предложения
На старой версии сайта к статье поступили такие замечания и предложения.
Mrb: решение для этой проблемы следующее:
1 2 3 4 5 |
if (( $reader ->nodeType == XMLReader::ELEMENT) && ( $reader ->name == 'Element11' ) && ! $reader ->isEmptyElement) { //код... } |
В этом случае скрипт будет проверять не является ли элемент пустым, то бишь самозакрывающимся.
Виталий
1 |
|
Вот и всё решение проблемы.
LIBXML_NOEMPTYTAG (integer) - разворачивать пустые тэги (например <br/> в <br></br>).
Категория: Программирование, веб | Опубликовано 03.05.2009 | Редакция от 15.01.2017
Похожие материалы
cURL - если запрещено allow_url_fopen (PHP)
Если нужно работать с каким-либо внешним источником данных, а функции вида file_get_contents не работают из-за запрета allow_url_fopen, то можно попробовать воспользоваться расширением cURL. Его применение рассмотрим на примере работы с Яндекс.XML.