XML для PHP-разработчиков: Часть 2. Расширенные методы парсинга XML

Методы синтаксического анализа объемных или сложных XML-документов в PHP5

Вторая статья серии из трех частей посвящена методам синтаксического анализа XML в РНР5 с акцентом на парсинг объемных или сложных XML-документов.

Клифф Морган, программист, независимый писатель

Клифф Морган (Cliff Morgan) — независимый консультант по проектированию и внедрению Web-приложений и Web-сайтов.



15.04.2010

Введение

В PHP5 увеличено разнообразие методов синтаксического анализа (парсинга) XML. Парсер Expat SAX Джеймса Кларка, который теперь основан на библиотеке libxml2, больше не является единственным полнофункциональным парсером. Нам уже знакома возможность парсинга посредством DOM в полном согласии со стандартом W3C. Дополнительные возможности предлагает SimpleXML, который мы рассмотрели в части 1 (см. Ресурсы), и XMLReader, который проще в понимании и быстрее в работе, чем SAX. Все расширения XML теперь основаны на библиотеке libxml2 проекта GNOME. Эта унифицированная библиотека позволяет разным расширениям взаимодействовать друг с другом. В настоящей статье обсуждаются методы парсинга XML в РНР5 с акцентом на объемные или сложные XML-документы. В ней рассказывается о методах парсинга и о критериях выбора, когда он есть, оптимального метода для синтаксического анализа XML-документов разного типа.


SimpleXML

В первой статье этой серии содержится важная информация об XML и инструкции по началу работы с интерфейсами прикладных программ (API). В ней демонстрировалось, что SimpleXML в сочетании с Document Object Model (DOM) служит идеальным инструментом для разработчиков, имеющих дело с простыми, предсказуемыми и относительно компактными XML-документами.

XML и PHP5

Язык Extensible Markup Language (XML) можно назвать и языком разметки, и форматом хранения текстовых данных; он предоставляет текстовые средства для описания древовидных структур и их применения к информации.

В РНР5 имеются как новые, так и переписанные расширения для XML-парсинга. SimpleXML, DOM и процессор XSLT загружают в память весь XML-документ целиком. Simple API for XML (SAX) и XMLReader относятся к тем парсерам, которые обрабатывают XML-документ по частям. SAX функционирует так же, как в РНР4, но теперь он основан на библиотеке libxml2, а не expat. Тем, кто знаком с DOM по другим языкам, в РНР5 будет легче с ним работать, чем в предыдущих версиях РНР.


Основы XML-парсинга

Существует два основных способа XML-парсинга: на базе деревьев и на базе потоков. Метод дерева предусматривает загрузку в память всего XML-документа целиком. Древовидная структура файла позволяет произвольно обращаться к элементам документа и редактировать XML. Примерами парсеров по методу дерева служат DOM и SimpleXML. Они хранят древовидную структуру в памяти в разных, но взаимодействующих форматах. При поточном парсинге весь документ в память не загружается. В данном случае термин «поток» употребляется в том же смысле, что и при описании поточного аудио. Происходит то же самое и по тем же причинам: данные поступают мелкими порциями с целью экономии полосы пропускания и ресурсов памяти. При поточном парсинге доступен только тот узел, который анализируется в данный момент, а редактирование XML-документа целиком невозможно. Примерами поточных парсеров служат XMLReader и SAX.


Парсеры, работающие по методу дерева

Парсеры, работающие по методу дерева, загружают в память весь документ, так что корень напоминает ствол дерева, а все дочерние, внучатые и более отдаленные потомки и атрибуты служат ветвями. Самый известый парсер, работающий по методу дерева, это DOM. Самый простой — SimpleXML. Рассмотрим тот и другой.

Парсинг при помощи DOM

Стандарт DOM, согласно W3C, представляет собой «...не зависящий от платформы и языка программирования интерфейс, который позволяет программам и сценариям динамически обращаться к документам и редактировать их содержание, структуру и стиль». Библиотека libxml2 проекта GNOME реализует DOM вместе со всеми его методами на языке С. Так как все XML-расширения РНР5 основаны на libxml2, они поддерживают полное взаимодействие друг с другом. Это взаимодействие значительно улучшает их функциональность. Например, можно извлечь элемент при помощи поточного парсера XMLReader, импортировать его в DOM и извлечь данные с использованием XPath. Подтверждение подобной гибкости продемонстрировано листинге 5.

