Как стать автором
Обновить

Работаем с XML как с массивом, на PHP

Время на прочтение5 мин
Количество просмотров28K

Всем привет. Хочу поделиться своим опытом в парсинге XML, хочу рассказать об инструменте который мне в этом помогает.

XML ещё жив и иногда его приходиться парсить. Особенно если вы работаете со СМЭВ (привет всем ребятам для которых "ФОИВ" не пустой звук :) ).

Цели у такого парсинга могут быть самые разные, от банального ответа на вопрос какое пространство имён используется в xml-документе, до необходимости получить структурированное представление для документа вцелом.

Инструмент для каждой цели будет свой. Пространство имён можно найти поиском подстроки или регулярным выражением. Что бы сделать из xml-документа структурированное представление (DTO) - придётся писать парсер.

Для работы с XML в PHP есть пара встроенных классов. Это XMLReader и SimpleXMLElement.

XMLReader

С помощью XMLReader парсинг будет выглядеть примерно так :

$reader = (new XMLReader());
$reader->XML($content);
while ($reader->read()) {
    $this->parse($reader);
}

Внутри метода parse(XMLReader $xml) будут бесконечные:

$name = $xml->name;
$value = $xml->expand()->textContent;
$attrVal = $xml->getAttribute('attribute');
$isElem = $xml->nodeType === XMLReader::ELEMENT;

Для небольших документов или когда нам из всего документа надо только пару элементов, это приемлемо, на больших объёмах - начинает в глазах рябить от однообразного кода, плюс совесть грызёт за оверхэд от перебора всех элементов документа.

SimpleXMLElement

Провести анализ только нужных элементов помогает SimpleXMLElement. Этот класс из XML-документа делает объект, у которого все элементы и атрибуты становятся свойствами, то есть появляется возможность работать только с определёнными элементами, а не со всеми подряд, пример:

$document = new SimpleXMLElement($content);
/* имя корневого элемента */
$name = $document->getName();

/* получить произвольный элемент */
$primary = $document
	->Message
	->ResponseContent
	->content
	->MessagePrimaryContent ?? null;

/* получить элементы определённого пространства имён */
$attachment = $primary
	->children(
		'urn://x-artefacts-fns-zpvipegr/root/750-08/4.0.1'
	)
	->xpath('tns:Вложения/fnst:Вложение')[0];

/* получить значение элемента */
$fileName = $attachment
	->xpath('//fnst:ИмяФайла')[0]
	->__toString();

Удобно, да не совсем. Если имя элемента на кириллице, то обратиться к нему через свойство не получиться, придётся использовать SimpleXMLElement::xpath(). С множественными значениями так же приходиться работать через SimpleXMLElement::xpath(). Кроме того SimpleXMLElement имеет свои особенности и некоторые вещи далеко не очевидны.

Converter

Есть способ проще. Достаточно XML-документ привести к массиву. В работе с массивами нет ни каких подводных камней. Массив из XML делается в пару строчек кода:

$xml=<<<XML
    <b attr4="55">
        <c>ccc
            <d/>
        </c>
        0000
    </b>           
XML;
$fabric = (new NavigatorFabric())->setXml($xml);
$converter = $fabric->makeConverter();
$arrayRepresentationOfXml = $converter->toArray();

Каждый XML-элемент будет представлен массивом, состоящим в свою очередь, из трёх других массивов.

Соответственно:

  • массив с индексом '*value' содержит значение элемента,

  • '*attributes' - атрибуты элемента,

  • '*elements' - вложенные элементы.

/*
'b' =>
  array (
	'*value' => '0000',
	'*attributes' =>
	array (
	  'attr4' => '55',
	),
	'*elements' =>
	array (
	  'c' =>
	  array (
	  ),
	),
  ),
*/

Если элемент множественный, то есть встречается в документе несколько раз подряд, то все его вхождения будут в массиве с индексом '*multiple'.

$xml=<<<XML	
<doc>
    <qwe>first occurrence</qwe>
    <qwe>second occurrence</qwe>
</doc>
XML;

/*
'doc' =>
array (
  'qwe' =>
  array (
	  '*multiple' =>
	  array (
  	  0 =>
	    array (
		  '*value' => 'first occurrence',
	    ),
	    1 =>
	    array (
		  '*value' => 'second occurrence',
	    )
	  )
  )
)
*/

Но и это ещё не всё.

XmlNavigator

