# 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 批准后用受控样本测试。