Библиотека утилиты CakePHP Xml запускает предупреждение DOMDocument

Я создаю XML в представлении с основной библиотекой CakePHP Xml :

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

Вид подается от контроллера с помощью массива:

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

По какой-то причине CakePHP выдает внутренний вызов следующим образом:

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

… который вызывает предупреждение PHP:

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

… потому что ( как задокументировано ) DOMDocument::createElement не DOMDocument::createElement значения. Однако это происходит только в определенных узлах, как иллюстрирует тестовый пример.

Я что-то делаю неправильно или просто попал в CakePHP?

Это может привести к ошибке в методе DOMDocument::createElement() . Вы можете избежать этого. Создайте текстовый блок отдельно и добавьте его в узел элемента.

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

Выход: https://eval.in/134277

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

Это предназначенный способ добавления текстовых узлов в DOM. Вы всегда создаете узел (элемент, текст, cdata, …) и добавляете его к своему родительскому узлу. Вы можете добавить более одного узла и разные типы узлов одному родителю. Как в следующем примере:

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

Вывод:

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

Это на самом деле потому, что методы DOMDocument хотят, чтобы в HTML были выведены правильные символы; то есть символы, такие как & будут нарушать контент и генерировать unterminated entity reference ошибку unterminated entity reference

просто htmlentities (), прежде чем использовать его для создания элементов:

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

это из-за этого символа: & Вам нужно заменить его соответствующим объектом HTML. &amp; Для выполнения перевода вы можете использовать функцию htmlspecialchars . Вы должны избегать значения при записи записи в свойство nodeValue. Как указано в отчете об ошибке в 2005 году, расположенном здесь

Амперсанды Правильно закодированы при настройке свойства textContent. К сожалению, они не кодируются, когда текстовая строка передается как необязательный второй аргумент в DOMElement :: createElement. Вы должны создать текстовый узел, установить textContent, а затем добавить текстовый узел к новому элементу.

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

Это таблица переводов:

 '&' (ampersand) becomes '&amp;' '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set. "'" (single quote) becomes '&#039;' (or &apos;) only when ENT_QUOTES is set. '<' (less than) becomes '&lt;' '>' (greater than) becomes '&gt;' 

Этот скрипт будет выполнять рекурсивные переводы:

 <?php function clean($type) { if(is_array($type)) { foreach($type as $key => $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); 

Вывод

 Array ( [data] => Array ( [root] => Array ( [0] => Array ( [@id] => A &amp; B: OK [name] => C &amp; D: OK [sub1] => Array ( [@id] => E &amp; F: OK [name] => G &amp; H: OK [sub2] => Array ( [0] => Array ( [@id] => I &amp; J: OK [name] => K &amp; L: OK [sub3] => Array ( [@id] => M &amp; N: OK [name] => O &amp; P: OK [sub4] => Array ( [@id] => Q &amp; R: OK [@] => S &amp; T: ERROR ) ) ) ) ) ) ) ) ) 

Проблема, кажется, в узлах, которые имеют как атрибуты, так и значения, поэтому необходимо использовать синтаксис @ :

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

Я написал небольшую вспомогательную функцию:

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

… и заботиться о том, чтобы называть его вручную, когда я создаю массив:

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

Трудно сказать, является ли это ошибкой или функцией, поскольку в документации это не упоминается.