Справочники, инструменты, документация

PHP: Работа с XML файлами SimpleXMLElement

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

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

Пусть у вас есть XML. Он может быть в строке, либо хранится в файле или отдаваться по запросу к определенному URL. Пусть XML хранится в строке. В этом случае из этой строки нужно создать объект с помощью new SimpleXMLElement:

$str = "<?xml version='1.0'?>
<worker>
    <name>Коля</name>
    <age>25</age>
    <salary>1000</salary>
</worker>";

$xml = new SimpleXMLElement($str);

Сейчас у нас в переменной $xml хранится объект с разобранным XML. Обращаясь к свойствам этого объекта можно получать доступ с содержимому тегов XML. Как именно - разберем чуть ниже. Если же XML хранится в файле или отдается по обращению к URL (что чаще всего и бывает), то следует использовать функцию simplexml_load_file, которая делает тот же объект $xml.

<?xml version='1.0'?>
<worker>
    <name>Коля</name>
    <age>25</age>
    <salary>1000</salary>
</worker>
$xml = simplexml_load_file(путь к файлу или урл);

В примерах ниже наш XML хранится в файле или по URL. Пусть дан следующий XML.

<?xml version='1.0'?>
<worker>
    <name>Коля</name>
    <age>25</age>
    <salary>1000</salary>
</worker>

Давайте получим имя, возраст и зарплату работника:

$xml = simplexml_load_file(путь к файлу или урл);
echo $xml->name; // выведет 'Коля'
echo $xml->age; // выведет 25
echo $xml->salary; // выведет 1000

Как вы видите, у объекта $xml есть свойства, соответствующие тегам. Вы может обратили внимание, что тег <worker> нигде не фигурирует при обращении. Это потому, что он корневой тег. Можно переименовать его, например, на <root> - и ничего не поменяется.

Корневой тег в XML может быть только один, так же, как и тег <html> в обычном HTML. Давайте чуть модифицируем наш XML.

<?xml version='1.0'?>
<root>
    <worker>
        <name>Коля</name>
        <age>25</age>
        <salary>1000</salary>
    </worker>
</root>

// В этом случае у нас получится цепочка обращений

$xml = simplexml_load_file(путь к файлу или урл);
echo $xml->worker->name; // выведет 'Коля'
echo $xml->worker->age; // выведет 25
echo $xml->worker->salary; // выведет 1000

Пусть некоторые данные хранятся в атрибутах.

<?xml version='1.0'?>
<root>
    <worker name="Коля" age="25" salary="1000">Номер 1</worker>
</root>
$xml = simplexml_load_file(путь к файлу или урл);
echo $xml->worker['name']; // выведет 'Коля'
echo $xml->worker['age']; // выведет 25
echo $xml->worker['salary']; //выведет 1000
echo $xml->worker; //выведет 'Номер 1'

Теги с дефисами. В XML разрешены теги (и атрибуты) с дефисом. В этом случае обращение к таким тегам происходит так:

<?xml version='1.0'?>
<root>
    <worker>
        <first-name>Коля</first-name>
        <last-name>Иванов</last-name>
    </worker>
</root>
$xml = simplexml_load_file(путь к файлу или урл);
echo $xml->worker->{first-name}; // выведет 'Коля'
echo $xml->worker->{last-name}; // выведет 'Иванов'

Перебор циклом. Пусть теперь у нас не один работник, а несколько. В этом случае мы можем перебрать наш объект с помощью цикла foreach.

<?xml version='1.0'?>
<workers>
    <worker>
        <name>Коля</name>
        <age>25</age>
        <salary>1000</salary>
    </worker>
    <worker>
        <name>Вася</name>
        <age>26</age>
        <salary>2000</salary>
    </worker>
    <worker>
        <name>Петя</name>
        <age>27</age>
        <salary>3000</salary>
    </worker>
</workers>
$xml = simplexml_load_file(путь к файлу или урл);

foreach ($xml as $worker) {
    echo $worker->name; // выведет 'Коля', 'Вася', 'Петя'
}

Из объекта в нормальный массив. Если вам неудобно работать с объектом, вы можете преобразовать его в нормальный массив PHP с помощью следующего хитрого приема.<

