我正在使用CakePHP的XML核心库在视图中生成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
不会转义值。但是,它仅在某些节点中执行此操作,如测试用例所示。
我做错了什么还是我只是在 CakePHP 中遇到了错误?
PHP DOMDocument::createElement()
方法中的一个错误。以下是避免此问题的两种方法。
创建文本节点
单独创建文本节点并将其追加到元素节点。
$dom = new DOMDocument;
$dom
->appendChild($dom->createElement('element'))
->appendChild($dom->createTextNode('S & T: ERROR'));
var_dump($dom->saveXml());
输出:
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>
物业DOMNode::$textContent
DOM Level 3 引入了一个名为 textContent
的新节点属性。它根据节点类型抽象节点的内容/值。设置元素节点的$textContent
会将其所有子节点替换为单个文本节点。读取它将返回所有后代文本节点的内容。
$dom = new DOMDocument;
$dom
->appendChild($dom->createElement('element'))
->textContent = 'S & T: ERROR';
var_dump($dom->saveXml());
这实际上是因为 DOMDocument 方法希望在 html 中输出正确的字符;也就是说,诸如 &
之类的字符会破坏内容并生成unterminated entity reference
错误
只是htmlentities((它在使用它来创建元素之前:
$dom = new DOMDocument;
$key = 'sub4';
$childValue = htmlentities('S & T: ERROR');
$dom->createElement($key ,$childValue);
这是因为这个字符:&
您需要将其替换为相关的HTML实体。 &
要执行翻译,您可以使用 htmlspecialchars 函数。写入 nodeValue 属性时,必须转义该值。引自 2005 年的错误报告,位于此处
与号在设置 属性文本内容。 不幸的是,它们没有在以下情况下进行编码 文本字符串作为可选的第二个参数传递给 圆顶::创建元素 您必须创建一个文本节点,设置文本内容,然后追加文本 节点到新元素。
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'),
很难说它是错误还是功能,因为文档没有提到它。