Парсер DOM работает по методу дерева. Он прост для понимания и применения, так как его структура в памяти напоминает оригинальный XML-документ. DOM передает информацию приложению, создавая дерево объектов, в точности повторяющее дерево элементов из XML-файла, так что каждый элемент XML служит узлом этого дерева. DOM – это стандарт W3C, что принесло ему признание разработчиков ввиду его согласованности с другими языками программирования. Так как DOM строит дерево всего документа, он потребляет большой объем памяти и много ресурсов процессора.

DOM в действии

Если ввиду какого-то ограничения вы вынуждены выбрать единственный парсер, имеет смысл выбрать DOM хотя бы в силу его гибкости. DOM позволяет составлять XML-документы, модифицировать их, обращаться к ним, проверять и преобразовывать их. При этом можно использовать все методы и свойства DOM.Большинство методов DOM второго уровня реализованы с надлежащей поддержкой свойств. Благодаря чрезвычайной гибкости DOM анализируемые документы могут быть чрезвычайно сложными. Однако помните, что за гибкость приходится платить тем, что весь документ загружается в память целиком.

В примере, приведенном в листинге 1, DOM применяется для парсинга документа и извлечения элемента при помощи функции getElementById. Перед ссылкой на идентификатор документ необходимо проверить, установив validateOnParse=true. Согласно стандарту DOM, для этого требуется DTD, который определяет атрибут ID как тип ID.

Листинг 1. Использование DOM с простым документом
<?php

$doc = new DomDocument;

// Прежде чем ссылаться на id, документ нужно проверить
$doc->validateOnParse = true;
$doc->Load('basic.xml');

echo "The element whose id is myelement is: " . 
$doc->getElementById('myelement')->tagName . "\n";

?>

Функция getElementsByTagName() возвращает новый экземпляр класса DOMNodeList, содержащий элементы с заданным именем тега. Конечно, нужно пролистать список. Изменение структуры документа во время работы со списком NodeList, выданным функцией getElementsByTagName(), влияет на список NodeList, с которым вы работаете (см. листинг 2). Проверка в данном случае не требуется.

Листинг 2. Метод DOM getElementsByTagName
DOMDocument {
 DOMNodeList getElementsByTagName(string name);
}

В примере из листинга 3 DOM используется с XPath.

Листинг 3. Использование DOM и парсинг с применением XPath
<?php

$doc = new DOMDocument;

// Мы не хотим возиться с пробелами
$doc->preserveWhiteSpace = false;

$doc->Load('book.xml');

$xpath = new DOMXPath($doc);

// Мы начали с корневого элемента
$query = '//book/chapter/para/informaltable/tgroup/tbody/row/entry[. = "en"]';

$entries = $xpath->query($query);

foreach ($entries as $entry) {
  echo "Found {$entry->previousSibling->previousSibling->nodeValue}," .
       " by {$entry->previousSibling->nodeValue}\n";
}
?>

Высказав все эти приятные вещи по поводу DOM, я хочу привести пример того, что не надо делать с DOM, а затем, в следующем примере, показать, как это обойти. Листинг 4 иллюстрирует загрузку объемного файла в DOM только с целью извлечения данных из одного атрибута при помощи DomXpath.

Листинг 4. Неправильное использование DOM с XPath при работе с объемным XML-документом
<?php

// Парсинг объемного документа при помощи DOM и DomXpath
// Сначала создадим новый документ DOM для парсинга
$dom = new DomDocument();

//  Это большой документ, а нам не нужно ничего, кроме дерева
//  Этот огромный документ занимает массу памяти 
$dom->load("tooBig.xml");
$xp = new DomXPath($dom);
$result = $xp->query("/blog/entries/entry[@ID = 5225]/title") ;
print $result->item(0)->nodeValue ."\n";

?>

В последнем примере (листинг 5) DOM с XPath применяется точно так же, только данные передаются в XMLReader по одному элементу за раз с использованием метода expand(). При этом узел, переданный XMLReader, преобразуется в DOMElement.

Листинг 5. Правильное использование DOM с XPath при работе с объемным XML-документом
<?php

// Парсинг большого документа посредством XMLReader с Expand - DOM/DOMXpath 
$reader = new XMLReader();

$reader->open("tooBig.xml");

while ($reader->read()) {
   switch ($reader->nodeType) {
       case (XMLREADER::ELEMENT):
       if ($reader->localName == "entry") {
           if ($reader->getAttribute("ID") == 5225) {
               $node = $reader->expand();
               $dom = new DomDocument();
               $n = $dom->importNode($node,true);
               $dom->appendChild($n);
               $xp = new DomXpath($dom);
               $res = $xp->query("/entry/title");
               echo $res->item(0)->nodeValue;
           }
       }
   }
}

