我想使用PHP标准库的DOM部分对PHP文档执行某些操作。由于其他人已经discovered,因此必须处理已解码的实体。为了说明困扰我的是什么,我举了一个简单的例子。
假设我们有以下代码
$doc = new DOMDocument();
$doc->loadXML(<XML data>);
$xpath = new DOMXPath($doc);
$node_list = $xpath->query(<some XPath>);
foreach($node_list as $node) {
//do something
}
如果循环中的代码类似于
$attr = "<some string>";
$val = $node->getAttribute($attr);
//do something with $val
$node->setAttribute($attr, $val);
它工作正常。但如果它更像是
$text = $node->textContent;
//do something with $text
$node->nodeValue = $text;
和$text
包含一些已解码的&amp; ,即使根本不对$text
执行任何操作,也不会对其进行编码。
目前,我在$text
之前$node->nodeValue
申请htmlspecialchars。现在我想知道
我必须处理的XML文档主要是提要,因此解决方案应该非常通用。
修改
原来我原来的问题范围不对,对不起。在这里,我提供了一个实际描述行为的例子。
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://feeds.bbci.co.uk/news/rss.xml?edition=uk");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$doc = new DOMDocument();
$doc->loadXML($output);
$xpath = new DOMXPath($doc);
$node_list = $xpath->query('//item/link');
foreach($node_list as $node) {
$node->nodeValue = $node->textContent;
}
echo $doc->saveXML();
如果我使用
在CLI上执行此代码php beeb.php |egrep 'link|Warning'
我得到的结果如
&LT; LINK&GT; http://www.bbc.co.uk/news/world-africa-23070006#sa-ns_mchannel=rss</link&GT;
应该是
&LT; LINK&GT; http://www.bbc.co.uk/news/world-africa-23070006#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa</link&GT;
(并且,如果省略循环)并根据警告
警告:第15行的/private/tmp/beeb.php中的main():未终止的实体引用ns_source = PublicRSS20-sa
当我将htmlspecialchars
应用于$node->textContent
时,它运行正常,但我觉得这样做非常不舒服。
答案 0 :(得分:6)
您的问题基本上是将DOMText::nodeValue
设置为XML编码字符串还是逐字字符串。
所以,让我们试试看,然后将其设置为&
和'&
,看看会发生什么:
$doc = new DOMDocument();
$doc->loadXML('<root>*</root>');
$text = $doc->documentElement->childNodes->item(0);
echo "Before Edit: ", $doc->saveXML($text), "\n";
$text->nodeValue = "&";
echo "After Edit 1: ", $doc->saveXML($text), "\n";
$text->nodeValue = "&";
echo "After Edit 2: ", $doc->saveXML($text), "\n";
然后输出如下(PHP 5.0.0 - 5.5.0):
Before Edit: *
After Edit 1: &
After Edit 2: &amp;
这表明设置nodeValue
- DOMText
节点需要UTF-8编码字符串,DOM库会自动编码XML保留字符。
所以你应该不将htmlspecialchars()
应用到你用这种方式添加的任何文本上。这将创建一个双重编码。
在您编写的时候,您会遇到相反的情况我建议您在命令行/ IDE中执行一个独立的PHP示例,以便您可以准确地看到输出。并不是说您的浏览器将其呈现为HTML,然后您认为保留的XML字符尚未编码。
正如您所指出的那样,您不是在编辑DOMText
而是编辑DOMElement
节点。它有点不同,这里&
字符需要作为实体&
而不是逐字传递,但只有这个字符。
所以这需要更多的工作:
DOMText
节点。一切都将完美编码。DOMText
节点表单作为子级添加到第一步。完成了。在这里你的内在foreach修改显示:
foreach($node_list as $node) {
$text = $doc->createTextNode($node->textContent);
$node->nodeValue = "";
$node->appendChild($text);
}
对于你的具体例子,虽然我必须承认我不明白为什么你这样做,因为这不会改变价值所以它不需要这个。
提示: 在PHP中,DOMDocument可以直接打开此Feed,这里不需要卷曲:
$doc = new DOMDocument(); $doc->load("http://feeds.bbci.co.uk/news/rss.xml?edition=uk");
答案 1 :(得分:2)
正如hakre所解释的,问题是在PHP的DOM库中,设置 nodeValue w.r.t的行为。实体取决于节点的类别,特别是DOMText
和DOMElement
在这方面有所不同。
为了说明这一点,举个例子:
$doc = new DOMDocument();
$doc->formatOutput = True;
$doc->loadXML('<root/>');
$s = 'text &<<"\'&text;&text';
$root = $doc->documentElement;
$node = $doc->createElement('tag1', $s); #line 10
$root->appendChild($node);
$node = $doc->createElement('tag2');
$text = $doc->createTextNode($s);
$node->appendChild($text);
$root->appendChild($node);
$node = $doc->createElement('tag3');
$text = $doc->createCDATASection($s);
$node->appendChild($text);
$root->appendChild($node);
echo $doc->saveXML();
输出
Warning: DOMDocument::createElement(): unterminated entity reference text in /tmp/DOMtest.php on line 10
<?xml version="1.0"?>
<root>
<tag1>text &<<"'&text;</tag1>
<tag2>text &amp;&lt;<"'&text;&text</tag2>
<tag3><![CDATA[text &<<"'&text;&text]]></tag3>
</root>
在这种特殊情况下,更改DOMText
个节点的 nodeValue 是合适的。结合hakre's两个答案,可以得到一个非常优雅的解决方案。
$doc = new DOMDocument();
$doc->loadXML(<XML data>);
$xpath = new DOMXPath($doc);
$node_list = $xpath->query(<some XPath>);
$visitTextNode = function (DOMText $node) {
$text = $node->textContent;
/*
do something with $text
*/
$node->nodeValue = $text;
};
foreach ($node_list as $node) {
if ($node->nodeType == XML_TEXT_NODE) {
$visitTextNode($node);
} else {
foreach ($node->childNodes as $child) {
if ($child->nodeType == XML_TEXT_NODE) {
$visitTextNode($child);
}
}
}
}