登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

DOMDocument 怎样处理 HTML fragment

来源:17golang原创

时间:2026-10-25 12:58:30 377浏览 收藏

处理一段 HTML fragment 时,最容易踩的坑是把它当成完整 HTML 文档交给 DOMDocument::loadHTML(),然后发现输出多了 html、head、body,或者多个并列节点的结构发生变化。更稳妥的做法是先判断输入类型:普通 HTML fragment 用临时根节点包装后解析;本身 XML 兼容的片段用 DOMDocumentFragment::appendXML();需要遵循现代 HTML5 解析规则时,再考虑 PHP 8.4 起提供的 Dom\HTMLDocument。

官方地址:https://www.php.net/manual/en/domdocument.loadhtml.php

如果目标是“解析一段普通 HTML 并取回其中的多个节点”,优先使用临时根节点包住 fragment,再遍历根节点的子节点分别调用 saveHTML()。不要依赖去掉外层标签的字符串替换,也不要把 loadHTML() 当成 HTML 清洗器。
要点速览
  • loadHTML() 面向 HTML 文档字符串,直接传 fragment 可能产生隐含的文档结构。
  • 临时根节点能让多个并列节点拥有明确父节点,输出时只序列化它的子节点。
  • createDocumentFragment() 配合 appendXML() 适合 XML 兼容片段,但不等于 HTML5 容错解析。
  • DOM 扩展使用 UTF-8;解析警告可以收集,但错误收集不代表输入已经安全。

先判断 fragment 的解析口径

HTML fragment 可能是

一段文字

重点 这样的多个并列节点,也可能是带 HTML5 标签、未闭合标签或实体的浏览器片段。它们都长得像字符串,但解析目标不同。

输入特征更合适的入口要留意什么
普通 HTML,允许浏览器式容错DOMDocument::loadHTML() 加临时根节点它使用 HTML4 解析规则,可能自动补结构
标签和实体符合 XML 语法createDocumentFragment() 加 appendXML()不闭合标签、裸 & 等会导致追加失败
需要现代 HTML5 解析语义Dom\HTMLDocument::createFromString()需要 PHP 8.4 起的 DOM API,并仍要按文档处理输入

PHP 官方手册特别提醒,loadHTML() 采用 HTML4 parser,与浏览器使用的 HTML5 parser 规则不同;它也不能安全地用来清洗不可信 HTML。这个边界决定了“处理 fragment”和“做安全过滤”必须拆成两个任务。

用包装根节点解析普通 HTML fragment

对于允许 HTML 容错、但最终只想拿回片段内容的场景,可以创建一个临时的 fragment-root。把输入放在这个节点内部,交给 loadHTML() 解析,最后遍历根节点的子节点并逐个序列化。这样不会把 html、head、body 当作业务片段的一部分。

HTML fragment 经过临时根节点、DOMDocument loadHTML 和子节点序列化的静态结构说明图
图1:结构说明图,展示普通 HTML fragment 的包装根节点与子节点序列化关系,不是运行截图。
'
        . '
' . $html . '
'; // 暂存 libxml 警告,避免把 HTML 容错提示直接混入页面输出。 $previous = libxml_use_internal_errors(true); try { if (!$dom->loadHTML($wrapped, LIBXML_NOERROR | LIBXML_NOWARNING)) { throw new InvalidArgumentException('HTML fragment 解析失败'); } $root = $dom->getElementById('fragment-root'); if (!$root instanceof DOMElement) { throw new RuntimeException('找不到 fragment-root'); } $result = ''; // 逐个序列化子节点,避免把临时根节点本身返回给调用方。 foreach ($root->childNodes as $child) { $result .= $dom->saveHTML($child); } return $result; } finally { // 恢复调用方原来的 libxml 错误处理设置。 libxml_clear_errors(); libxml_use_internal_errors($previous); } } $fragment = '

欢迎使用 DOM

片段'; echo parseHtmlFragment($fragment);

这里的关键不是某个“去壳”字符串技巧,而是让片段在 DOM 树里拥有明确的父节点。saveHTML($child) 只输出当前子节点,因此两个并列节点会保持为两个片段。若输入包含未闭合标签,loadHTML() 仍可能依据 HTML4 规则修复结构;这正是它适合“HTML 容错处理”而不适合“精确复现浏览器 DOM”的原因。

用 DOMDocumentFragment 处理 XML 兼容片段

