How to write CDATA using SimpleXmlElement?
Asked Answered
H

5

59

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>
Hazlitt answered 7/6, 2011 at 3:0 Comment(9)
Doesn't look like SimpleXML supports creation of CDATA nodes. Try DOM insteadBarbican
Why do you care? <title lang="en">Site Title</title> and <title lang="en"><![CDATA[Site Title]]></title> are identical except that one uses more bytes and is harder to read as a human.Ackerman
@Ackerman Good point. Just a client requirement.Hazlitt
@Ackerman - 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 <title lang="en">Site<br>Title</title> it would break the XML parser (opening br tag without a closing is not strict XML) whereas <title lang="en"><![CDATA[Site<br>Title]]></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.Oedipus
@JimboJonny — Which is fine if you are writing it by hand, but the question is about generating it from PHP.Ackerman
@Ackerman - 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.Oedipus
@Ackerman - 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 <em></em> 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.Oedipus
"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.Ackerman
@Ackerman - 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.Oedipus
H
95

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

    <?php
    
    // http://coffeerings.posterous.com/php-simplexml-and-cdata
    // https://web.archive.org/web/20110223233311/http://coffeerings.posterous.com/php-simplexml-and-cdata
    
    // Customized 'SimpleXMLElement' class.
    class SimpleXMLExtended extends SimpleXMLElement {
    
      // Create CDATA section custom function.
      public function addCData( $cdata_text ) {
        $node              = dom_import_simplexml( $this ); 
        $ownerDocumentNode = $node->ownerDocument;
        
        $node->appendChild( $ownerDocumentNode->createCDATASection( $cdata_text )); 
      }
    
    }
    
    // How to create the following example, below:
    // <?xml version="1.0"?>
    // <site>
    //   <title lang="en"><![CDATA[Site Title]]></title>
    // </site>
    
    /*
     * Instead of SimpleXMLElement:
     * $xml = new SimpleXMLElement( '<site/>' );
     * create from custom class, in this case, SimpleXMLExtended.
    */
    
    // Name of the XML file.
    $xmlFile    = 'config.xml';

    // <?xml version="1.0"?>
    // <site></site>
    // ^^^^^^^^^^^^^
    $xml        = new SimpleXMLExtended( '<site/>' );
    
    // Insert '<title><title>' into '<site></site>'.
    // <?xml version="1.0"?>
    // <site>
    //   <title></title>
    //   ^^^^^^^^^^^^^^^
    // </site>
    $xml->title = NULL; // VERY IMPORTANT! We need a node where to append.
    
    // CDATA section custom function.
    // <?xml version="1.0"?>
    // <site></site>
    // <title><![CDATA[Site Title]]></title>
    //        ^^^^^^^^^^^^^^^^^^^^^^
    // </site>
    $xml->title->addCData( 'Site Title' );
    
    // Add an attribute.
    // <?xml version="1.0"?>
    // <site></site>
    //   <title lang="en"><![CDATA[Site Title]]></title>
    //          ^^^^^^^^^^
    // </site>
    $xml->title->addAttribute( 'lang', 'en' );
    
    // Save.
    $xml->saveXML( $xmlFile );
    
    ?>

XML file, config.xml, generated:

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

Thank you Petah, hope it helps!

Hazlitt answered 7/6, 2011 at 3:20 Comment(2)
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.Prelatism
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"? php.net/manual/en/libxml.constants.phpAcclimatize
J
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 <>');
Jato answered 11/12, 2013 at 6:22 Comment(0)
C
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;
 };
Calorific answered 5/12, 2014 at 0:18 Comment(0)
A
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>
   */
Alber answered 29/10, 2015 at 6:9 Comment(0)
E
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');
Essy answered 25/8, 2016 at 13:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.