Создание XML файлов из массивов на PHP

Владимир | | PHP, Web разработка.

php array xml

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

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

Постановка задачи

Есть PHP массив (может быть многомерным). Нужно получить xml строку с данными из этого массива.

Немного теории

В дистрибутив PHP входит модуль XMLWriter, который предназначен для записи данных в XML формате. Использовать непосредственно этот модуль в своих скриптах не очень удобно, т.к. он поддерживает только самые базовые функции и преобразование массива в этот формат «выливается» в несколько десятков строк кода.

Поэтому мы напишем собственный класс (Array2XML), который будет представлять собой оболочку для XMLWriter'а и решать одну узкую задачу – преобразовывать PHP массив в XML формат.

Прежде чем переходить к коду класса покажу, как им пользоваться.

require_once('Array2XML.php');

header('Content-type: application/xml');

$data = array(
	'val1' => 111,
	'val2' => '222',
	'val3' => 333,
	500,
	'container' => array(
		'mystr' => 'test test',
		'myobj' => array(
			'x' => 250,
			'y' => 150,
			'name' => 'objName'
		)
	)
);

$converter = new Array2XML();
$xmlStr = $converter->convert($data);

echo $xmlStr;

Как видите, после подключения файла с классом и установки заголовка (строки 1, 3), мы объявили массив с данными.

Обратите внимание. Не для всех элементов массива явно заданы имена ключей.

После этого, мы создаем объект типа Array2XML() и вызываем его метод convert. В качестве параметра этот метод получает наш массив. Как несложно догадаться, после его выполнения мы получим XML документ с данными из массива.

И сразу же покажу скриншот страницы с результатом работы скрипта.

xml data

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

Если массив многомерный, то его структура будет полностью повторяться в XML документе (вложенные массивы будут находиться внутри соответствующих тегов).

Единственное отличие в названиях касается элементов массива, для которых явно не заданы ключи. PHP присваивает им порядковые номера, но число нельзя использовать в качестве XML тега. Поэтому я добавил к его номеру приставку «key». Лучше, конечно, задавать ключи для всех элементов массива явно.

Теперь переходим непосредственно к классу

class Array2XML {
	
	private $writer;
	private $version = '1.0';
	private $encoding = 'UTF-8';
	private $rootName = 'root';
	

	function __construct() {
		$this->writer = new XMLWriter();
	}
	
	public function convert($data) {
		$this->writer->openMemory();
		$this->writer->startDocument($this->version, $this->encoding);
		$this->writer->startElement($this->rootName);
		if (is_array($data)) {
			$this->getXML($data);
		}
		$this->writer->endElement();
		return $this->writer->outputMemory();
	}
	public function setVersion($version) {
		$this->version = $version;
	}
	public function setEncoding($encoding) {
		$this->encoding = $encoding;
	}
	public function setRootName($rootName) {
		$this->rootName = $rootName;
	}
	private function getXML($data) {
		foreach ($data as $key => $val) {
			if (is_numeric($key)) {
				$key = 'key'.$key;
			}
			if (is_array($val)) {
				$this->writer->startElement($key);
				$this->getXML($val);
				$this->writer->endElement();
			}
			else {
				$this->writer->writeElement($key, $val);
			}
		}
	}
}
//end of Array2XML.php

Прежде всего, в конструкторе мы создаем объект типа XMLWriter. С помощью которого, будем выполнять всю рутинную работу.

Теперь, обратите внимание на метод convert (строки 14-23). В качестве единственного параметра он получает массив с данными.

Метод openMemory() начинает запись нового XML документа.

startDocument – вставляет заголовок.

startElement($this->rootName) – создает корневой тег. По-умолчанию, используется имя root, но его можно изменить.

После этого, мы проверяем, являются ли полученные данные массивом (строка 18) и вызываем метод getXML (строки 33-47), который и выполняет преобразование массива в XML формат.

Рассмотрим его подробнее. Алгоритм следующий:
1) обходим в цикле массив;
  1.1) если ключ текущего элемента является числом, добавляем к нему приставку «key»;
  1.2) если текущий элемент является массивом:
    1.2.1) создаем открывающий тег, в качестве названия используем ключа данного элемента (строка 39);
    1.2.2) вызываем метод getXML, а в качестве параметра передаем текущий элемент, т.е. вложенный массив (рекурсия);
    1.2.3)создаем закрывающий тег;
  1.3) создаем тег для текущего элемента массива (с помощью метода writeElement)

Важно. PHP позволяет задать максимальную глубину рекурсии. И если глубина вложенности массивов будет больше этого значения, то скрипт просто не будет работать.

Кроме того, класс содержит методы, которые позволяют указать версию XML, кодировку и имя корневого тега (setVersion, setEncoding, setRootName).

Как видите, класс получился довольно простой, но вполне работоспособный 🙂

Скачать

Вы можете скачать архив с этим примером.

P.S. Если у вас есть аналогичное решение буду рад обсудить 😉

Интересно почитать:

Оптимизация сайтов — единственный способ вырваться в ТОП поисковиков.
Хотите отдохнуть и расслабиться? Японский сад создаст нужное настроение.
Забудьте о CD болванках. DVD диски превосходят их по всем параметрам. dvd купить киев

  • Спасибо, странно, никогда раньше не обращал внимания на XMLWriter, все как то SimpleXML хватало. Пойду почитаю.

  • Спасибо, странно, никогда раньше не обращал внимания на XMLWriter, все как то SimpleXML хватало. Пойду почитаю.

  • Спасибо. Как раз ищу что-то подобное 🙂

  • Спасибо. Как раз ищу что-то подобное 🙂

  • nck

    На мой взгляд, лучшее решение — скрипт by Keith Devens. Называется это громко — XML Library.
    Работает быстро, исходник текущей версии — всего 117 строк (с комментариями и тегами PHP). Умеет как писать, так и читать (не DOM, и не SAX).
    Использовать так:



    Смотреть по адресу http://keithdevens.com/software/phpxml

    • Интересная библиотека, нужно будет попробовать.

    • nck

      Почему-то код в теге {code} не отобразился.
      Ещё раз:

      • Ваш код:

        <?php
        include('xml.php');
        $data = XML_unserialize($xml);
        $xml = XML_serialize($data);
        ?>
        В прошлый раз был:
        <?php
        include('xml.php');
        $data = XML_unserialize($xml);
        ?>
        <?php
        include('xml.php');
        $xml = XML_serialize($data);
        ?>

      • Действительно, проблема в тегах, точнее в их обработке движком WP.
        Проблема в том, что если автоматически преобразовывать угловые скобки в эскейп последовательности, то буду запрещены вообще все теги, а это меня не устраивает. Я перепробовал несколько плагинов с вроде бы подходящей функциональностью, но со всеми были проблемы. Поэтому на данный момент я просто конвертирую все теги в notepad++ 🙂

  • nck

    На мой взгляд, лучшее решение — скрипт by Keith Devens. Называется это громко — XML Library.
    Работает быстро, исходник текущей версии — всего 117 строк (с комментариями и тегами PHP). Умеет как писать, так и читать (не DOM, и не SAX).
    Использовать так:



    Смотреть по адресу http://keithdevens.com/software/phpxml

    • Интересная библиотека, нужно будет попробовать.

    • nck

      Почему-то код в теге {code} не отобразился.
      Ещё раз:

      • Ваш код:

        <?php
        include('xml.php');
        $data = XML_unserialize($xml);
        $xml = XML_serialize($data);
        ?>
        В прошлый раз был:
        <?php
        include('xml.php');
        $data = XML_unserialize($xml);
        ?>
        <?php
        include('xml.php');
        $xml = XML_serialize($data);
        ?>

      • Действительно, проблема в тегах, точнее в их обработке движком WP.
        Проблема в том, что если автоматически преобразовывать угловые скобки в эскейп последовательности, то буду запрещены вообще все теги, а это меня не устраивает. Я перепробовал несколько плагинов с вроде бы подходящей функциональностью, но со всеми были проблемы. Поэтому на данный момент я просто конвертирую все теги в notepad++ 🙂

  • Большое спасибо. Давно ломал голову над этим вопросом.

  • Большое спасибо. Давно ломал голову над этим вопросом.

  • Хехе, у меня тем же занимается самописная функция. Массив берется из БД.

    public static function array2xmlstring($array) {
    if (empty($array)) {
    return false;
    }
    $xmlstring = «»;
    foreach ($array as $article) {
    $xmlstring .= «»;
    foreach ($article as $col => $val) {
    $xmlstring .= «».$val.»»;
    }
    $xmlstring .= «»;
    }
    $xmlstring .= «»;
    return $xmlstring;
    }

    • Я так понял многомерные массивы не поддерживаются?

      • guest

        function utils_array_to_simple_xml($array, $check_plain = TRUE) {
        $xml = »;
        foreach ($array as $key => $val) {
        if ($key = str_replace(array(«t», «r», «n»), ' ', trim($key))) {
        $params = array_reverse($key = explode(' ', $key));
        array_pop($params);
        $params = trim(implode(' ', $params));
        $xml .=
        ».
        (is_array($val) ? call_user_func(__FUNCTION__, $val, $check_plain) : ($check_plain ? check_plain($val) : $val)).
        »;
        }
        }
        return $xml;
        }

  • Хехе, у меня тем же занимается самописная функция. Массив берется из БД.

    public static function array2xmlstring($array) {
    if (empty($array)) {
    return false;
    }
    $xmlstring = «»;
    foreach ($array as $article) {
    $xmlstring .= «»;
    foreach ($article as $col => $val) {
    $xmlstring .= «».$val.»»;
    }
    $xmlstring .= «»;
    }
    $xmlstring .= «»;
    return $xmlstring;
    }

    • Я так понял многомерные массивы не поддерживаются?

  • Vetroff

    Ребята! Вы — психи! Вы полные психи, черт возьми, что понимаете всю эту галиматью! Я вам искренне завидую. )

    • Почему это галиматью? 🙂
      Все нормально работает 🙂

      • Дмитрий

        Владимир, да это ж спамер!

        • Похоже, вы правы.
          Честно говоря, сейчас спамеры очень хорошо маскируются. Буквально на днях принял обычного комментатора за спамера. Потом пришлось извиняться.

  • Vetroff

    Ребята! Вы — психи! Вы полные психи, черт возьми, что понимаете всю эту галиматью! Я вам искренне завидую. )

    • Почему это галиматью? 🙂
      Все нормально работает 🙂

      • Дмитрий

        Владимир, да это ж спамер!

        • Похоже, вы правы.
          Честно говоря, сейчас спамеры очень хорошо маскируются. Буквально на днях принял обычного комментатора за спамера. Потом пришлось извиняться.

  • Владимир, у меня только 2х мерные массивы, т.к. мне надо таблицу из БД пересылать.

    Vetroff, это наша работа) Врачи вон по латыни пишут, тоже фиг кто поймет. Каждому свое)

    • Тогда все ясно. Действительно, использовать какие-то дополнительные библиотеки в такой ситуации просто нет смысла.

  • Владимир, у меня только 2х мерные массивы, т.к. мне надо таблицу из БД пересылать.

    Vetroff, это наша работа) Врачи вон по латыни пишут, тоже фиг кто поймет. Каждому свое)

    • Тогда все ясно. Действительно, использовать какие-то дополнительные библиотеки в такой ситуации просто нет смысла.

  • не лучшая идея фантастика
    . Потом разбирать это довольно гемморойно.
    А вот так на много практичнее фантастика
    .
    Но все равно спасибо автору (правда код я таки немного модифицировал)
    PS: убейте предыдущий коммент

  • мда.. а тег Code у тебя не работает

    • Почему не работает? У меня в стилях указано, что текст внутри <code> должен быть зеленым 😉

  • не лучшая идея фантастика
    . Потом разбирать это довольно гемморойно.
    А вот так на много практичнее фантастика
    .
    Но все равно спасибо автору (правда код я таки немного модифицировал)
    PS: убейте предыдущий коммент

  • мда.. а тег Code у тебя не работает

    • Почему не работает? У меня в стилях указано, что текст внутри <code> должен быть зеленым 😉

  • Он должен быть красным потому что ссылается на другой протокол.

  • Он должен быть красным потому что ссылается на другой протокол.

  • Shurik

    Ребята, помогите мне, точнее поставьте на правильный путь! Хочу сделать карту сайта (sitemap.xml) Но черт возьми не могу работать с xml вообще. С чего мне лучше всего начать?

    • Есть разные варианты:

      1) использовать готовое решение, например, вроде плагина к WP Google XML Sitemaps

      2) есть online сервис

      3) разобраться с какой-нибудь библиотекой для работы с XML и заодно с форматом карты сайта 🙂

  • Shurik

    Ребята, помогите мне, точнее поставьте на правильный путь! Хочу сделать карту сайта (sitemap.xml) Но черт возьми не могу работать с xml вообще. С чего мне лучше всего начать?

    • Есть разные варианты:

      1) использовать готовое решение, например, вроде плагина к WP Google XML Sitemaps

      2) есть online сервис

      3) разобраться с какой-нибудь библиотекой для работы с XML и заодно с форматом карты сайта 🙂

  • KP-0B0-C0C

    Очень полезная и написанная максимально доступным языком статья! Мне помогла! Большое спасибо! =)

  • singer

    Не самый удачный вариант, имхо — использование array_key_exists() в цилке + не поддерживается с 2005 года. На мой взгялд пример в этой статье — лучше.

  • Я вот заметил странную особенность XmlWriter.
    если ему во writeAttribute(«ЗависимОтРегистра», «Тут значение»);
    то получим

    как от этого можно избавится… ???

    • прошу прощения. Это меня Google chrome ввел в заблуждения. В итоговом файле все нормально регистром атрибутов.
      А при просмотре структуры через Chrome все были приведены к lowercase

  • Спасибо 🙂

  • Zadvinski

    Скрипт конечно замечательный, потому что простой и функциональный.

    Есть маленький момент с кодировкой — если кому-то позарез нужно windows-1251 вместо UTF-8, то скорее всего будет ругаться ошибкой. 
    Но есть выход — поменять в классе одну единственную 16-ю строчку и написать:
    $this->writer->startDocument($this->version.'» encoding=»'.$this->encoding);

    То есть не устанавливаем кодировку, а в строке версии пишем и версию и кодировку..  вроде как пашетИдея почерпнута отсюда — http://www.php.net/manual/en/function.xmlwriter-start-document.php#89957

    • tnt

      сработало! спасибо за это важное уточнение, я то я тут как раз над этим голову ломал —  с какого фига он на русский ругался при 1251

  • Гость

    Вот ещё один врайтер…
    http://istem.ru/open/xmlgen.php

  • i_search_for_you

    Спасибо!
    Я добавил поддержку записи атрибутов, через спец массив attributes :

    /*
    * Этот метод преобразует данные массива в XML строку.
    * Если массив многомерный, то метод вызывается рекурсивно.
    */
    private function getXML($data) {
    foreach ($data as $key => $val) {
    if (is_numeric($key)) {
    $key = 'key'.$key;
    }

    if($key=='attributes') continue;

    if (is_array($val)) {
    $currentElem = $this->writer->startElement($key);
    if(isset($val['attributes'])){
    foreach($val['attributes'] as $attrName=>$attrVal){
    $this->writer->startAttribute( $attrName );
    $this->writer->text( $attrVal );
    $this->writer->endAttribute();
    }
    }
    $this->getXML($val);
    $this->writer->endElement();
    }
    else {
    $this->writer->writeElement($key, $val);
    }
    }
    }

    пользоваться так:

    'container' => array(
    'mystr' => 'test test',
    'attributes' => array('url'=>'http://test.com', 'attr1'=>'attr1name'),
    )

    • Быколай

      Молодец. После разборок с SimpleXml и Dom, тоже решил использовать это решение. Под себя допилил в итоге метод к такому виду:

      private function getXML($data) {
      foreach ($data as $key => $val) {
      if (is_int($key)) {
      $key = 'element';
      }
      if($key == '@' && is_array($val)){
      foreach ($val as $name => $value){
      var_dump($name);
      $this->writer->writeAttribute($name, $value);
      }
      }elseif($key[0] == '@'){
      $name = str_replace('@', null, $key);
      $this->writer->writeAttribute($name, $val);
      }elseif (is_array($val)) {
      $this->writer->startElement($key);
      $this->getXML($val);
      $this->writer->endElement();
      }else {
      $this->writer->writeElement($key, $val);
      }
      }
      }

      Позволяет указывать массив атрибутов (ключ @) или прямо @имя_атрибута => значение (если сверху массива).

  • Kirill Pupkin

    зачем часть после кода вставлена в код с подсветкой, не удобно читать html-теги

  • Евгений

    спасибо огромное, очень помогло

  • Vlad

    подскажите что не так делаю?

    writer = new XMLWriter();
    }

    public function convert($res) {
    $this->writer->openMemory();
    $this->writer->startDocument($this->version, $this->encoding);
    $this->writer->startElement($this->rootName);
    if (is_array($res)) {
    $this->getXML($res);
    }
    $this->writer->endElement();
    return $this->writer->outputMemory();
    }
    public function setVersion($version) {
    $this->version = $version;
    }
    public function setEncoding($encoding) {
    $this->encoding = $encoding;
    }
    public function setRootName($rootName) {
    $this->rootName = $rootName;
    }
    private function getXML($res) {
    foreach ($res as $key => $val) {
    if (is_numeric($key)) {
    $key = 'key'.$key;
    }
    if (is_array($val)) {
    $this->writer->startElement($key);
    $this->getXML($val);
    $this->writer->endElement();
    }
    else {
    $this->writer->writeElement($key, $val);
    }
    }
    }
    }
    //end of Array2XML.php
    ?>

    файл class.php

    convert($res);

    echo $xmlStr;
    ?>

    получается так:

    Ошибка синтаксического анализа XML: элемент не найден
    Адрес: http://localhost/class.php
    Строка 2, символ 5:
    —-^

    • Vlad

      require_once(«connection.php»);
      require_once('Array2XML.php');
      header('Content-type: application/xml');
      $ath = mysql_query(«select * from students;»);
      $res=mysql_fetch_array($ath);

      $converter = new Array2XML();
      $xmlStr = $converter->convert($res);

      echo $xmlStr;

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

        • Vlad

          хм… не нашел адрес Ваш…. куда прислать?

        • Vlad

          получилось из под windows (denwer)…. но выводит только первую строку из массива…. и все данные дублируются в тегах key…. как можно переписать метод? как не переделываю пишет ошибку почему то

        • Vlad
        • vladimirsta[at]yandex.ru
          или загрузите на любой файлообменник и присылайте ссылку

  • Stanislav

    Ага, а как сделать ,например структуру. где в элемент вложено несколько других элементов с одинаковым именем?

    • Преобразовать PHP массив в такую структуру как показано в этой статье не получится, т.к. названия ключей не должны совпадать. Но задача решается. Например, можно использовать следующий массив:

      $data = array(
      array('tagName'=>'node', 'tagValue'=>'...'),
      array('tagName'=>'node', 'tagValue'=>'...'),
      array('tagName'=>'node', 'tagValue'=>'...'),
      );

      В парсере нужно вместо ключа элемента использовать значение tagName.
      А вообще решение нужно выбирать исходя из формата ваших данных.

  • Агент Капля

    как реализовать что бы он красиво код xml раставил а то получается в се в строку.

    • Вы хотите в xml вставить пробелы и переносы строк? А зачем? Ведь xml можно открыть специализированным редактором (или браузером) и получите красивое форматирование. Кстати, скриншот в статье сделан в браузере.

      • Агент Капля

        разобрался сам!! спасибо за класс!!!

        $dom->formatOutput = true;

        $file=»file.xml»;

        $fp = fopen($file, «w»);

        fwrite($fp, $dom->saveXML());

        fclose ($fp);

        Да еще xml уже не в моде, все чаще используют для конфигов и локализации JsonDB работает анологично Mysql http://habrahabr.ru/post/119719/

  • mbhjkn,

    mjnk,jnk,n

  • Тимур Серебрянский

    Здравствуйте. А как добавить атрибуты для корневого тега?