如果片段来自受控模板,并且标签闭合、属性引号、实体都符合 XML 语法,可以绕过完整 HTML 文档包装,直接在同一个 DOMDocument 上创建 DOMDocumentFragment,再调用 appendXML()。它更像“把若干节点附加到 DOM 树”,而不是让 HTML parser 猜测缺失结构。

createDocumentFragment();

    // appendXML() 要求片段接近 XML 语法,适合受控、已闭合的节点。
    if (!$fragment->appendXML($html)) {
        throw new InvalidArgumentException('fragment 不是 XML 兼容的节点集合');
    }

    // 插入 fragment 时,实际加入的是 fragment 里的子节点。
    $parent->appendChild($fragment);
}

$dom = new DOMDocument('1.0', 'UTF-8');
$container = $dom->appendChild($dom->createElement('section'));

// 使用成对标签和 XML 实体,避免让 appendXML() 猜测 HTML 容错规则。
appendXmlFragment($dom, $container, '

第一段

第二段 & 补充

'); echo $dom->saveHTML($container), PHP_EOL;

这个方案适合“片段由程序生成,结构受控”的场景。它不适合直接接收用户粘贴的任意 HTML:HTML5 标签、未闭合节点和 HTML 专用实体都可能让 XML 追加失败。需要容错时回到上一节的临时根节点;需要安全过滤时另行使用白名单策略。

处理编码、警告和输出边界

DOM 扩展使用 UTF-8。中文片段在进入 DOM 前应统一编码,不能只在 new DOMDocument('1.0', 'UTF-8') 里声明版本就认为输入已经完成转码。如果输入来自其他编码,先在应用边界完成可靠的转码,再交给解析器。

libxml_use_internal_errors(true) 只是把解析警告收集到 libxml 内部,便于业务代码决定记录或返回;它不会修复 HTML,也不会移除脚本、事件属性或危险 URL。尤其是 loadHTML() 的官方说明明确指出,它不能安全地用于 sanitizing HTML。解析和清洗需要两套职责:

  • 解析层:把字符串转换成 DOM 节点,处理缺失闭合、编码和输出范围。
  • 策略层:根据业务白名单决定允许哪些元素、属性和链接协议。
  • 输出层:用 saveHTML() 序列化选中的节点,并确认没有把临时包装节点一起返回。

如果只是想把 fragment 当作纯文本插入,不要调用 appendXML() 或 loadHTML() 让它变成节点;请使用 createTextNode()。这条选择能避免“本来要显示字符串,却意外解释成标签”的语义变化。

选择 HTML5 方案并做速查

PHP 8.4 起,DOM 扩展提供了命名空间下的 Dom\HTMLDocument,官方文档将它作为遵循 HTML5 规范解析 HTML 的路径。它与旧的 DOMDocument::loadHTML() 不是简单的同名替换:前者面向现代 HTML 解析语义,后者仍保留兼容性更强但规则不同的 HTML4 parser。

DOMDocument loadHTML、DOMDocumentFragment appendXML 与 Dom HTMLDocument HTML5 解析边界说明图
图2:边界说明图,展示三种解析能力的静态关系与适用边界,不是运行证据。
你的输入建议选择不要忽略
已有多个合法 HTML 节点,需要返回片段字符串临时根节点 + loadHTML() + 子节点 saveHTML()HTML4 容错可能调整结构
受控模板,节点 XML 兼容createDocumentFragment() + appendXML()错误输入应检查返回值或异常
依赖 HTML5 解析规则,运行环境支持 PHP 8.4+Dom\HTMLDocument::createFromString()仍需单独做不可信输入的清洗

可以把判断压缩成一句话:需要 HTML 容错就包装后解析,需要受控节点拼接就用 fragment,需要 HTML5 规范语义就使用新的 Dom\HTMLDocument。无论哪条路径,都不要把解析成功等同于安全过滤成功。

常见问题

为什么直接 loadHTML(fragment) 会多出 html 和 body?

loadHTML() 的输入模型是 HTML 文档字符串。传入不完整片段后,解析器可能补出文档结构;把内容放到临时根节点并只序列化它的子节点,就能把包装层隔离在业务输出之外。

appendXML() 能处理所有 HTML fragment 吗?

不能。它适合 XML 兼容的受控片段,不负责浏览器式 HTML 容错。未闭合标签、HTML 专用实体或不符合 XML 语法的属性,都应改走 HTML 解析路径或先规范化输入。

loadHTML() 能不能顺便清洗用户提交的 HTML?

不能把它当 sanitizer。它负责解析和序列化,不能替代元素、属性、URL 协议等白名单策略;安全过滤要在 DOM 结构建立后单独完成。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>