DOMDocument appendXML带有特殊字符

时间:2011-01-10 10:25:27

标签: php html domdocument

我正在从我的数据库中检索一些html字符串,我想将这些字符串解析为我的DOMDocument。问题是,DOMDocument以特殊字符发出警告。

  

警告:   DOMDocumentFragment :: appendXML()   [domdocumentfragment.appendxml]:   实体:第2行:解析器错误:实体   'nbsp'未在中定义   page.php文件   在第189行

我想知道为什么,我想知道如何解决这个问题。这是我页面的一些代码片段。我该如何解决这些警告?

$doc = new DOMDocument();

// .. create some elements first, like some divs and a h1 ..

while($row = mysql_fetch_array($result))
{
    $messageEl = $doc->createDocumentFragment();
    $messageEl->appendXML($row['message']); // gives it's warnings here!

    $otherElement->appendChild($messageEl);
}

echo $doc->saveHTML();

我还发现了一些关于验证的内容,但是当我应用它时,我的页面将不再加载。我试过的代码是这样的。

$implementation = new DOMImplementation();
$dtd = $implementation->createDocumentType('html','-//W3C//DTD XHTML 1.0 Transitional//EN','http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$doc = $implementation->createDocument('','',$dtd);
$doc->validateOnParse = true;
$doc->formatOutput = true;

// in the same whileloop, I used the following:
$messageEl = $doc->createDocumentFragment();
$doc->validate(); // which stopped my code, but error- and warningless.
$messageEl->appendXml($row['message']);

提前致谢!

5 个答案:

答案 0 :(得分:6)

XML中没有 。唯一定义了实际名称(而不是使用数字引用)的字符实体是&<>"'

这意味着你必须使用不间断空格的数字等价物,即 或(十六进制) 

如果您尝试将HTML保存到XML容器中,请将其另存为文本。 HTML和XML可能看起来很相似,但它们非常不同。 appendXML()期望格式良好的XML作为参数。请改用nodeValue属性,它将对您的HTML字符串进行XML编码,而不会发出任何警告。

// document fragment is completely unnecessary
$otherElement->nodeValue = $row['message'];

答案 1 :(得分:5)

这是一个棘手的问题,因为它实际上是多个问题。

就像Tomalak指出的那样,XML中没有 。所以你做了正确的事情来指定DOMImplementation,因为在XHTML中有 。但是,要让DOM知道文档是XHTML,您需要加载并验证DTD。 DTD位于

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd

但是因为除了在请求中发送了UserAgent之外,每天有数百万个请求到W3C decided to block access to the page {{}}}。要提供UserAgent,您必须创建自定义流上下文。

在代码中:

// make sure DOM passes a User Agent when it fetches the DTD
libxml_set_streams_context(
    stream_context_create(
        array(
            'http' => array(
                'user_agent' => 'PHP libxml agent',
            )
        )
    )
);

// specify the implementation
$imp = new DOMImplementation;

// create a DTD (here: for XHTML)
$dtd = $imp->createDocumentType(
    'html',
    '-//W3C//DTD XHTML 1.0 Transitional//EN',
    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
);

// then create a DOMDocument with the configured DTD
$dom = $imp->createDocument(NULL, "html", $dtd);
$dom->encoding = 'UTF-8';
$dom->validate();

$fragment = $dom->createDocumentFragment();
$fragment->appendXML('
    <head><title>XHTML test</title></head>
    <body><p>Some text with a &nbsp; entity</p></body>
    '
);
$dom->documentElement->appendChild($fragment);
$dom->formatOutput = TRUE;
echo $dom->saveXml();