?>

Парсинг при помощи SimpleXML

Расширение SimpleXML – еще один способ парсинга XML-документа. Для расширения SimpleXML требуется PHP5 и используется встроенная поддержка XPath. SimpleXML лучше всего работает с несложными данными XML. В том случае, если XML-документ не слишком сложен, глубок и не имеет смешанного контента, SimpleXML проще в применении, чем DOM, как и предполагает его название. Он интуитивно понятен, если вы работаете с известной структурой документа.

SimpleXML в действии

SimpleXML обладает многими преимуществами DOM и проще в программировании. Он позволяет легко обращаться к дереву XML, имеет встроенную поддержку проверки и XPath, а также взаимодействует с DOM, обеспечивая ему поддержку при чтении и записи XML-документов. Документы, анализируемые SimpleXML, пишутся легко и быстро. Однако помните, что, как и в случае DOM, простота и гибкость SimpleXML достигается ценой загрузки в память всего XML-документа целиком.

Код, приведенный в листинге 6, извлекает из примера XML-документа сюжет произведения, содержащийся в элементе <plot>.

Листинг 6. Извлечение фрагмента текста
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
  <book>
     <title>Great American Novel</title>
     <plot>
        Cliff meets Lovely Woman. Loyal Dog sleeps, but
        wakes up to bark at mailman.
     </plot>
     <success type="bestseller">4</success>
     <success type="bookclubs">9</success>
  </book>
</books>
XML;
?>
<?php

$xml = new SimpleXMLElement($xmlstr);
echo $xml->book[0]->plot; // "Cliff meets Lovely Woman. ..."
?>

С другой стороны, может понадобиться извлечь многострочный адрес. Когда у одного родительского элемента имеется несколько экземпляров дочернего элемента, применяется обычная методика итерирования. Эта функциональность демонстрируется в листинге 7.

Листинг 7. Извлечение нескольких экземпляров элемента
<?php
$xmlstr = <<<XML
<xml version='1.0' standalone='yes'?>
<books>
  <book>
     <title>Great American Novel</title>
     <plot>
        Cliff meets Lovely Woman.
     </plot>
     <success type="bestseller">4</success>
     <success type="bookclubs">9</success>
  </book>
  <book>
     <title>Man Bites Dog</title>
     <plot>
        Reporter invents a prize-winning story.
     </plot>
     <success type="bestseller">22</success>
     <success type="bookclubs">3</success>
  </book>
</books>
XML;
?>
<php

$xml = new SimpleXMLElement($xmlstr);

foreach ($xml->book as $book) {
  echo $book->plot, '<br />';
}
?

Кроме чтения имен элементов и их значений, SimpleXML может обращаться к атрибутам элемента. В листинге 8 производится обращение к атрибутам элемента; это делается точно так же, как обращение к элементам массива.

Листинг 8. Демонстрация обращения SimpleXML к атрибутам элемента
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
  <book>
     <title>Great American Novel</title>
     <plot>
        Cliff meets Lovely Woman.
     </plot>
     <success type="bestseller">4</success>
     <success type="bookclubs">9</success>
  </book>
  <book>
     <title>Man Bites Dog</title>
     <plot>
        Reporter invents a prize-winning story.
     <plot>
     <success type="bestseller">22</success>
     <success type="bookclubs">3</success>
  </book>
<books>
XML;
?>
<?php

$xml = new SimpleXMLElement($xmlstr);

foreach ($xml->book[0]->success as $success) {
  switch((string) $success['type']) {
  case 'bestseller':
     echo $success, ' months on bestseller list<br />';
     break;
  case 'bookclubs':
     echo $success, ' bookclub listings<br />';
     break;
  }
}

?>

В последнем примере (см. листинг 9) SimpleXML и DOM используются с расширением XMLReader. При помощи XMLReader данные передаются последовательно, по одному элементу, с использованием метода expand(). Этим методом узел, переданный XMLReader, можно преобразовать в DOMElement, а затем передать SimpleXML.

Листинг 9. Использование SimpleXML и DOM с расширением XMLReader для анализа объемного XML-документа
<?php

// Парсинг большого документа при помощи Expand и SimpleXML
$reader = new XMLReader();

$reader->open("tooBig.xml");