$xml = simplexml_load_file(путь к файлу или урл);
var_dump(json_decode(json_encode($xml), true));

SimpleXML

Расширение SimpleXML предоставляет очень простой и легкий в использовании набор инструментов для преобразования XML в объект, с которым можно затем работать через его свойства и с помощью итераторов. SimpleXML присутствует в PHP начиная с версии 5.

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

<?xml version="1.0" encoding="UTF-8"?>
<recipe name="хлеб" preptime="5" cooktime="180">
 <title>Простой хлеб</title>
 <ingredient amount="3" unit="стакан">Мука</ingredient>
 <ingredient amount="0.25" unit="грамм">Дрожжи</ingredient>
 <ingredient amount="1.5" unit="стакан">Тёплая вода</ingredient>
 <ingredient amount="1" unit="чайная ложка">Соль</ingredient>
 <instructions>
 <step>Смешать все ингредиенты и тщательно замесить.</step>
 <step>Закрыть тканью и оставить на один час в тёплом помещении.</step>
 <step>Замесить ещё раз, положить на противень и поставить в духовку.</step>
 </instructions>
</recipe>

Загрузка XML

Прежде чем начать обрабатывать данные, их нужно сначала загрузить. Для этого достаточно использовать функцию simplexml_load_file(). Она принимает имя файла, и возвращает объект типа SimpleXMLElement. И с этим объектом уже можно будет работать.

$xml = simplexml_load_file('recipe.xml');
print_r($xml);
SimpleXMLElement Object
(
 [@attributes] => Array
  (
   [name] => хлеб
   [preptime] => 5
   [cooktime] => 180
  )
 [title] => Простой хлеб
 [ingredient] => Array
  (
   [0] => Мука
   [1] => Дрожжи
   [2] => Тёплая вода
   [3] => Соль
  )
 [instructions] => SimpleXMLElement Object
  (
   [step] => Array
    (
     [0] => Смешать все ингредиенты и тщательно замесить.
     [1] => Закрыть тканью и оставить на один час в тёплом помещении.
     [2] => Замесить ещё раз, положить на противень и поставить в духовку.
    )
  )
)

Кроме того, существует еще и функция simplexml_load_string(), которая берет XML не из файла, а из строки.

$str = file_get_contents('recipe.xml');
$xml = simplexml_load_string($str);

Получение данных

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

echo $xml->title; // выводит содержимое элемента <title>

Поскольку ингредиентов у нас несколько, то $xml->ingredient будет массивом из четырех элементов. Перебрать все ингредиенты можно так:

foreach ( $xml->ingredient as $ingredient ) {
 echo $ingredient . '<br/>';
}

Для того что бы получить, к примеру, третий ингредиент (теплая вода), достаточно обратиться к нему по индексу:

$xml->ingredient[2]; // элементы массивы нумеруются с нуля

Шаги приготовления step являются дочерними для элемента instructions, чтобы получить их, нужно сначала получить instructions:

echo $xml->instructions->step; // выводит текст первого шага

Атрибуты

Работать с атрибутами тоже очень легко. Они доступны как ассоциативный массив своего элемента. То есть, для того что бы получить название рецепта (атрибут name корневого узла recipe), достаточно написать:

echo $xml['name'];

Или, для получения количества первого ингредиента можно написать так:

echo $xml->ingredient['amount'];

Сейчас мы рассмотрели только один способ получения данных: когда нам уже известны названия узлов и атрибутов. Но случается и так, когда структура XML файла заранее не известна, но нам нужно его обработать. SimpleXML предоставляет и такую возможность.

Получение дочерних узлов

Метод children() возвращает список дочерних элементов. В нашем случае $xml — корневая ветвь, и если написать:

$nodes = $xml->children();
echo $nodes[0];

то получим содержимое элемента <title>, а если:

$nodes = $xml->children();
echo $nodes[2];

то второй ингредиент.

Обойти все дочерние ветви первого уровня легко можно при помощи цикла foreach:

echo '<ul>';
foreach ( $xml->children() as $node ) {
 if ( count($node) == 0 ) echo '<li>' . $node . '</li>';
}
echo '</ul>';

Результат:

