57

I have this code to create and update xml file:

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

This generates the following xml file:

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

The question is: is there a way to add CDATA with this method/technique to create xml code below?

<?xml version="1.0"?>
<site>
  <title lang="en"><![CDATA[Site Title]]></title>
</site>
quantme
  • 3,609
  • 4
  • 34
  • 49
  • 2
    Doesn't look like SimpleXML supports creation of CDATA nodes. Try [DOM](http://php.net/manual/en/book.dom.php) instead – Phil Jun 07 '11 at 03:05
  • 2
    Why do you care? `Site Title` and `<![CDATA[Site Title]]>` are identical except that one uses more bytes and is harder to read as a human. – Quentin Dec 05 '14 at 16:43
  • @Quentin Good point. Just a client requirement. – quantme Mar 12 '15 at 16:29
  • 2
    @Quentin - CDATA usage allows easier writing because you do not have to worry about escaping anything/making it strict XML inside the data. For example if you wrote `Site<br/>Title` it would break the XML parser (opening br tag without a closing is not strict XML) whereas `<![CDATA[Site<br/>Title]]>` does not. So when dealing with clients it's often **more** readable to just have CDATA as opposed to all the wonky escaping said non-CDATA node may have to contain to avoid CDATA. – Jimbo Jonny Nov 17 '15 at 17:17
  • @JimboJonny — Which is fine if you are writing it by hand, but the question is about generating it from PHP. – Quentin Nov 17 '15 at 17:32
  • @Quentin - I disagree. The literal nature of CDATA makes it even more useful to not have to escape/use logic to remove things that would break dynamically created XML content. It's the equivalent of saying *"Just interpret this as a literal string, not part of the XML markup"*, which is extremely useful ANY time you don't know exactly what content is going to end up inside a node, whether it's being hand written or code populated such as via a CMS. There may be other ways to escape data to make it work when machine doing it, but CDATA is just as viable a method. – Jimbo Jonny Nov 17 '15 at 18:01
  • @Quentin - And CDATA often ends up more readable and using less bytes (the opposite of your two complaints). For example, which is more readable: having CDATA tags at the start and end or a bunch of escaped content everywhere? Which is more bytes of data: replacing every single possibly offending character with html entities or having 12 extra bytes of data total? Even a single `` tag escaped within the content would add as many bytes as the surrounding CDATA tags. You see, there are MANY cases where CDATA is a viable solution, whether hand or code populated XML. – Jimbo Jonny Nov 17 '15 at 18:03
  • "whether it's being hand written or code populated such as via a CMS" — If it is being populated with code, as it is in the question, then *the library will take care of the escaping or converting to CDATA*, it's a matter for the library to worry about, not the author. – Quentin Nov 17 '15 at 18:19
  • @Quentin - It is completely legitimate for the author of the CMS to decide they want their data stored in a form that is both more human readable and in many cases smaller. CDATA is a legitimate and even advantageous XML form and the author of a CMS has every legitimate right to determine that's how they want their data stored regardless of whether that's the default output of SimpleXML. The notion that nobody should ever do anything except the default behavior of a class/method because "that's for the library to worry about" is patently absurd. – Jimbo Jonny Nov 27 '15 at 19:26

5 Answers5

96

Got it! I adapted the code from this great solution (archived version):

    <?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 file generated:

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

Thank you Petah

quantme
  • 3,609
  • 4
  • 34
  • 49
  • 4
    `public function addChildcdata($element_name, $cdata) { $this->$element_name = NULL; $this->$element_name->addCData($cdata); }` This function added to the extending class allows you to append CData directly. – user151841 Dec 15 '15 at 20:17
  • I can just add my 2c that when you load it file `simplexml_load_file/string()` you can simply supply it with an option "LIBXML_NOCDATA"? http://php.net/manual/en/libxml.constants.php – ReSpawN Jul 19 '17 at 09:59
30

Here's my version of this class that has a quick addChildWithCDATA method, based on your answer:

    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;
  }
}

Simply use it like that:

$node = new SimpleXMLElementExtended();
$node->addChildWithCDATA('title', 'Text that can contain any unsafe XML charachters like & and <>');
Community
  • 1
  • 1
Ronen Yacobi
  • 844
  • 9
  • 16
19

You can also create a helper function for this, if you'd rather not extend 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;
 };
Patrick Coffey
  • 1,073
  • 8
  • 13
2
    class MySimpleXMLElement extends SimpleXMLElement{

        public function addChildWithCData($name , $value) {
            $new = parent::addChild($name);
            $base = dom_import_simplexml($new);
            $docOwner = $base->ownerDocument;
            $base->appendChild($docOwner->createCDATASection($value));
        }

    }

        $simpleXmlElemntObj = new MySimpleXMLElement('<site/>');

        /* USAGE */

        /* Standard */
        $simpleXmlElemntObj->addChild('Postcode','1111');

       /* With CDATA */
       $simpleXmlElemntObj->addChildWithCData('State','Processing');


    /* RESULT */
    /*
    <?xml version="1.0"?>
    <site>
        <Postcode>1111</Postcode>
        <State><![CDATA[Processing]]></State>
    </site>
   */
0

Here is my combined solution with adding child with CDATA or adding CDATA to the node.

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');
Krzysztof Przygoda
  • 1,217
  • 1
  • 17
  • 24