CakePHP Xml utility library triggers DOMDocument warning

Álvaro González picture Álvaro González · Apr 9, 2014 · Viewed 7.4k times · Source

I'm generating XML in a view with CakePHP's Xml core library:

$xml = Xml::build($data, array('return' => 'domdocument'));
echo $xml->saveXML();

View is fed from the controller with an array:

$this->set(
    array(
        'data' => array(
            'root' => array(
                array(
                    '@id' => 'A & B: OK',
                    'name' => 'C & D: OK',
                    'sub1' => array(
                        '@id' => 'E & F: OK',
                        'name' => 'G & H: OK',
                        'sub2' => array(
                            array(
                                '@id' => 'I & J: OK',
                                'name' => 'K & L: OK',
                                'sub3' => array(
                                    '@id' => 'M & N: OK',
                                    'name' => 'O & P: OK',
                                    'sub4' => array(
                                        '@id' => 'Q & R: OK',
                                        '@'   => 'S & T: ERROR',
                                    ),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        ),
    )
);

For whatever the reason, CakePHP is issuing an internal call like this:

$dom = new DOMDocument;
$key = 'sub4';
$childValue = 'S & T: ERROR';
$dom->createElement($key, $childValue);

... which triggers a PHP warning:

Warning (2): DOMDocument::createElement(): unterminated entity reference               T [CORE\Cake\Utility\Xml.php, line 292

... because (as documented), DOMDocument::createElement does not escape values. However, it only does it in certain nodes, as the test case illustrates.

Am I doing something wrong or I just hit a bug in CakePHP?

Answer

ThW picture ThW · Apr 9, 2014

This is a bug in PHPs DOMDocument::createElement() method. Here are two ways to avoid the problem.

Create Text Nodes

Create the textnode separately and append it to the element node.

$dom = new DOMDocument;
$dom
  ->appendChild($dom->createElement('element'))
  ->appendChild($dom->createTextNode('S & T: ERROR'));

var_dump($dom->saveXml());

Output:

string(58) "<?xml version="1.0"?>
<element>S &amp; T: ERROR</element>
"

This is the originally intended way to add text nodes to a DOM. You always create a node (element, text , cdata, ...) and append it to its parent node. You can add more then one node and different kind of nodes to one parent. Like in the following example:

$dom = new DOMDocument;
$p = $dom->appendChild($dom->createElement('p'));
$p->appendChild($dom->createTextNode('Hello '));
$b = $p->appendChild($dom->createElement('b'));
$b->appendChild($dom->createTextNode('World!'));

echo $dom->saveXml();

Output:

<?xml version="1.0"?>
<p>Hello <b>World!</b></p>

Property DOMNode::$textContent

DOM Level 3 introduced a new node property called textContent. It abstracts the content/value of a node depending on the node type. Setting the $textContent of an element node will replace all its child nodes with a single text node. Reading it returns the content of all descendant text nodes.

$dom = new DOMDocument;
$dom
  ->appendChild($dom->createElement('element'))
  ->textContent = 'S & T: ERROR';

var_dump($dom->saveXml());