Files
mdpolish/research-wiki/scratch/html-table-cleaning-ecosystem-research-2026-08-21.md
T
Bepr4 8ac4f1dd12 更名 mdpolish 并补充清洗流水线设计与调研记录
- 仓库由 govdoc-md-cleaner 更名为 mdpolish,更新 README、AGENTS、CLAUDE
  及 reference 中的仓库名;冻结的 design 与带日期 scratch 保留旧名
- 冻结 0002:可组合清洗组件与流水线(check/transform、单轮修改加最终复查)
- 新增 0003 草稿:第一版可执行核心架构,待评审
- 新增 HTML 表格清洗专题调研(2026-08-21)
2026-08-21 22:49:03 +08:00

144 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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、DoclingHTML/JSON 视图) |
| 强行归一 | 全部转成管道表格,合并单元格静默损坏或内容重复 | Turndown + gfm 插件、DoclingMarkdown 视图) |
| 保真派 | 容错解析 → 校验网格 → 简单表转 GFM、复杂表保留 HTML/JSON | 本项目审计 T001 方向、Pandocgrid 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 `<table>` | 原生 `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. 首行不是全 `<th>`(无表头行)的表格:保留 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`→空格、`|``&#124;`),再用
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 MD033no-inline-html | 报告 `<table>`(它有 Markdown 等价物),但 `fix` 只自动转 `em/strong/code/a/img/br/hr` 等行内简单标签,**不含表格**`allowed-inside = ["table"]` 可整块豁免 | 本地 `reference/rumdl/docs/md033.md` |
| rumdl MD056table-column-count | 校验每行列数与表头一致,自动修复方式是补/删空单元格 | 本地 `reference/rumdl/docs/md056.md` |
| rumdl MD058blanks-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 批准后用受控样本测试。