在HTML片段上加载HTML LIBXML_HTML_NOMIMULED会生成不正确的标记


loadHTML LIBXML_HTML_NOIMPLIED on an html fragment generates incorrect tags

在HTML片段中使用LIBXML_HTML_NOMIMULED标志会生成不正确的标记:

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';
$doc = new DOMDocument();
$doc->loadHTML($str, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
echo $doc->saveHTML();

输出:

<p>Lorem ipsum dolor sit amet.<p>Nunc vel vehicula ante.</p></p>

我已经找到了使用正则表达式解决这一问题的方法,但这违背了使用DOM的目的。我已经用几个版本的libxml和php进行了测试,最新的版本是libxml 2.9.2和php 5.6.7(Debian Jessy)。欢迎提出任何建议。

重新排列由您使用的LIBXML_HTML_NOIMPLIED选项完成。看起来对你的案子来说还不够稳定。

此外,出于便携性的原因,您可能不想使用它,例如,我手头有一个带有Libxml 2.7.8的PHP 5.4.36,它不支持LIBXML_HTML_NOIMPLIED(Libxml>=2.7.7),但稍后支持LIBXML_HTML_NODEFDTD(Libxml>=2.7.8)选项。

我知道处理它的方法。当你加载片段时,你把它包装成一个<div>元素:

$doc->loadHTML("<div>$str</div>");

这有助于引导DOMDocument了解所需的结构。

然后,您可以从文档本身提取此容器:

$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);

然后从文档中删除所有子项:

while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}

现在文档完全为空,您现在可以再次附加子级。幸运的是,我们之前删除了<div>容器元素,所以我们可以从中添加:

while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}

然后可以使用已知的saveHTML方法检索片段:

echo $doc->saveHTML();

在您的场景中给出:

<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>

这种方法与现场现有的材料有点不同(请参阅我在下面给出的参考资料),所以下面的例子是:

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';
$doc = new DOMDocument();
$doc->loadHTML("<div>$str</div>");
$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}
while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}
echo $doc->saveHTML();

我还非常推荐关于如何在没有HTML包装的情况下保存DOMDocument的HTML了解更多信息以及关于内部html 的信息

参考文献

  • 如何在没有HTML包装的情况下保存DOMDocument的HTML
  • 如何获取DOMNode的innerHTML

LIBXML_HTML_NOIMPLIED选项没有错误,只是文档记录不好。要解决这个问题,请用<html>…</html>包装输入字符串,处理HTML,然后将其从输出中删除。LibXML需要一个根节点,它将找到的第一个元素视为根节点,删除中途找到的(位置不正确的)结束标记,然后输出在文档末尾找到的第一元素的结束标记。从(Lib)XML的角度来看,这是合乎逻辑的。