4

我想使用 PHP 标准库的DOM部分对 XML 文档执行某些操作。正如其他人已经发现的那样,然后必须处理解码的实体。为了说明困扰我的问题,我举一个简单的例子。

假设我们有以下代码

$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包含一些解码的&,它不会被编码,即使一个人什么都不做$text

目前,我在设置之前应用了htmlspecialchars。现在我想知道$text$node->nodeValue

  1. 如果这足够了,
  2. 如果没有,什么就足够了,
  3. 如果有更优雅的解决方案,例如属性操作。

我必须处理的 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'

我得到类似的结果

<link> http://www.bbc.co.uk/news/world-africa-23070006#sa-ns_mchannel=rss</link >

应该是

<link> http://www.bbc.co.uk/news/world-africa-23070006#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa</link >

(并且是,如果省略循环)并根据警告

警告:main():第 15 行 /private/tmp/beeb.php 中的未终止实体引用 ns_source=PublicRSS20-sa

当我申请htmlspecialchars$node->textContent,它工作得很好,但我觉得这样做很不舒服。

4

2 回答 2

8

您的问题基本上是设置DOMText::nodeValue为 XML 编码字符串还是逐字字符串。

因此,让我们尝试一下并将其设置为&'&amp;然后看看会发生什么:

$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 = "&amp;";

echo "After Edit 2: ", $doc->saveXML($text), "\n";

然后输出如下(PHP 5.0.0 - 5.5.0):

Before Edit: *
After Edit 1: &amp;
After Edit 2: &amp;amp;

这表明设置-node 需要nodeValue一个DOMTextUTF-8 编码的字符串,并且 DOM 库会自动对 XML 保留字符进行编码。

所以你不应该应用htmlspecialchars()你以这种方式添加的任何文本。这将创建一个双重编码。

在您编写时,您会遇到相反的情况,我建议您在命令行上/在您的 IDE 中执行一个隔离的 PHP 示例,以便您可以准确地看到输出。并不是您的浏览器将其呈现为 HTML,然后您认为保留的 XML 字符没有被编码。


正如您所指出的,您不是在编辑一个节点,DOMText而是一个DOMElement节点。它的工作方式有点不同,这里的&字符需要作为实体&amp;而不是逐字传递,但是只有这个字符。

所以这需要更多的工作:

  1. 读出文本内容并将其转换为DOMText节点。一切都将被完美编码。
  2. 删除元素节点的节点值,使其为空。
  3. 将节点表单作为子节点添加DOMText第一步。

并做了。在这里,您的内部 foreach 修改显示:

foreach($node_list as $node) {
    $text = $doc->createTextNode($node->textContent);
    $node->nodeValue = "";
    $node->appendChild($text);
}

对于您的具体示例,尽管我必须承认我不明白您为什么要这样做,因为这不会改变值,因此不需要它。

提示:在 PHP DOMDocument 中可以直接打开这个提要,这里不需要 curl:

$doc = new DOMDocument();
$doc->load("http://feeds.bbci.co.uk/news/rss.xml?edition=uk");
于 2013-06-27T00:46:56.520 回答
2

正如hakre所解释的,问题在于在 PHP 的 DOM 库中,设置nodeValue wrt 实体的行为取决于节点的类,特别是在这方面有所不同DOMTextDOMElement为了说明这一点,举个例子:

$doc = new DOMDocument();
$doc->formatOutput = True;
$doc->loadXML('<root/>');

$s = 'text &amp;&lt;<"\'&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 &amp;&lt;&lt;"'&text;</tag1>
  <tag2>text &amp;amp;&amp;lt;&lt;"'&amp;text;&amp;text</tag2>
  <tag3><![CDATA[text &amp;&lt;<"'&text;&text]]></tag3>
</root>

在这种特殊情况下,更改节点的nodeValue是合适的。DOMText结合hakre 的两个答案,得到一个非常优雅的解决方案。

$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);
            }
        }
    }
}
于 2013-08-15T14:03:06.530 回答