Xml parse: примеры (PHP)

Использование xml_parse для обработки XML данных в PHP
Раздел: XML
xml_parse(resource parser, string data [, bool is_final]): int
Основные сведения о xml_parse

Функция xml_parse() является частью встроенного XML парсера Expat в PHP. Она выполняет разбор XML документа, передавая данные в ранее определенные функции-обработчики. Этот подход основан на событийной модели (SAX - Simple API for XML).

Использование функции актуально для последовательной обработки больших XML файлов, когда загрузка всего документа в память (как в DOM) нежелательна или невозможна.

Аргументы функции
  • parser (обязательный): ресурс, созданный функцией xml_parser_create(). Указывает на используемый XML парсер.
  • data (обязательный): строка, содержащая часть или весь XML документ для разбора.
  • is_final (опциональный, по умолчанию false): логическое значение. Если установлено в true, указывает парсеру, что это последний фрагмент данных.

Функция возвращает целое число: 1 при успешном разборе или 0 в случае ошибки.

Простые примеры использования
Базовый разбор строки
<?php
$parser = xml_parser_create();

function startElement($parser, $name, $attrs) {
    echo "Открыт тег: $name\n";
}

function endElement($parser, $name) {
    echo "Закрыт тег: $name\n";
}

function charData($parser, $data) {
    $data = trim($data);
    if ($data !== '') echo "Данные: $data\n";
}

xml_set_element_handler($parser, "startElement", "endElement");
xml_set_character_data_handler($parser, "charData");

$xml = "<book><title>PHP 8</title><author>Иван</author></book>";

if (xml_parse($parser, $xml, true)) {
    echo "Разбор завершен успешно.\n";
} else {
    echo sprintf("Ошибка: %s в строке %d",
        xml_error_string(xml_get_error_code($parser)),
        xml_get_current_line_number($parser));
}
xml_parser_free($parser);
?>
Открыт тег: BOOK
Открыт тег: TITLE
Данные: PHP 8
Закрыт тег: TITLE
Открыт тег: AUTHOR
Данные: Иван
Закрыт тег: AUTHOR
Закрыт тег: BOOK
Разбор завершен успешно.
Пошаговая обработка данных
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Отключаем приведение к верхнему регистру

$result = [];
$currentTag = '';

function startElement($parser, $name, $attrs) use (&$result, &$currentTag) {
    $currentTag = $name;
}

function charData($parser, $data) use (&$result, &$currentTag) {
    if (trim($data) && $currentTag) {
        $result[$currentTag] = trim($data);
    }
}

xml_set_element_handler($parser, "startElement", null);
xml_set_character_data_handler($parser, "charData");

// Имитация поступления данных частями
$chunks = [
    "<user><name>Мария",
    "</name><age>28",
    "</age></user>"
];

foreach ($chunks as $chunk) {
    xml_parse($parser, $chunk, false);
}
xml_parse($parser, '', true); // Финальный вызов

print_r($result);
xml_parser_free($parser);
?>
Array
(
    [name] => Мария
    [age] => 28
)
Альтернативные функции и подходы в PHP

PHP предлагает несколько методов работы с XML, каждый со своими особенностями.

SimpleXML

Простой объектно-ориентированный интерфейс для чтения и изменения XML. Преобразует XML в объект, с которым можно работать как с массивом. Идеален для небольших документов и простых задач.

$xml = simplexml_load_string('<book><title>Test</title></book>');
echo $xml->title;
Test
DOMDocument

Полная реализация DOM (Document Object Model). Позволяет создавать, изменять, валидировать и навигировать по XML. Требует больше памяти, но предоставляет полный контроль над документом.

$dom = new DOMDocument();
$dom->loadXML('<root><item>1</item></root>');
echo $dom->getElementsByTagName('item')->item(0)->nodeValue;
1
XMLReader

Как и xml_parse(), использует потоковое чтение (pull-parser), но с более удобным итеративным API. Рекомендуется для обработки больших файлов вместо устаревшего Expat.