<ul>
 <li>Простой хлеб</li>
 <li>Мука</li>
 <li>Дрожжи</li>
 <li>Тёплая вода</li>
 <li>Соль</li>
</ul>

Фукция count() позволяет определить количество дочерних узлов.

Для того, чтобы получить имя текущий ветви, используется метод getName():

$nodes = $xml->children();
echo $nodes[0]->getName(); // выведет title

Получение атрибутов

Получить список атрибутов для текущего элемента поможет метод attributes(). По функционалу и механизму работы он аналогичен методу children(), за тем исключением, что здесь идет работа с атрибутами.

$nodes = $xml->children();
// все атрибуты узла <ingredient>Мука</ingredient>
foreach ( $nodes[1]->attributes() as $name => $value ) {
 echo 'атрибут ' . $name . ', значение ' . $value . '<br/>';
}

Результат:

атрибут amount, значение 3<br/>
атрибут unit, значение стакан<br/>

Изменение значений узлов и атрибутов

Объект SimpleXMLElement позволяет манипулировать всеми элементами:

$xml = simplexml_load_file('recipe.xml');
$xml->title = 'Ржаной хлеб';
$xml->ingredient[0] = 'Ржаная мука';
print_r($xml);
$xml->ingredient[2]['amount'] = '300';
$xml->ingredient[2]['unit'] = 'грамм';
print_r($xml->ingredient[2]);

Результат:

SimpleXMLElement Object
(
 [@attributes] => Array
  (
   [name] => хлеб
   [preptime] => 5
   [cooktime] => 180
  )
 [title] => Ржаной хлеб
 [ingredient] => Array
  (
   [0] => Ржаная мука
   [1] => Дрожжи
   [2] => Тёплая вода
   [3] => Соль
  )
 [instructions] => SimpleXMLElement Object
  (
   [step] => Array
    (
     [0] => Смешать все ингредиенты и тщательно замесить.
     [1] => Закрыть тканью и оставить на один час в тёплом помещении.
     [2] => Замесить ещё раз, положить на противень и поставить в духовку.
    )
  )
)
SimpleXMLElement Object
(
 [@attributes] => Array
  (
   [amount] => 300
   [unit] => грамм
  )

 [0] => Тёплая вода
)

Добавление элементов и атрибутов

Чтобы добавить дочерний элемент к текущему, достаточно использовать метод addChild(). Первым параметром идет имя нового элемента, вторым значение, которое задавать необязательно.

Добавим еще один шаг к инструкциям:

$node = $xml->instructions; // получаем ветвь инструкций
$node->addChild('step', 'Почитать газету'); // добавляем элемент
print_r($node);

Результат:

SimpleXMLElement Object
(
 [step] => Array
  (
   [0] => Смешать все ингредиенты и тщательно замесить.
   [1] => Закрыть тканью и оставить на один час в тёплом помещении.
   [2] => Замесить ещё раз, положить на противень и поставить в духовку.
   [3] => Почитать газету
  )

)

Метод addAttribute() позволяет добавить атрибут к текущему узлу. Первый параметр это имя атрибута, второй значение.

$node = $xml->instructions; // получаем ветвь инструкций
$step = $node->addChild('step', 'Почитать газету'); // добавляем элемент
$step->addAttribute('newspaper', 'Аргументы и факты'); // добавляем артибут
print_r($step);

Результат:

SimpleXMLElement Object
(
 [@attributes] => Array
  (
   [newspaper] => Аргументы и факты
  )

 [0] => Почитать газету
)

Использование XPath

SimpleXML включает в себя встроенную поддержку XPath. Поиск всех элементов <step>:

foreach ($xml->xpath('//step') as $step) {
 echo $step . '<br/>';
}

Результат:

Смешать все ингредиенты и тщательно замесить.<br/>
Закрыть тканью и оставить на один час в тёплом помещении.<br/>
Замесить ещё раз, положить на противень и поставить в духовку.<br/>

Взаимодействие с DOM

PHP может преобразовывать XML узлы из SimpleXML в формат DOM и наоборот. Этот пример показывает, как можно изменить DOM элемент в SimpleXML:

$dom = new DOMDocument('1.0', 'utf-8');
$dom->load('recipe.xml');
$xml = simplexml_import_dom($dom);
На этой странице