while ($reader->read()) {
   switch ($reader->nodeType) {
       case (XMLREADER::ELEMENT):
       if ($reader->localName == "entry") {
           if ($reader->getAttribute("ID") == 5225) {
               $node = $reader->expand();
               $dom = new DomDocument();
               $n = $dom->importNode($node,true);
               $dom->appendChild($n);
               $sxe = simplexml_import_dom($n);
               echo $sxe->title; 
           }
       }
   }
}

?>

Потоковые парсеры

Потоковые парсеры называются так потому, что они анализируют XML в потоке, во многом напоминая работу потокового аудио. В каждый момент времени они работают с одним отдельным узлом, а закончив, совершенно забывают о его существовании. XMLReader — это pull-парсер, и программирование для него во многом напоминает извлечение результата запроса к таблице базы данных при помощи курсора. Это облегчает работу с незнакомыми или непредсказуемыми XML-файлами.

Парсинг при помощи XMLReader

XMLReader — это потоковый парсер того типа, который часто называют курсорным или pull-парсером. XMLReader вытягивает информацию из XML-документа по требованию. Он основан на API, полученном из C# XmlTextReader. В PHP 5.1 он включен и задействован по умолчанию и основан на библиотеке libxml2. До выхода PHP 5.1 расширение XMLReader не было включено по умолчанию, но было доступно в PECL (см. Ресурсы). XMLReader поддерживает пространства имен и проверку, включая DTD и Relaxed NG.

XMLReader в действии

Как потоковый парсер, XMLReader хорошо подходит для работы с объемными XML-документами; программировать в нем намного легче и обычно быстрее, чем в SAX. Это лучший потоковый парсер.

В следующем примере (листинг 10) объемный XML-документ анализируется при помощи XMLReader.

Листинг 10. XMLReader с объемным XML-файлом
<?php

$reader = new XMLReader();
$reader->open("tooBig.xml");
while ($reader->read()) {
  switch ($reader->nodeType) {
  case (XMLREADER::ELEMENT):
     if ($reader->localName == "entry") {
        if ($reader->getAttribute("ID") == 5225) {
           while ($reader->read()) {
              if ($reader->nodeType == XMLREADER::ELEMENT) {
                 if ($reader->localName == "title") {
                    $reader->read();
                    echo $reader->value;
                    break;
                 }
                 if ($reader->localName == "entry") {
                    break;
                 }
              }
           }
        }
     }
  }
}
?>

Парсинг при помощи SAX

Simple API for XML (SAX) представляет собой потоковый парсер. События связаны с читаемым XML-документом, поэтому SAX программируется в стиле обратных вызовов. Существуют события для открывающих и закрывающих тегов элемента, сущностей и ошибок парсинга. Главная причина использования парсера SAX вместо XMLReader заключается в том, что парсер SAX иногда более эффективен и обычно лучше знаком. Важный недостаток — код для парсера SAX получается сложнее, и его труднее писать, чем для XMLReader.

SAX в действии

SAX должен быть знаком тем, кто работал с XML в PHP4, а расширение SAX в PHP5 совместимо с версией, к которой они привыкли. Так как это потоковый парсер, он хорошо справляется с объемными файлами, но это не лучший выбор, чем XMLReader.

В листинге 11 приведен пример обработки объемного XML-документа парсером SAX.

Листинг 11. Использование SAX для анализа объемного XML-файла
<?php

//Этот класс содержит все методы обратного вызова,
//которые автоматически управляют данными XML.
class SaxClass {
  private $hit = false;
  private $titleHit = false;

  //обратный вызов для начала каждого элемента
  function startElement($parser_object, $elementname, $attribute) {
     if ($elementname == "entry") {
        if ( $attribute['ID'] == 5225) {
           $this->hit = true;
        } else {
           $this->hit = false;
        }
     }
     if ($this->hit && $elementname == "title") {
        $this->titleHit = true;
     } else {
        $this->titleHit =false;
     }
  }

  //обратный вызов для конца каждого элемента
  function endElement($parser_object, $elementname) {
  }

//обратный вызов для содержимого каждого элемента
  function contentHandler($parser_object,$data)
  {
     if ($this->titleHit) {
        echo trim($data)."<br />";
     }
  }
}

//Функция запуска парсинга, когда все значения установлены
//и файл открыт
function doParse($parser_object) {
  if (!($fp = fopen("tooBig.xml", "r")));

  //прокрутка данных
  while ($data = fread($fp, 4096)) {
     //анализ фрагмента
     xml_parse($parser_object, $data, feof($fp));
  }
}

$SaxObject = new SaxClass();
$parser_object = xml_parser_create();
xml_set_object ($parser_object, $SaxObject);

//Не меняйте регистр данных
xml_parser_set_option($parser_object, XML_OPTION_CASE_FOLDING, false);

xml_set_element_handler($parser_object,"startElement","endElement");
xml_set_character_data_handler($parser_object, "contentHandler");

doParse($parser_object);

?>

Заключение

PHP5 предлагает большое разнообразие методов парсинга. Парсинг при помощи DOM, который теперь полностью совместим со стандартом W3C, — знакомый вариант, подходящий для сложных, но относительно компактных документов. SimpleXML — это способ работы с простыми и не слишком объемными документами, а потоковый парсер XMLReader, который легче и быстрее, чем SAX, предпочтителен для очень больших документов.

Ресурсы

Научиться

  • Оригинал статьи (EN).
  • XML для PHP-разработчиков, часть 1: Работа с XML в PHPза 15 минут (Клифф Морган, developerWorks, февраль 2007 г.): В первой статье этой серии из трех частей рассказывается о реализации XML в РНР5 и о том, как легко работать с XML в среде РНР.
  • XML для PHP-разработчиков, часть 3: дополнительные методы чтения, организации и составления XML-документов (Клифф Морган, developerWorks, март 2007 г.): в заключительной части серии из трех статей об XML для РНР-разработчиков рассказывается о методах чтения, обработки и составления XML-документов в РНР5.(EN)
  • SAX, the power API (Benoît Marchal, developerWorks, август 2001 г.): Введение в SAX, сравнение DOM и SAX, инструкции по работе с SAX (EN).
  • Reading and writing the XML DOM in PHP (Jack Herrington, developerWorks, декабрь 2005 г.): Три метода чтения XML: библиотека DOM, синтаксический анализатор SAX и регулярные выражения. Также рассказывается, как писать XML с применением текстовых шаблонов DOM и PHP (EN).
  • What kind of language is XSLT (Michael Kay, developerWorks, апрель 2005 г.): Об истоках языка XSLT и его преимуществах (EN).
  • Tip: Implement XMLReader: An interface for XML converters (Benoît Marchal, developerWorks, ноябрь 2003 г.): В этой статье исследуются API для конвейеров XML и причины, по которым знакомый интерфейс XMLReader подходит для многих компонентов. (EN)
  • SimpleXML Processing with PHP (Elliotte Rusty Harold, developerWorks, октябрь 2006 г.): Расширение SimpleXML позволяет PHP-страницам обращаться к XML с запросами, вести в них поиск, модифицировать и повторно публиковать его (EN).
  • A PHP5 migration guide (Jack Herrington, developerWorks, September 2006 г.): Как перенести код, разработанный в PHP V4, на PHP V5, значительно улучшив его обслуживаемость и стабильность (EN).
  • Introducing Simple XML in PHP5 ( Alejandro Gervasio, Dev Shed, июнь 2006 г.): первая часть серии из трех статей, посвященных SimpleXML. Введение в расширение simplexml для РНР5 - библиотеку, предназначенную главным образом для синтаксического анализа простых XML-файлов (EN).
  • PHP Cookbook, Second Edition (Adam Trachtenberg and David Sklar, O'Reilly Media, август 2006 г.): О создании динамических Web-приложений, работающих с любыми Web-браузерами (EN).
  • XML.com: Сайт O'Reilly с обширной информацией по XML (EN).
  • W3C XML Information: Спецификация XML из первоисточника (EN).
  • Сайт, посвященный PHP-разработке (EN)
  • Planet PHP: Новости сообщества PHP-разработчиков (EN).
  • Сертификация IBM по XML: Как стать сертифицированным IBM разработчиком XML и родственных технологий. (EN)
  • Техническая библиотека по XML: Широкий спектр технических статей и рекомендаций, руководств, стандартов и технических руководств IBM в разделе XML сайта developerWorks. (EN)
  • Технические мероприятия и Web-трансляции developerWorks(EN)

Получить продукты и технологии

  • PEAR: PHP Extension and Application Repository: Подробные сведения о PEAR, среде разработки и системе дистрибуции РНР-компонентов (EN).
  • PECL: PHP Extension Community Library: Родственный сайт по PEAR и репозиторий расширений PHP (EN).
  • xmllib2: XML-парсер на языке C и набор инструментов от Gnome.(EN)
  • Используйте в своем следующем Open Source-проекте ознакомительное ПО IBM, которое можно загрузить прямо с сайта developerWorks.(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source
ArticleID=482508
ArticleTitle=XML для PHP-разработчиков: Часть 2. Расширенные методы парсинга XML
publish-date=04152010