$reader = new XMLReader();
$reader->XML('<data><value>42</value></data>');
while ($reader->read()) {
    if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'value') {
        echo $reader->readInnerXml();
    }
}
42
Аналоги в других языках программирования
Python: xml.sax

Модуль SAX в Python предоставляет схожую событийную модель. Обработчики определяются в классе, наследующем ContentHandler.

import xml.sax

class MyHandler(xml.sax.ContentHandler):
    def startElement(self, name, attrs):
        print(f"Начало: {name}")
    def characters(self, content):
        if content.strip():
            print(f"Текст: {content}")

handler = MyHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
parser.parseString("<note><body>Текст</body></note>")
Начало: note
Начало: body
Текст: Текст
JavaScript: SAX-парсеры

В браузерном JavaScript нет нативного SAX-парсера, но существуют библиотеки, например, sax-js для Node.js. Работа строится на подписке на события.

const sax = require('sax');
const parser = sax.parser(true);
parser.onopentag = (tag) => { console.log(`Открыт: ${tag.name}`); };
parser.ontext = (text) => { if(text.trim()) console.log(`Текст: ${text}`); };
parser.write('<doc>Привет</doc>').close();
Открыт: doc
Текст: Привет
MySQL и XML

MySQL предоставляет функции для извлечения данных из XML строк, такие как ExtractValue(). Однако это не полноценный парсер, а инструмент для простого XPath-запроса.

SELECT ExtractValue('<a><b>X</b></a>', '/a/b') AS result;
result
X
Типичные ошибки и их решение
Неверная кодировка

Парсер Expat по умолчанию работает с UTF-8. Если передать данные в другой кодировке без указания этого факта, возникнут ошибки разбора.

<?php
$parser = xml_parser_create();
$xml_windows1251 = mb_convert_encoding('<test>тест</test>', 'windows-1251', 'UTF-8');
// Пытаемся разобрать строку в CP1251 как UTF-8
if (!xml_parse($parser, $xml_windows1251, true)) {
    echo "Ошибка: ", xml_error_string(xml_get_error_code($parser));
}
?>
Ошибка: Invalid character

Решение: использовать xml_parser_create() с указанием кодировки или конвертировать данные.

Несоответствие структуры XML

Парсер строго следит за корректностью XML (закрытые теги, правильная вложенность).

<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, function(){}, function(){});
$bad_xml = "<open><inner></open></inner>"; // Неправильная вложенность
if (!xml_parse($parser, $bad_xml, true)) {
    echo "Строка: ", xml_get_current_line_number($parser), " Ошибка: ", xml_error_string(xml_get_error_code($parser));
}
?>
Строка: 1 Ошибка: Mismatched tag
Необработанные сущности

Парсер может не обработать XML сущности, если не установлен соответствующий обработчик.

<?php
$parser = xml_parser_create();
$xml_with_entity = '<!DOCTYPE test [ <!ENTITY example "Пример"> ]><test>&example;</test>';
// Без обработчика сущностей разбор может завершиться с ошибкой или проигнорировать &example;
xml_parse($parser, $xml_with_entity, true);
?>

Решение: использовать xml_set_external_entity_ref_handler() или отключить обработку DTD.

Изменения в новых версиях PHP

Функция xml_parse() и весь модуль XML Expat остаются стабильными и практически не менялись в последних основных версиях PHP. Основные изменения касаются не самой функции, а её контекста:

  • В PHP 8.0 ресурсы (resource), возвращаемые xml_parser_create(), были заменены на объекты класса XMLParser. Это изменение прозрачно для большинства пользователей, так как объекты передаются в xml_parse() так же, как и ресурсы. Тип аргумента parser теперь объявлен как XMLParser.
  • Были усилены типизация и проверки аргументов. Передача значения неправильного типа в PHP 8 вызовет TypeError, а в PHP 7 это могло вызвать предупреждение.
  • Модуль Expat считается устаревшим (legacy) по сравнению с XMLReader, но продолжает поддерживаться для обратной совместимости. Новые проекты рекомендуется использовать XMLReader для потокового разбора.
