Я создаю 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 & 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. &
Для выполнения перевода вы можете использовать функцию htmlspecialchars . Вы должны избегать значения при записи записи в свойство nodeValue. Как указано в отчете об ошибке в 2005 году, расположенном здесь
Амперсанды Правильно закодированы при настройке свойства textContent. К сожалению, они не кодируются, когда текстовая строка передается как необязательный второй аргумент в DOMElement :: createElement. Вы должны создать текстовый узел, установить textContent, а затем добавить текстовый узел к новому элементу.
htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
Это таблица переводов:
'&' (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 '>'
Этот скрипт будет выполнять рекурсивные переводы:
<?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 & 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 ) ) ) ) ) ) ) ) )
Проблема, кажется, в узлах, которые имеют как атрибуты, так и значения, поэтому необходимо использовать синтаксис @
:
'@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'),
Трудно сказать, является ли это ошибкой или функцией, поскольку в документации это не упоминается.