这还需要一些时间才能完成(不要问我为什么)但最后,你会得到(重新格式化为

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC 
    "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>XHTML test</title>
    </head>
    <body>
        <p>Some text with a &nbsp; entity</p>
    </body>
</html>

另见DOMDocument::validate() problem

答案 2 :(得分:0)

我确实看到了有问题的问题,而且这个问题已经得到了回答,但如果我可以提出我过去处理类似问题的想法。

可能是因为您的任务需要在生成的XML中包含来自数据库的标记数据,但可能需要解析也可能不需要解析。如果它只是包含的数据,而不是XML的结构化部分,则可以在CDATA section(s)中放置数据库中的字符串,从而有效地绕过了此阶段的所有验证错误。

答案 3 :(得分:0)

这是另一种方法,因为我们不想降低网络请求的速度(或者根本不希望任何来自用户输入的网络请求):

<?php
$document = new \DOMDocument();
$document->loadHTML('<html><body></body></html>');

$html = '<b>test&nbsp;</b>';
$fragment = $document->createDocumentFragment();

$html = '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document [
<!ENTITY nbsp   "&#160;" >
]>
<document>'.$html.'</document>';

$newdom = new \DOMDocument();
$newdom->loadXML($html, LIBXML_HTML_NOIMPLIED | LIBXML_NOCDATA | LIBXML_NOENT | LIBXML_NONET | LIBXML_NOBLANKS);

foreach ($newdom->documentElement->childNodes as $childnode)
  $fragment->appendChild($fragment->ownerDocument->importNode($childnode, TRUE));

$document->getElementsByTagName('body')[0]->appendChild($fragment);

echo $document->saveHTML();

在这里,我们将DTD的相关部分,特别是latin1 entity definitions作为内部DOCTYPE定义包括在内。然后,将HTML内容包装在document元素中,以便能够处理一系列子元素。然后将已解析的节点导入并添加到目标DOM中。

我们的实际实现使用file_get_contents从本地文件加载包含所有实体定义的DTD。

答案 4 :(得分:-1)

虽然聪明可能是一个不错的选择(为什么第14次发明轮子?),etranger可能有一个观点。在某些情况下,您不希望使用像一个完整的新(和未经研究的)包这样的东西,但更像是想要从数据库发布一些数据,这些数据恰好包含XML解析器存在问题的HTML内容。

警告,以下是一个简单的解决方案,但除非你确定你可以逃脱它,否则不要这样做! (当我在截止日期前大约2个小时没有时间学习时,我做了这个,留下了一些像聪明的工具......)

在将字符串粘贴到appendXML函数之前,请通过preg_replace运行它。例如,替换所有&amp; NBSP; [some_prefix] _nbsp的字符。然后,在显示html的页面上,以相反的方式执行。

和Presto! =)

示例代码: 将文本放入文档片段的代码:

// add text tag to p tag.
// print("CCMSSelTextBody::getDOMObject: strText: ".$this->m_strText."<br>\n");
$this->m_strText = preg_replace("/&nbsp;/", "__nbsp__", $this->m_strText);
$domTextFragment = $domDoc->createDocumentFragment();
$domTextFragment->appendXML(utf8_encode($this->m_strText));
$p->appendChild($domTextFragment);
// $p->appendChild(new DOMText(utf8_encode($this->m_strText)));

解析字符串并编写html的代码:

// Instantiate template.
$pTemplate = new CTemplate($env, $pageID, $pUser, $strState);

// Parse tag-sets.
$pTemplate->parseTXTTags();
$pTemplate->parseCMSTags();

// present the html code.
$html = $pTemplate->getPageHTML();
$html = preg_replace("/__nbsp__/", "&nbsp;", $html);
print($html);

想出一个更强大的替代品可能是一个好主意。 (如果你坚持彻底:在time()值上做一个md5,并将其结果硬编码为前缀。所以就像在第一个片段中一样:

$this->m_strText = preg_replace("/&nbsp;/", "4597ee308cd90d78aa4655e76bf46ee0_nbsp", $this->m_strText);

在第二个:

$html = preg_replace("/4597ee308cd90d78aa4655e76bf46ee0_nbsp/", "&nbsp;", $html);

对于您需要规避的任何其他标签和内容,请执行相同的操作。

这是一个黑客,而不是任何想象力的代码。但它保存了我的直播,并希望与其他遇到这个特定问题的人分享它。

使用上述内容需要您自担风险。