Расширенные примеры
Парсинг с извлечением атрибутов и построением дерева
Пример php
<?php
$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);

$tree = [];
$path = [];
$current = &$tree;

function startElement($parser, $name, $attrs) use (&$current, &$path) {
    $node = ['_tag' => $name, '_attrs' => $attrs, '_children' => []];
    $current[] = &$node;
    $path[] = &$current;
    $current = &$node['_children'];
}

function endElement($parser, $name) use (&$current, &$path) {
    $current = &array_pop($path);
}

function charData($parser, $data) use (&$current) {
    $data = trim($data);
    if ($data !== '') {
        $current[] = $data;
    }
}

xml_set_element_handler($parser, "startElement", "endElement");
xml_set_character_data_handler($parser, "charData");

$xml = '<catalog>
          <book id="1"><title>Война и мир</title><author>Толстой</author></book>
          <book id="2"><title>Преступление и наказание</title></book>
        </catalog>';

if (xml_parse($parser, $xml, true)) {
    // Очистка служебных ключей для наглядности
    function clean(&$arr) {
        foreach ($arr as &$item) {
            if (is_array($item)) {
                unset($item['_children'], $item['_tag']);
                clean($item);
            }
        }
    }
    clean($tree);
    echo "Успешно разобрано. Первая книга: ";
    print_r($tree[0]['book'][0]);
}
xml_parser_free($parser);
?>
Успешно разобрано. Первая книга:
Array
(
    [_attrs] => Array
        (
            [ID] => 1
        )
    [0] => Война и мир
    [1] => Толстой
)
Обработка больших файлов по частям с ограничением памяти
Пример php
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);

$recordCount = 0;
$inRecord = false;
$currentTag = '';
$currentData = [];

function startElement($parser, $name, $attrs) use (&$inRecord, &$currentTag, &$recordCount) {
    if ($name === 'RECORD') {
        $inRecord = true;
        $recordCount++;
    }
    $currentTag = $name;
}

function endElement($parser, $name) use (&$inRecord, &$currentData) {
    if ($name === 'RECORD') {
        $inRecord = false;
        // Здесь можно сохранить $currentData в БД или файл
        // echo "Запись #" . $recordCount . " обработана.\n";
        $currentData = [];
    }
    $currentTag = '';
}

function charData($parser, $data) use (&$inRecord, &$currentTag, &$currentData) {
    if ($inRecord && $currentTag && trim($data)) {
        @$currentData[$currentTag] .= trim($data);
    }
}

xml_set_element_handler($parser, "startElement", "endElement");
xml_set_character_data_handler($parser, "charData");

// Имитация чтения большого файла
$handle = fopen('large_data.xml', 'r');
if ($handle) {
    while (!feof($handle)) {
        $chunk = fread($handle, 4096); // Чтение по 4KB
        if (!xml_parse($parser, $chunk, feof($handle))) {
            echo sprintf("Ошибка в строке %d: %s\n",
                xml_get_current_line_number($parser),
                xml_error_string(xml_get_error_code($parser)));
            break;
        }
    }
    fclose($handle);
    echo "Обработано записей: $recordCount\n";
}
xml_parser_free($parser);
?>
Обработано записей: [число записей в файле]
Использование с пользовательскими сущностями
Пример php
<?php
function defaultHandler($parser, $data) {
    // Обработчик по умолчанию для сущностей и прочего
    echo "[Необработанные данные: $data]";
}

$parser = xml_parser_create();
xml_set_default_handler($parser, "defaultHandler");

$xml = '<doc><copy>©</copy> &nbsp; текст</doc>';
xml_parse($parser, $xml, true);
xml_parser_free($parser);
?>
[Необработанные данные: ©][Необработанные данные:  ] текст

PHP xml_parse function comments

En
Xml parse Start parsing an XML document