Как написать CDATA с помощью SimpleXmlElement?

У меня есть этот код для создания и обновления файла xml:

<?php
$xmlFile    = 'config.xml';
$xml        = new SimpleXmlElement('<site/>');
$xml->title = 'Site Title';
$xml->title->addAttribute('lang', 'en');
$xml->saveXML($xmlFile);
?>

Это создает следующий xml-файл:

<?xml version="1.0"?>
<site>
  <title lang="en">Site Title</title>
</site>

Вопрос в следующем: есть ли способ добавить CDATA с помощью этого метода/техники для создания кода xml ниже?

<?xml version="1.0"?>
<site>
  <title lang="en"><![CDATA[Site Title]]></title>
</site>

person quantme    schedule 07.06.2011    source источник
comment
Не похоже, что SimpleXML поддерживает создание узлов CDATA. Вместо этого попробуйте использовать DOM.   -  person Phil    schedule 07.06.2011
comment
Почему тебе не все равно? <title lang="en">Site Title</title> и <title lang="en"><![CDATA[Site Title]]></title> идентичны, за исключением того, что один использует больше байтов и его труднее читать человеку.   -  person Quentin    schedule 05.12.2014
comment
@Квентин Хороший вопрос. Просто требование клиента.   -  person quantme    schedule 12.03.2015
comment
@Quentin - использование CDATA упрощает запись, потому что вам не нужно беспокоиться о том, чтобы что-то экранировать / сделать его строгим XML внутри данных. Например, если вы напишете <title lang="en">Site<br>Title</title>, это нарушит синтаксический анализатор XML (открытие тега br без закрытия не является строгим XML), а <title lang="en"><![CDATA[Site<br>Title]]></title> — нет. Таким образом, при работе с клиентами часто более читабельнее просто иметь CDATA, а не все неуклюжее экранирование, которое может потребоваться узлу, отличному от CDATA, чтобы избежать CDATA.   -  person Jimbo Jonny    schedule 17.11.2015
comment
@JimboJonny — Это нормально, если вы пишете это вручную, но вопрос в том, чтобы сгенерировать его из PHP.   -  person Quentin    schedule 17.11.2015
comment
@ Квентин - я не согласен. Буквальный характер CDATA делает его еще более полезным, так как не нужно избегать/использовать логику для удаления вещей, которые могут нарушить динамически создаваемый XML-контент. Это то же самое, что сказать Просто интерпретируйте это как буквальную строку, а не как часть XML-разметки, что чрезвычайно полезно В ЛЮБОЙ раз, когда вы точно не знаете, какой контент окажется внутри узла, независимо от того, пишется ли он вручную или заполняется кодом, например, через CMS. Могут быть и другие способы избежать данных, чтобы заставить их работать, когда это делает машина, но CDATA является таким же жизнеспособным методом.   -  person Jimbo Jonny    schedule 17.11.2015
comment
@Quentin - И CDATA часто оказывается более читаемым и использует меньше байтов (противоположность вашим двум жалобам). Например, что более читабельно: теги CDATA в начале и в конце или куча экранированного контента повсюду? Что больше байтов данных: замена каждого возможного оскорбительного символа html-объектами или наличие 12 дополнительных байтов данных? Даже один экранированный тег <em></em> в содержимом добавит столько же байтов, сколько и окружающие теги CDATA. Видите ли, есть МНОГИЕ случаи, когда CDATA является жизнеспособным решением, независимо от того, заполняется ли XML вручную или кодом.   -  person Jimbo Jonny    schedule 17.11.2015
comment
независимо от того, пишется ли он вручную или заполняется кодом, например, через CMS — если он заполняется кодом, как в вопросе, то библиотека позаботится об экранировании или преобразовании в CDATA, это дело библиотеки, а не автора.   -  person Quentin    schedule 17.11.2015
comment
@Quentin - Автор CMS совершенно законно решает, что они хотят, чтобы их данные хранились в форме, которая одновременно более удобочитаема для человека и во многих случаях меньше. CDATA является законной и даже выгодной формой XML, и автор CMS имеет все законные права определять, как они хотят хранить свои данные, независимо от того, является ли это выходом SimpleXML по умолчанию. Представление о том, что никто никогда не должен делать ничего, кроме поведения класса/метода по умолчанию, потому что об этом должна беспокоиться библиотека, явно абсурдно.   -  person Jimbo Jonny    schedule 27.11.2015


Ответы (4)


Понятно! Я адаптировал код из этого отличного решения (архивная версия):

<?php
// http://coffeerings.posterous.com/php-simplexml-and-cdata
class SimpleXMLExtended extends SimpleXMLElement {
  public function addCData($cdata_text) {
    $node = dom_import_simplexml($this); 
    $no   = $node->ownerDocument; 
    $node->appendChild($no->createCDATASection($cdata_text)); 
  } 
}

$xmlFile    = 'config.xml';
// instead of $xml = new SimpleXMLElement('<site/>');
$xml        = new SimpleXMLExtended('<site/>');
$xml->title = NULL; // VERY IMPORTANT! We need a node where to append
$xml->title->addCData('Site Title');
$xml->title->addAttribute('lang', 'en');
$xml->saveXML($xmlFile);
?>

Сгенерированный XML-файл:

<?xml version="1.0"?>
<site>
  <title lang="en"><![CDATA[Site Title]]></title>
</site>

Спасибо, Петах

person quantme    schedule 07.06.2011
comment
public function addChildcdata($element_name, $cdata) { $this->$element_name = NULL; $this->$element_name->addCData($cdata); } Эта функция, добавленная в расширяющий класс, позволяет напрямую добавлять CData. - person user151841; 15.12.2015
comment
Я могу просто добавить свой 2c, что при загрузке файла simplexml_load_file/string() вы можете просто предоставить ему параметр LIBXML_NOCDATA? php.net/manual/en/libxml.constants.php - person ReSpawN; 19.07.2017

Вот моя версия этого класса с быстрым методом addChildWithCDATA на основе вашего ответ:

    Class SimpleXMLElementExtended extends SimpleXMLElement {

  /**
   * Adds a child with $value inside CDATA
   * @param unknown $name
   * @param unknown $value
   */
  public function addChildWithCDATA($name, $value = NULL) {
    $new_child = $this->addChild($name);

    if ($new_child !== NULL) {
      $node = dom_import_simplexml($new_child);
      $no   = $node->ownerDocument;
      $node->appendChild($no->createCDATASection($value));
    }

    return $new_child;
  }
}

Просто используйте его так:

$node = new SimpleXMLElementExtended();
$node->addChildWithCDATA('title', 'Text that can contain any unsafe XML charachters like & and <>');
person Ronen Yacobi    schedule 11.12.2013

Вы также можете создать для этого вспомогательную функцию, если не хотите расширять SimpleXMLElement:

 /**
  * Adds a CDATA property to an XML document.
  *
  * @param string $name
  *   Name of property that should contain CDATA.
  * @param string $value
  *   Value that should be inserted into a CDATA child.
  * @param object $parent
  *   Element that the CDATA child should be attached too.
  */
 $add_cdata = function($name, $value, &$parent) {
   $child = $parent->addChild($name);

   if ($child !== NULL) {
     $child_node = dom_import_simplexml($child);
     $child_owner = $child_node->ownerDocument;
     $child_node->appendChild($child_owner->createCDATASection($value));
   }

   return $child;
 };
person Patrick Coffey    schedule 05.12.2014

Вот мое комбинированное решение с добавлением дочернего элемента с CDATA или добавлением CDATA к узлу.

class SimpleXMLElementExtended extends SimpleXMLElement
{
    /**
    * Add value as CData to a given XML node
    *
    * @param SimpleXMLElement $node SimpleXMLElement object representing the child XML node
    * @param string $value A text to add as CData
    * @return void
    */
    private function addCDataToNode(SimpleXMLElement $node, $value = '')
    {
        if ($domElement = dom_import_simplexml($node))
        {
            $domOwner = $domElement->ownerDocument;
            $domElement->appendChild($domOwner->createCDATASection("{$value}"));
        }
    }

    /**
    * Add child node with value as CData
    *
    * @param string $name The child XML node name to add
    * @param string $value A text to add as CData
    * @return SimpleXMLElement
    */
    public function addChildWithCData($name = '', $value = '')
    {
        $newChild = parent::addChild($name);
        if ($value) $this->addCDataToNode($newChild, "{$value}");
        return $newChild;
    }

    /**
    * Add value as CData to the current XML node 
    *
    * @param string $value A text to add as CData
    * @return void
    */
    public function addCData($value = '')
    {
        $this->addCDataToNode($this, "{$value}");
    }
}

// Usage example:

$xml_doc = '<?xml version="1.0" encoding="utf-8"?>
<offers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1">
</offers>';

$xml = new SimpleXMLElementExtended($xml_doc);

$offer = $xml->addChild('o');
$offer->addAttribute('id', $product->product_id);
$offer->addAttribute('url', 'some url');

$cat = $offer->addChildWithCData('cat', 'Category description as CDATA');

// or

$cat = $offer->addChild('cat');
$cat->addCData('Category description as CDATA');
person Krzysztof Przygoda    schedule 25.08.2016