Если от работы с XML-документов как с массивом, у вас в глазах рябит от квадратных скобочек, то XmlNavigator - это ваш вариант, создаётся так же в две строки кода.

/* документ */
$xml = <<<XML
<doc attrib="a" option="o" >666
    <base/>
    <valuable>element value</valuable>
    <complex>
        <a empty=""/>
        <b val="x"/>
        <b val="y"/>
        <b val="z"/>
        <c>0</c>
        <c v="o"/>
        <c/>
        <different/>
    </complex>
</doc>
XML;
$fabric = (new NavigatorFabric())->setXml($xml);
$navigator = $fabric->makeNavigator();

XmlNavigator делает, то же самое что и Converter, но предоставляет API, и с документом мы работаем как с объёктом.

Имя элемента, метод name()

/* Имя элемента */
echo $navigator->name();
/* doc */

Значение элемента, метод value()

/* Значение элемента */
echo $navigator->value();
/* 666 */

Список атрибутов, метод attribs()

/* get list of attributes */
echo var_export($navigator->attribs(), true);
/*
array (
  0 => 'attrib',
  1 => 'option',
)
*/

Значение атрибута, метод get()

/* get attribute value */
echo $navigator->get('attrib');
/* a */

Список вложенных элементов, метод elements()

/* Список вложенных элементов */
echo var_export($navigator->elements(), true);
/*
array (
  0 => 'base',
  1 => 'valuable',
  2 => 'complex',
)
*/

Получить вложенный элемент, метод pull()

/* Получить вложенный элемент */
$nested = $navigator->pull('complex');

echo $nested->name();
/* complex */

echo var_export($nested->elements(), true);
/*
array (
  0 => 'a',
  1 => 'different',
  2 => 'b',
  3 => 'c',
)
*/

Перебрать все вхождения множественного элемента, метод next()

/* Получить вложенный элемент вложенного элемента */        
$multiple = $navigator->pull('complex')->pull('b');

/* Перебрать все вхождения множественного элемента */
foreach ($multiple->next() as $index => $instance) {
    echo " {$instance->name()}[$index]" .
        " => {$instance->get('val')};";
}
/*
b[0] => x; b[1] => y; b[2] => z;
*/

Все методы класса XmlNavigator

Класс XmlNavigator реализует интерфейс IXmlNavigator.

<?php

namespace SbWereWolf\XmlNavigator;

interface IXmlNavigator
{
    public function name(): string;

    public function hasValue(): string;

    public function value(): string;

    public function hasAttribs(): bool;

    public function attribs(): array;

    public function get(string $name = null): string;

    public function hasElements(): bool;

    public function elements(): array;

    public function pull(string $name): IXmlNavigator;

    public function isMultiple(): bool;

    public function next();
}

Из названий методов очевидно их назначение. Не очевидные были рассмотрены выше.

Как установить?

composer require sbwerewolf/xml-navigator

Заключение

В работе приходиться использовать сначала SimpleXMLElement - с его помощью из всего документа получаем необходимый элемент, и уже с этим элементом работаем через XmlNavigator.

$document = new SimpleXMLElement($content);
$primary = $document
    ->Message
    ->ResponseContent
    ->content
    ->MessagePrimaryContent;
$attachment = $primary
    ->children(
        'urn://x-artefacts-fns-zpvipegr/root/750-08/4.0.1'
    )
    ->xpath('tns:Вложения')[0];

$fabric = (new NavigatorFabric())->setSimpleXmlElement($attachment);
$navigator = $fabric->makeNavigator();

Желаю вам приятного использования.

Эпилог

Конечно у вас могут быть свои альтернативы для работы с XML. Предлагаю поделиться в комментариях.

Конечно, не могу сказать, что XmlNavigator поможет с любым XML - не проверял, но с обычными документами, без хитростей в схеме документа, проблем не было.

Если вам важен порядок следования элементов, то придётся пользоваться XMLReader. Потому что SimpleXMLElement приводит документ к объекту, а у объекта нет такого понятия как порядок следования элементов.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Чем вы парсите XML?
10.2% XML, что это?5
32.65% XMLReader16
48.98% SimpleXMLElement24
8.16% Сторонней библиотекой (пожалуйста напишите название в комментариях)4
12.24% можно попробовать эту либу6
Проголосовали 49 пользователей. Воздержались 11 пользователей.
Теги:
Хабы:
Всего голосов 19: ↑7 и ↓12-1
Комментарии15

Публикации

Истории

Работа

PHP программист
94 вакансии

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань