CakePHP Xml biblioteca de utilitários desencadeia DOMDocument aviso

Estou gerando XML em uma visão com a biblioteca núcleo Xml do CakePHP:

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

A vista é alimentada pelo controlador com uma matriz:

 $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', ), ), ), ), ), ), ), ), ) ); 

Por qualquer motivo, o CakePHP está emitido uma chamada interna como esta:

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

… o que desencadeia um aviso PHP:

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

… porque ( conforme documentado ), DOMDocument::createElement não escapa de valores. No entanto, ele só faz isso em certos nós, como ilustra o caso de teste.

Estou fazendo algo errado ou acabei de acertar um bug no CakePHP?

Isso pode ser um erro no método DOMDocument::createElement() PHP. Você pode evitá-lo. Crie o textnode separadamente e anexe-o ao nó do elemento.

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

Saída: https://eval.in/134277

 string(58) " S & T: ERROR " 

Esta é a maneira pretendida de adicionar nós de texto a um DOM. Você sempre cria um nó (elemento, texto, cdata, …) e anexa-o ao seu nó pai. Você pode adicionar mais de um nó e diferentes tipos de nós para um dos pais. Como no exemplo a seguir:

 $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(); 

Saída:

  

Hello World!

Isso é, de fato, porque os methods DOMDocument querem que os caracteres corretos sejam enviados em html; isto é, caracteres como o que irá quebrar o conteúdo e gerar um erro de unterminated entity reference

apenas htmlentities () antes de usá-lo para criar elementos:

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

é por causa desse caracter: & Você precisa replace isso pela entidade HTML relevante. & Para realizar a tradução, você pode usar a function htmlspecialchars . Você deve escaping do valor ao escrever a escrita na propriedade nodeValue. Como citado de um relatório de erro em 2005 localizado aqui

Os termos estão devidamente codificados quando se configura a propriedade textContent. Infelizmente, eles não são codificados quando a seqüência de texto é passada como o segundo argumento opcional para DOMElement :: createElement. Você deve criar um nó de texto, definir o textoContenente e, em seguida, append o nó de texto ao novo elemento.

 htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 

Esta é a tabela de tradução:

 '&' (ampersand) becomes '&' '"' (double quote) becomes '"' when ENT_NOQUOTES is not set. "'" (single quote) becomes ''' (or ') only when ENT_QUOTES is set. '<' (less than) becomes '<' '>' (greater than) becomes '>' 

Este script irá fazer as traduções recursivamente:

  $value){ $type[$key] = clean($value); } return $type; } else { $string = htmlspecialchars($type, ENT_QUOTES, 'UTF-8'); return $string; } } $data = 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', ) , ) , ) , ) , ) , ) , ) , ) , ); $data = clean($data); 

Saída

 Array ( [data] => Array ( [root] => Array ( [0] => Array ( [@id] => A & B: OK [name] => C & D: OK [sub1] => Array ( [@id] => E & F: OK [name] => G & H: OK [sub2] => Array ( [0] => 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 ) ) ) ) ) ) ) ) ) 

O problema parece estar em nós que possuem atributos e valores, portanto, precisam usar a syntax @ :

 '@id' => 'A & B: OK', // <-- Handled as plain text 'name' => 'C & D: OK', // <-- Handled as plain text '@' => 'S & T: ERROR', // <-- Handled as raw XML 

Escrevi uma pequena function auxiliar:

 protected function escapeXmlValue($value){ return is_null($value) ? null : htmlspecialchars($value, ENT_XML1, 'UTF-8'); } 

... e cuide-se de chamá-lo manualmente quando crio a matriz:

 '@id' => 'A & B: OK', 'name' => 'C & D: OK', '@' => $this->escapeXmlValue('S & T: NOW WORKS FINE'), 

É difícil dizer se é bug ou recurso, já que a documentação não o menciona.