DOMDocument 怎样处理 HTML fragment
处理一段 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
. '';
// 暂存 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。

| 你的输入 | 建议选择 | 不要忽略 |
|---|---|---|
| 已有多个合法 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 结构建立后单独完成。
Fiber 怎样安全取得恢复值
- 上一篇
- Fiber 怎样安全取得恢复值
- 下一篇
- stream_context_create 怎样设置 HTTP 超时
-
- 文章 · php教程 | 52分钟前 |
- mb_detect_encoding 怎样判断编码候选
- 421浏览 收藏
-
- 文章 · php教程 | 54分钟前 | json_validate PHP JSON
- json_validate 怎样只验证 JSON
- 460浏览 收藏
-
- 文章 · php教程 | 56分钟前 | HTTP · PHP · php stream_context_create http timeout
- stream_context_create 怎样设置 HTTP 超时
- 309浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- Fiber 怎样安全取得恢复值
- 120浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- Randomizer 怎样生成可复现随机序列
- 194浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- array_find 怎样返回第一个匹配元素
- 363浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- createFromFormat 怎样读取微秒时间
- 442浏览 收藏
-
- 文章 · php教程 | 1小时前 | PHP · 时区 · 日期时间 · php 时区 DateTimeZone DateTimeImmutable
- DateTimeImmutable 怎样按时区创建不可变时间
- 209浏览 收藏
-
- 文章 · php教程 | 16小时前 | 反射 · PHP · php ReflectionAttribute Attribute::IS_REPEATABLE
- PHP ReflectionAttribute 怎样判断属性是否允许重复声明
- 321浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 685次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 699次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 753次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 717次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 552次使用
-
- PHP JSON_THROW_ON_ERROR 抛错后怎么保留原始字段位置
- 2026-09-09 501浏览
-
- PHP 8.5 array_last() 怎么处理空数组:从 null 结果到兼容旧版本的 Polyfill
- 2026-08-16 501浏览
-
- 宝塔配置Ruby环境:RVM+Nginx反代教程
- 2026-05-29 501浏览
-
- unset函数作用范围详解
- 2026-05-29 501浏览
-
- VS Code配置Xdebug教程:PHP调试技巧全解析
- 2026-05-13 501浏览

