如何让HTML5与DOMDocument配合使用?

25
我试图使用DOMDocument解析HTML代码并对其进行更改,然后将其组装回字符串并发送到输出。但是在解析方面存在一些问题,这意味着我发送到DOMDocument的内容并不总是以相同的形式返回 :) 下面是问题列表:
  1. 使用 ->loadHTML:

    • 无论 preserveWhitespaceformatOutput 设置如何,都会格式化我的文档(在预格式化文本上丢失空格)
    • 如果我有像 <header><footer> 等 html5 标签,它会给我报错。但是可以通过抑制来解决,所以我可以接受这一点。
    • 生成不一致的标记 - 例如,如果我添加一个 <link ... /> 元素(带有自闭合标记),解析/保存 HTML 后输出将是 <link .. >
  2. 使用 ->loadXML:

    • 编码实体,如 > 来自 <style><script> 标记:body > div 变成 body &gt; div
    • 所有标记都以相同的方式关闭,例如 <meta ... /> 变成 <meta...></meta>;但这可以用正则表达式修复。

我没有尝试过HTML5lib,但出于性能原因,我更喜欢使用DOMDocument而不是自定义解析器


更新:

就像Honeymonster提到的那样,使用CDATA可以解决loadXML的主要问题。

有没有什么方法可以防止除了特定集合以外的所有空HTML标签自动关闭,而不使用正则表达式?

现在我有:

$html = $dom->saveXML($node);

$html = preg_replace_callback('#<(\w+)([^>]*)\s*/>#s', function($matches){

       // ignore only these tags
       $xhtml_tags = array('br', 'hr', 'input', 'frame', 'img', 'area', 'link', 'col', 'base', 'basefont', 'param' ,'meta');

       // if a element that is not in the above list is empty,
       // it should close like   `<element></element>` (for eg. empty `<title>`)
       return in_array($matches[1], $xhtml_tags) ? "<{$matches[1]}{$matches[2]} />" : "<{$matches[1]}{$matches[2]}></{$matches[1]}>";
}, $html);

这段代码是有效的,但它也会在CDATA内容中进行替换,而我不希望这样做...

3
你有一个我们可以玩耍的测试片段吗? - John Conde
如果你连尝试都没尝试过,怎么知道html5lib比DOMDocument慢呢? - Brad
4
我理解因为它是用PHP编写的,所以DOMDocument是一个用C语言编写的PHP扩展。 - Alex
“预格式化文本丢失空格”我对此表示怀疑。请提供一个演示该问题的示例。而“自闭合标签”到底有什么问题呢?我的意思是,HTML5不需要它,对吧?或者你是指XHTML5? - hakre
实际上,某些元素确实需要它们,至少在某些浏览器中。 - Alex
6个回答

14

使用html5lib。它可以解析HTML5并生成DOMDocument。示例:

require_once '/path/to/HTML5/Parser.php';
$dom = HTML5_Parser::parse('<html><body>...');

文档


2
但是html5lib可以保存文档并返回一个格式良好的字符串吗?我在源代码中没有看到这个。 - Wiliam
但是为什么DOMDocument不支持HTML5? - zomega

12

如果您想支持HTML5,请不要触碰DOMDocument。

目前最好的选择似乎是https://github.com/Masterminds/html5-php

以前最好的选择是https://github.com/html5lib/html5lib-php但正如描述所说,它“目前未维护”。自2011年10月以来这一状态已经存在,所以我不再期待更新。

我没有在生产环境中使用过html5-php,因此无法提供任何关于它的实际体验。我使用html5lib-php在生产环境中,我会说它可以正确地解析格式良好的文档,但在某些简单语法错误方面会出现意外错误。另一方面,它似乎正确实现了采用机构算法和其他一些奇怪的边界情况。如果html5lib-php仍然得到维护,我仍然会选择它。然而,目前情况下,我更喜欢使用html5-php并可能帮助修复剩余的bug。


自 PHP 7.2 起,扩展 html-tidy 应该对 HTML5 有某种支持。我还没有任何使用它的经验,因此无法评价其实现质量。 - Mikko Rantalainen
我使用了PHP的“tidy”扩展,根据我的经验,解析器似乎很快,但是如果你从“tidy”请求DOM,那么速度会非常慢。实际上,它非常慢,以至于使用“tidy”将HTML5转换为XHTML(字符串->字符串转换),然后使用XML解析器创建DOM可能更快。 - Mikko Rantalainen

12

虽然已经过去了十年,但PHP DOMDocument仍存在问题,我发现了两种解决方法。

解决方法1

在loadHTML方法中添加LIBXML_NOERROR选项,代码如下:

<?php

$dom = new DOMDocument();

$dom->loadHTML('<header data-attribute="foo">bar<', LIBXML_NOERROR);

echo $dom->saveHTML();
// outputs the html with valid closing tag without any error
?>

解决方案2

在加载HTML之前添加libxml_use_internal_errors(true)

<?php

$dom = new DOMDocument();

libxml_use_internal_errors(true);

$dom->loadHTML('<header data-attribute="foo">bar<');

echo $dom->saveHTML();
// outputs the html with valid closing tag without any error
?>

7

不幸的是,或者说可能是幸运的是,domdocument被设计成不尝试保留原始文档的格式。这是为了通过使所有元素保持相同的样式来更容易地管理解析器的内部状态。据我所知,大多数解析器都会在内存中创建树形表示,并且直到用户请求这样做才会担心文本格式。这就是为什么您的自关闭标签与单独的闭合标签一起输出。好消息是这并不重要。

至于样式标签和脚本标签被转换为&lt;&gt;, 您可以尝试通过以下方式将问题元素的内容括在推荐的cdata标记中来避免转换:

<style>
  /*<![CDATA[*/
    body > div {
      width: 50%;
    }
  /*]]>*/
</style>

注释/* */在cdata声明周围是为了应对不知道cdata区域的破碎客户端,这些客户端将声明视为CSS代码。如果您只在内部使用文档,则可以省略/* */注释,并仅包含cdata声明。如果您在操作文档并在未检查/* */注释是否保留的情况下将其发送到浏览器,则可能会遇到上述破碎客户端问题;我不确定domdocument是否会保留这些注释。

1
哇,我简直不敢相信我没有想到使用 CDATA :) 谢谢,这解决了许多与 XML 解析器有关的问题,我本来想用它的 ;) - Alex
1
请注意,如果您要嵌入不受信任的用户输入,则仍然不能跳过编码数据。用户数据可能很容易包含子字符串 ]]>/*]]>*/ - Mikko Rantalainen

4

1
我从这个特定库中得到的主要收获是:允许使用CSS选择器查询DOM(目前可用的有:*,标签名,标签名#id,#id,标签名.classname,.classname,标签名[attribute="value"],[attribute="value"],标签名[attribute],[attribute]) - 这样你现在可以 $foo = $dom->querySelectorAll('img[srcset]'); - 非常有帮助。 - frumbert

-10

初始化domDocument时,请按以下方式操作:

$dom = new DOMDocument(5, 'UTF-8');

2
很遗憾,版本参数并不是指HTML版本。 - nibra
为什么你还没有删除这个回答? - miken32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接