Организация общения xml-сообщениями на PHP

Возникла в ходе работы такая несложная задача  - «организация общения xml-сообщениями».

SOAP слишком затратно. Для чтения данных из xml-сообщения сначала было решено конвертировать xml-файл в многомерный массив PHP и последующей передачей данных по ключам в этом массиве. Для обратного процесса, соответственно, генерация правильно сформированного многомерного массива и перегон в xml-строку. Среди явных плюсов данного подхода имеется быстрота и миллионы «велосипедов» для конвертации. Среди минусов – чрезмерное количество проверок на существование значения для нужной переменной в этой структуре при чтении xml-сообщения и неповоротливые куски кода по компоновке соответствующего многомерного массива при генерации xml-сообщения.

Собственно, наплевав на выбор между хреном и редькой, так и было сделано. Потом, спустя время, что-то подсказывало, что есть более изящное решение. Память и мануалы подсказали, что можно использовать для этих целей DOMDocument и DOMXPath.

Итак. Две простейшая задачи: получить из XML-документа данные и сформировать из данных xml-документ.

Есть несложная структура данных

root/param1 => value1
root/param2 => value2
root/list_params/param1 => value3

root/list_params/paramN => valueN

нужно получить

<root>
  <param1>value1</param1>
  <param2>value2</param2>
  <list_params>
    <param1>value1</param1>
    <param2>value2</param2>
    ...
    <paramN>valueN</paramN>
  </list_params>
</root>

для этого используем следующую функцию

function dom_from_xpath($queries, $_o = array(), $dom = null)
{
  $pattern = '/^\/\/[a-zA-Z_]+(\/[a-zA-Z_]+)*$/';
  if(!$queries || !is_array($queries) || !count($queries))
  {
    return null;
  }
  $_v = isset($_o['version']) ? $_o['version'] : '1.0';
  $_e = isset($_o['encoding']) ? $_o['encoding'] : 'utf-8';
  $_fo = isset($_o['format_output']) ? $_o['format_output'] : true;
  $_ad = isset($_o['allow_duplicate']) ? $_o['allow_duplicate'] : false;
  $_s = isset($_o['strict']) ? $_o['strict'] : true;
  $empty_dom = new DOMDocument($_v, $_e);
  $dom = $dom ? clone $dom : clone $empty_dom;
  $dom->formatOutput = $_fo;
  foreach ($queries as $var => $value)
  {
    if(!preg_match($pattern, $var))
    {
      if($_s)
      {
        return $dom;
      }
      else
      {
        continue;
      }
    }
    $chunks = explode('/', trim($var, '/'));
    $i = $count = count($chunks);
    $expression = '/';
    $current = $dom;
    $xpath = new DOMXpath($dom);
    do
    {
      $expression .= '/' . $chunks[$count - $i];
      if($empty_dom->saveXML() == $dom->saveXML())
      {
        $element = $dom->createElement($chunks[$count - $i]);
        $element = $current->appendChild($element);
        $current = $element;
      }
      $list = $xpath->query($expression);
      if (!$list->item(0))
      {
        $element = $dom->createElement($chunks[$count - $i]);
        $element = $current->appendChild($element);
        $current = $element;
      }
      else
      {
        $current = $list->item(0);
      }
      $xpath = new DOMXpath($dom);
    }
    while(--$i);
    if($_ad && $current->nodeValue)
    {
      $current = $current->parentNode;
      $element = $dom->createElement($chunks[$count - 1]);
      $element = $current->appendChild($element);
      $current = $element;
    }
    else
    {
      $current->nodeValue = '';
    }
    $current = $current->appendChild($dom->createTextNode($value));
  }

  return $dom;
}

Извините за частичную обфускацию кода, шаблон WordPress тому виной :)

Возможности:

Пример N 1

$xpath_queries = array('//root/first_child' => 'first_child_value');
$dom = dom_from_xpath($xpath_queries);
$xml = $dom->saveXML();
print_r($xml);

Результат N 1

<root>
  <first_child>first_child_value</first_child>
</root>

Пример N 2

$xpath_queries = array('//root/first_child' => 'first_child_value');
$dom = dom_from_xpath($xpath_queries);
$xpath_queries = array('//root/second_child' => 'second_child_value');
$dom = dom_from_xpath($xpath_queries, array(), $dom);
$xml = $dom->saveXML();
print_r($xml);

Результат N 2

<root>
  <first_child>first_child_value</first_child>
  <second_child>second_child_value</second_child>
</root>

Пример N 3

$xpath_queries = array('//root/first_child' => 'first_child_value');
$dom = dom_from_xpath($xpath_queries);
$xpath_queries = array('//root/first_child' => 'modified_first_child_value');
$dom = dom_from_xpath($xpath_queries, array(), $dom);
$xml = $dom->saveXML();
print_r($xml);

Результат N 3

<root>
  <first_child>modified_first_child_value</first_child>
</root>

Пример N 4

$xpath_queries = array('//root/first_child' => 'first_child_value');
$dom = dom_from_xpath($xpath_queries);
$xpath_queries = array('//root/first_child' => 'modified_first_child_value');
$dom = dom_from_xpath($xpath_queries, array('allow_duplicate' => true), $dom);
$xml = $dom->saveXML();
print_r($xml);

Результат N 4

<root>
  <first_child>first_child_value</first_child>
  <first_child>modified_first_child_value</first_child>
</root>

Преобразование строки в xml документ гораздо проще. Для этого используем следующую функцию

function dom_from_source($source, $_o = array())
{
  $_v = isset($_o['version']) ? $_o['version'] : '1.0';
  $_e = isset($_o['encoding']) ? $_o['encoding'] : 'utf-8';
  $_fo = (bool) (isset($_o['format_output']) ? $_o['format_output'] : 1);
  $dom = new DOMDocument($_v, $_e);
  $dom->formatOutput = $_fo;
  $dom->loadXML($source);

  return $dom;
}

Ниже идет небольшая библиотека для проверки и получения элементов xml-документа.

/**
 * Проверка на существование элемента, соответствующего Xpath запросу
 * и, опционально, равного значению параметра $value
 * @param DOMXPath $xpath
 * @param String $query
 * @param String $value
 * @return boolean
 */
function xpath_has_node($xpath, $query, $value = NULL)
{
  $result = $xpath->query($query)->item(0);
  return (bool) ($value === NULL ? $result : $result->nodeValue == $value);
}

/**
 * Проверка на существование элементов, соответствующих набору Xpath запросов.
 * @param DOMXPath $xpath
 * @param Array $queries
 * @return NULL|boolean
 */
function xpath_has_nodes($xpath, $queries)
{
  if(!is_array($queries) || !count($queries))
  {
    return null;
  }
  foreach ($queries as $query)
  {
    if(!xpath_has_node($xpath, $query))
    {
      return false;
    }
  }
  return true;
}

/**
 * Проверка на существование элемента, соответствующего Xpath запросу
 * @param DOMDocument $dom
 * @param String $query
 * @return boolean
 */
function dom_has_node($dom, $query)
{
  $xpath = new DOMXPath($dom);
  return xpath_has_node($xpath, $query);
}

/**
 * Проверка на существование элементов, соответствующих набору Xpath запросов.
 * @param DOMDocument $dom
 * @param Array $queries
 * @return Boolean
 */
function dom_has_nodes($dom, $queries)
{
  $xpath = new DOMXPath($dom);
  return xpath_has_nodes($xpath, $queries);
}

/**
 * Получение набора элементов по набору XPath запросов.
 * @param DOMXPath $xpath
 * @param Array $queries
 * @param Boolean $is_multiple
 * @return Array
 */
function xpath_extract_nodes($xpath, $queries, $is_multiple = false)
{
  $result = array();
  foreach ($queries as $query)
  {
    $query_result = xpath_extract_node($xpath, $query, $is_multiple);
    if($query_result === NULL)
    {
      continue;
    }
    $result[$query] = $query_result;
  }

  return $result;
}

/**
 * Получение элемента по XPath запросу.
 * Возвращает DOMNodeList или DOMNode в зависимости от параметра $is_multiple
 * @param DOMXpath $xpath
 * @param String $query
 * @param Boolean $is_multiple
 * @return mixed
 */

function xpath_extract_node($xpath, $query, $is_multiple = false)
{
  $node_list = $xpath->query($query);
  return $is_multiple ? $node_list : $node_list->item(0);
}

Возможности:

Пример

$source = '
<root>
<param1>value 1</param1>
  <param2>value 2</param2>
  <list_params>
    <param1>list value 1</param1>
    <param2>list value 2</param2>
    <paramN>list value N</paramN>
  </list_params>
</root>';
$dom = dom_from_source($source);
$xpath = new DOMXPath($dom);
$xpath_root = '//root';
$xpath_param1 = $xpath_root . '/param1';
$xpath_param2 = $xpath_root . '/param2';
$xpath_list_params = $xpath_root . '/list_params';
$xpath_queries = array
(
  $xpath_param1,
  $xpath_param2,
  $xpath_list_params,
);
if(xpath_has_nodes($xpath, $xpath_queries))
{
  $params = array();
  foreach (xpath_extract_node($xpath, $xpath_list_params . '/*', true) as $node)
  {
    $params[$node->tagName] = $node->nodeValue;
  }
  print_r($params);
}

Результат
Проверка наличия трех элементов с путями //root/param1, //root/param2 и //root/list_params и конвертация поддерева //root/list_params в ассоциативный массив.
В стандартный вывод мы получим строку

Array ( [param1] => list value 1 [param2] => list value 2 [paramN] => list value N )

This entry was posted in Программирование and tagged , . Bookmark the permalink.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

*


Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>