# HTML 表格清洗专题调研:业界工具在 Markdown 清洗中如何处理 HTML 表格
> 状态:调研记录,尚未进入任何 design。
>
> 调研日期:2026-08-21。
>
> 定位:回答一个具体问题——Markdown 清洗中遇到 HTML 表格,业界工具实际怎么做。
> 结论用于印证或修正 [`../reference/GOVDOC_SAAS_CLEANING_SCOPE.md`](../reference/GOVDOC_SAAS_CLEANING_SCOPE.md)
> 中 T001/T002 的方向,不构成对任何方案的批准。
## 1. 结论先行
围绕“Markdown 里的 HTML 表格怎么办”,生态里的工具分成三种流派:
| 流派 | 做法 | 代表 |
|---|---|---|
| 生成端保真 | 复杂表格直接输出 HTML,不做管道表格 | MinerU、Docling(HTML/JSON 视图) |
| 强行归一 | 全部转成管道表格,合并单元格静默损坏或内容重复 | Turndown + gfm 插件、Docling(Markdown 视图) |
| 保真派 | 容错解析 → 校验网格 → 简单表转 GFM、复杂表保留 HTML/JSON | 本项目审计 T001 方向、Pandoc(grid tables / AST) |
支撑这张表的共同事实是:**GFM 管道表格语法在原理上表达不了合并单元格**。三种流派只是对这条约束的
不同回答——绕开它、硬转它、或者按能力分流。
## 2. 语法能力边界:GFM 管道表格没有合并单元格写法
Pandoc 手册对各家 Markdown 表格方言的原文描述(2026-08-21 从官方 MANUAL 核实):
| 方言 | 合并单元格 | 单元格内块级元素 |
|---|---|---|
| pipe tables(≈GFM 表格) | 不支持(单元格不能跨多行) | 不能包含块级元素 |
| multiline tables | 明确不支持跨行/跨列单元格 | 可以 |
| grid tables | **支持**("Cells can span multiple columns or rows") | 可以 |
| HTML `
` | 原生 `rowspan`/`colspan` | 可以 |
grid tables 是唯一支持合并单元格的 Markdown 表格语法,但它是 Pandoc 扩展,GitHub 不渲染,
对“清洗后还要在 GFM 渲染器里查看”的场景不可用。所以在 GFM 方言内部,合并单元格没有任何无损写法;
想保真只能保留 HTML,或存结构化 JSON。
Pandoc 手册同时警告:从表达能力更强的格式转换时"some document elements, such as complex tables,
may not fit","can be expected to be lossy"。
来源:[Pandoc MANUAL - Tables](https://pandoc.org/MANUAL.html)
## 3. 生成端:PDF→Markdown 转换器为什么输出 HTML 表格
MD 文档里出现 HTML 表格,通常不是 bug,而是转换器面对合并单元格(`rowspan`/`colspan`)、
多级表头等管道语法表达不了的结构时的标准回退。
| 工具 | 表格输出策略 | 依据 |
|---|---|--- |
| [MinerU](https://github.com/opendatalab/MinerU) | 所有表格一律输出 HTML 嵌在 Markdown 中,不做管道表格;支持跨页表格拼接 | 官方 README 功能列表 |
| [Docling](https://docling.org/) | 内部 TableFormer 模型专门恢复合并单元格;同一文档可导出 HTML / Markdown / JSON 三种视图 | 官网能力页 + docling-core 2.92.0 源码 |
对本项目的含义:HTML 表格是合法的中间形态,不是待清除的垃圾。清洗目标不是“消灭 HTML 表格”,
而是“识别哪些表格结构正确、哪些在转换中损坏”。
## 4. 反面教材一:Turndown 静默产出错位表格
[Turndown](https://github.com/mixmark-io/turndown) 是最流行的 HTML→Markdown 转换库之一,
[turndown-plugin-gfm 的 tables.js](https://github.com/mixmark-io/turndown-plugin-gfm/blob/master/src/tables.js)
(v1.0.2,2018 年发布后基本未改)只做两件事:
1. 首行不是全 ``(无表头行)的表格:保留 HTML 不转;
2. 其余表格:按 DOM 位置逐格输出管道符。
它**完全没有 `colspan`/`rowspan` 的处理代码**。合并单元格不触发上面的回退,直接按 DOM 位置压扁,
转出列数不齐的坏表,且**不报任何错**。
对本项目的含义:
- “无表头就不转”是能力判断驱动的回退,这个思想是对的;但它的能力判断漏掉了合并单元格;
- 连最流行的转换库在这里都会静默弄坏表格——审计要求“先建 DOM、校验网格、禁止正则替换”有真实事故支撑;
- 选 HTML→Markdown 转换库时,“是否处理 span”必须列入验证项,不能信 README 宣称。
## 5. 反面教材二:Docling 的 Markdown 导出重复合并单元格内容
读了 docling-core 2.92.0 的源码(wheel 解包,2026-08-21):
- 内部 `TableCell` 带 `row_span`/`col_span` 和起止行列偏移;`TableData.grid` 属性把同一个 cell 对象
**铺满**它覆盖的每个 (行, 列) 位置;
- **HTML 序列化器**(`transforms/serializer/html.py`):遍历网格时跳过被覆盖的续位
(`rowstart != i` 或 `colstart != j` 时 `continue`),只在起始位置输出,并正确带上
`rowspan="N"`/`colspan="N"`——语义保真;
- **Markdown 序列化器**(`transforms/serializer/markdown.py` 的 `MarkdownTableSerializer`):
直接遍历铺满后的 grid,每个位置都输出 `col.text`——一个 `row_span=3` 的单元格内容在 Markdown
输出里**重复出现 3 次**。转义只处理换行和管道符(`\n`→空格、`|`→`|`),再用
tabulate `tablefmt="github"` 输出管道表格。
即 Docling 面对“Markdown 视图必须有合并单元格”的需求,选择了**内容重复**来保住矩形形状。
这是“强行转管道表格会丢语义”的又一个实例,和 Turndown 的压扁是同一根源的两种表现。
对本项目的含义:
- “转 GFM”不是免费的格式变换,每一家实现都发明了自己的有损映射;
- 如果未来用 Docling 做回源提取(调研报告第 6.1 节的候选方向),它的 Markdown 导出不能直接当作
保真输出使用,需要用它的 `DoclingDocument` JSON 或 HTML 视图;
- 审计 T001 说“强行转 GFM 会丢失语义”,这里的机制证据是:跨行列单元格要么被压扁(Turndown)、
要么被重复(Docling)、要么失去合并关系本身(都失去 `rowspan`/`colspan` 语义)。
## 6. 清洗与格式化工具:主流选择是“不动 HTML 块”
| 工具 | 对 Markdown 内 HTML 表格的行为 | 来源 |
|---|---|---|
| remark / mdformat | raw HTML 当不透明块原样传递,不重新格式化、不转换 | [mdformat](https://mdformat.readthedocs.io/) 官方文档(核心保证是格式化前后 AST 一致,HTML 块不在处理范围) |
| rumdl MD033(no-inline-html) | 报告 ``(它有 Markdown 等价物),但 `fix` 只自动转 `em/strong/code/a/img/br/hr` 等行内简单标签,**不含表格**;`allowed-inside = ["table"]` 可整块豁免 | 本地 `reference/rumdl/docs/md033.md` |
| rumdl MD056(table-column-count) | 校验每行列数与表头一致,自动修复方式是补/删空单元格 | 本地 `reference/rumdl/docs/md056.md` |
| rumdl MD058(blanks-around-tables) | GFM 表格前后补空行 | 本地 `reference/rumdl/docs/md058.md` |
两点值得注意:
1. **没有主流工具自动把 HTML 表格转成 GFM 表格。** 连以“消灭 HTML”为目标的 MD033 都把表格留在
“只报告、不修复”的范围里——因为工具作者知道这个转换会弄坏表格。
2. **MD056 的自动修复方向与本项目审计相反。** 审计 T002 反对“补空单元格凑齐列数通过语法检查”,
因为这可能掩盖静默丢列;MD056 恰恰把补空作为修复手段。借用这类规则时必须关掉它的自动修复,
只取检测部分。
## 7. 与既有材料的关系
- 审计 T001(`../reference/GOVDOC_SAAS_CLEANING_SCOPE.md` 第 4.4 节)的五步法——容错解析建 DOM、
展开 rowspan/colspan 校验二维网格、简单矩形表转 GFM / 复杂表保留 HTML 或 JSON、拆多行、
回源确认幻觉——与本次调研的所有正面证据一致,未发现需要修正的点;
- [`markdown-cleaning-ecosystem-research-2026-08-20.md`](markdown-cleaning-ecosystem-research-2026-08-20.md)
第 5 节的推荐流程(html5lib 容错解析、禁正则、按合并单元格分流)同样得到印证;
- 新增的证据是反面案例的具体机制:Turndown 的压扁路径、Docling Markdown 视图的重复路径、
Pandoc 手册的方言能力原文、MD056 修复方向与审计相反。
## 8. 对本项目的待决问题(不是结论)
以下问题在对应 design 时需要回答,本调研只提供背景:
1. 简单/复杂表格的分界线,除了“有无合并单元格”,是否还要看单元格内块级元素、嵌套表格和表头层级;
2. 复杂表保留的“规范 HTML”具体规范到什么程度(属性白名单?标签重排?缩进策略?);
3. JSON grid 的格式是否对齐 Docling 的 `TableCell`(row_span/col_span/offset 字段),
还是自定义 schema——涉及与未来回源 adapter 的成本权衡;
4. 无表头表格(Turndown 的回退条件)按哪种流派处理:补合成表头转 GFM,还是保留 HTML。
## 9. 验证状态
- Pandoc 手册、Turndown 源码、MinerU README、Docling 官网:2026-08-21 通过网络核实;
- docling-core 2.92.0:下载 wheel 解包读源码核实,涉及
`MarkdownTableSerializer.serialize`、`TableData.grid`、HTML 序列化器的 span 处理;
- rumdl 三条规则:读本地 `reference/rumdl/docs/`(该目录为镜像副本,以 rumdl 上游为准);
- 未验证:各工具在本项目真实数据上的实际表现——需要等对应组件 design 批准后用受控样本测试。
|