繁体   English   中英

带有特殊字符的DOMDocument appendXML

[英]DOMDocument appendXML with special characters

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

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

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

$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']);

提前致谢!

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

这意味着您必须使用不间断空格的数字等效值,即  或(以十六进制表示)  

如果您试图将HTML保存到XML容器中,则将其另存为文本。 HTML和XML可能看起来很相似,但是却截然不同。 appendXML()期望格式正确的XML作为参数。 改用nodeValue属性,它将对XML字符串进行XML编码而没有任何警告。

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

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

就像Tomalak指出的那样,没有  在XML中。 因此,您指定了DOMImplementation的操作正确,因为在XHTML中存在  但是,要让DOM知道该文档是XHTML,您必须针对DTD进行加载和验证。 DTD位于

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

但是由于每天有对该页面的数百万个请求,因此W3C决定阻止对该页面的访问 ,除非请求中发送了UserAgent。 要提供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();

这仍然需要一些时间来完成(不要问我为什么),但是最后,您会得到( 重新格式化为SO

<?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()问题

我确实看到有问题的问题,并且该问题已经得到回答,但是如果我愿意的话,我想提出我过去对类似问题的看法。

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

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

<?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实体定义作为内部DOCTYPE定义。 然后,将HTML内容包装在document元素中,以便能够处理一系列子元素。 然后将解析的节点导入并添加到目标DOM中。

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

虽然聪明人可能是一个不错的选择(为什么要第14次发明方向盘?),但etranger可能有一点意义。 在某些情况下,您不想使用像完整的新包装(未经研究)的软件包那样的过大杀伤力,而更像是想要从数据库中发布一些数据,而这些数据恰好包含XML解析器有问题的html东西。

警告,以下是一个简单的解决方案,但是除非您确定可以摆脱它,否则请不要这样做! (我是在截止日期前约2小时并且没有时间学习的时候才这样做的,让诸如此类的东西成为孤独的工具...)

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

和普雷斯托! =)

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

// 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);

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

无论如何,这是一个hack,不是好的代码。 但它挽救了我的生命,并希望与遇到此特定问题的其他人分享,并花几分钟时间。

使用上述内容的风险自负。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM