diff --git a/README.md b/README.md index b9ec0cb..b9af324 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ profile 表达论文、政务文档、RAG、文档对比等不同需求。 (`research-wiki/scratch/html-table-cleaning-ecosystem-research-2026-08-21.md`):核实 Pandoc 表格 方言能力边界、Turndown 不处理合并单元格、Docling Markdown 导出重复合并单元格内容、MinerU 全 HTML 输出,印证审计 T001 的分流方向。 +- 2026-08-21 完成 45 份测试 Markdown 中 HTML 表格的只读结构分析 + (`research-wiki/scratch/html-table-real-data-analysis-2026-08-21.md`):剔除空 `` 后 + 简单表 53% / 合法合并表 8% / 损坏表 40%,并定位标签不闭合、丢失 colspan 标注、无表头三类主因。 - 2026-08-21 仓库由 `govdoc-md-cleaner` 更名为 `mdpolish`,GitHub 远程仓库与本地目录同步改名; 冻结的 design 记录和带日期的 scratch 笔记保留当时的旧名。 diff --git a/research-wiki/scratch/html-table-real-data-analysis-2026-08-21.md b/research-wiki/scratch/html-table-real-data-analysis-2026-08-21.md new file mode 100644 index 0000000..ff24e08 --- /dev/null +++ b/research-wiki/scratch/html-table-real-data-analysis-2026-08-21.md @@ -0,0 +1,170 @@ +# HTML 表格真实数据结构分析:45 份 GovDoc 测试 Markdown + +> 状态:只读分析记录,尚未进入任何 design。文中"建议"部分是候选方向,不是批准的方案。 +> +> 分析日期:2026-08-21。 +> +> 定位:用真实数据回答"我们的 HTML 表格到底长什么样、损坏在哪",为未来表格清洗 design 提供测量依据, +> 并修正 [`html-table-cleaning-ecosystem-research-2026-08-21.md`](html-table-cleaning-ecosystem-research-2026-08-21.md) +> 第 8 节中可以用数据回答的待决问题。 + +## 1. 结论先行 + +按"展开 rowspan/colspan 后每行列数是否一致"给全部顶层表格分类,**剔除空 `` 之后**的分布: + +| 分类 | 定义 | 数量 | 占比 | +|---|---|---|---| +| A 简单矩形表 | 无合并单元格、每行列数一致、无嵌套、无游离内容 | 2474 | 53% | +| B 合法合并表 | 有 rowspan/colspan,展开后仍是完整矩形 | 356 | 8% | +| C 损坏表 | 参差网格、标签截断、占位冲突等 | 1879 | 40% | + +四个改变预期的发现: + +1. **一半的"损坏"是空 `` 造成的假象**——剔除后 C 类从 54% 降到 40%; +2. **标签不闭合会吞掉半篇文档**——最极端的一个未闭合表格吞了 2.79MB、1294 个表格; +3. **参差网格与合并单元格强相关**——六成以上参差表带 span 属性,指向转换器丢失 colspan 标注; +4. **无表头是常态**(A 类中 96%),**表格内图片为零**,单元格内管道符为零。 + +## 2. 数据范围与方法 + +- 输入:`/home/lihaoze/gov_test_data/compare/*/uploads/*.md`,45 份,全程只读; + 本文只含聚合数字和结构事实,不含任何原文片段; +- 分析脚本在会话级临时目录 `/tmp/table-analysis/analyze.py`,未入库(仓库处于文档治理阶段,无源码目录); + 本文第 2.1 节的规则描述是复现依据; +- 解析器:lxml `HTMLParser(recover=True)`。本地未装 html5lib。 + +### 2.1 测量规则 + +1. **代码围栏遮蔽**:先标记 ```` ``` ````/`~~~` 围栏内的位置,围栏里的 `` 不计入; +2. **片段切分**:对围栏外的 `` 事件按嵌套深度配对出顶层片段;悬空的 `
` + (无对应开启)单独计数并忽略;到文件尾仍未闭合的片段标记 `truncated`; +3. **结构分析**:每个片段单独喂给 lxml 容错解析;对每个 `` 展开 rowspan/colspan 建二维占位网格, + 检测占位冲突,计算每行展开后的列数,收集表头、嵌套、游离文本、块级子标签、空单元格、超长单元格、 + 退化重复等特征; +4. **分类谓词**(`truncated` 为片段级标记,其余为表格级): + + ```text + A_simple : 非 truncated 且 span_cells=0 且无嵌套 且每行展开列数一致 + 且无占位冲突 且无游离文本 且块级子标签 ⊆ {br} + B_span_ok: 非 truncated 且无占位冲突 且每行展开列数一致 且无游离文本 + C_broken : 其余全部 + ``` + +5. **空行修复复核**:把没有任何 `td`/`th` 子元素的 `` 整行剔除后重新跑同一分类,观察迁移。 + +## 3. 总量与原始分类 + +- 45 份文档共 7550 个 `` 259(5.5%)、 +单行表 157、截断片段 67、超长单元格(>500 字符)197、巨型片段(>10KB)70、退化重复内容 33、 +游离文本 27、占位冲突 14、嵌套表格 5、含块级标签 5、**含图片 0**。 + +## 4. 发现一:空 `` 是最大的单一"假损坏"来源 + +完全没有 `td`/`th` 子元素的空行标签在参差表里出现 3000+ 行次。它们把"列数一致的好表"撑成 +"某些行 0 列"的参差表。 + +剔除空行后重新分类: + +| 迁移路径 | 数量 | +|---|---| +| C_broken → A_simple | 553 | +| C_broken → B_span_ok | 94 | + +即分类变为 **A 2474(53%)/ B 356(8%)/ C 1879(40%)**,一条零风险修复救回 13% 的表。 +空行不含任何内容,剔除是无损的。 + +空行出现的位置:表中间 1151 处、表尾 385 处(对修复后仍为 C 的表统计),**没有出现在表头位置**—— +符合"转换器输出残留"而非"表头占位"的形态。 + +## 5. 发现二:标签不闭合会吞掉后续正文 + +最极端案例 `001-2/uploads/file_0_PDF.md`:全文件 1315 开 / 1124 闭;从第 1315 行开始的一个未闭合 +表格把后续 **2,789,092 字符、内部含 1294 个表格**的内容全部吞进一个"顶层片段"。 + +全库 395 个闭合缺口意味着:**清洗的第一步不是处理表格,而是安全切分片段**。深度配对在标签缺失时 +会把正文和后续完整表格归并进一个巨型片段,后续所有基于片段的统计和修改都会失真。 + +切分策略的可用锚点:相邻顶层表格之间,2943 对隔着真实文本、1661 对只隔空行——块边界 +(空行 + 后续正文)在实际数据中是可识别的。 + +## 6. 发现三:参差网格与 span 强相关,指向"丢 colspan 标注" + +2506 个参差表中 **1624 个(65%)带 span 属性**——格子内容在、宽度信息没了,是转换器丢失 +colspan 标注的形态,不全是真缺内容。 + +剔除空行后仍参差的 1851 个表,参差发生位置: + +| 位置 | 数量 | 推断成因 | +|---|---|---| +| 中间各行乱 | 780 | 真·结构损坏或逐行丢标注 | +| 表头行比正文长 | 619 | 多级表头被拍平成一行、丢层级 | +| 首行短 | 239 | 表头/首行缺格 | +| 仅尾部截短 | 213 | 跨页截断尾巴 | + +四类成因不同,修复策略应当不同,但**都不能靠补空单元格自动修**——那正是 +[`../reference/GOVDOC_SAAS_CLEANING_SCOPE.md`](../reference/GOVDOC_SAAS_CLEANING_SCOPE.md) T002 +明确反对、且 rumdl MD056 的 auto-fix 方向被本仓库否决的做法。 + +## 7. 发现四:无表头是常态,转换障碍集中在 `
` + +对修复后仍是 A 类的 2474 个表,检查转 GFM 管道表格的内容障碍: + +| 障碍 | 数量 | 占 A 类 | +|---|---|---| +| 无 `
` 表头 | 2393 | 96% | +| 单元格含 `
` | 501 | 20% | +| 单行表 | 176 | 7% | +| 超长单元格(>500 字符) | 54 | 2% | +| 单元格文本含 `\|` | 0 | 0% | +| 表格内 `` | 0 | 0% | + +转义压力比预期小(管道符、图片都是零),压力集中在**合成表头**和 `
` 处理上。 + +## 8. 建议的处理策略(候选,未批准) + +三步走,对齐审计 T001 的分流方向,用真实数据修正边界: + +1. **切分**:容错定位 `` 片段;未闭合的在块边界截断并标记 truncated, + 绝不让深度配对吞正文; +2. **无损修复**:只做剔除空 `` 这一级别的零风险修复(实测救回 13% 的表); +3. **分流**: + - A 类(53%)→ 转 GFM:合成表头、`
` 转空格、管道符转义兜底; + - B 类(8%)→ 保留 HTML,规范化输出; + - C 类(40%)→ 不自动修,保留原样 + 按第 6 节四类成因报告分类原因,交人工确认或回源。 + +对生态调研第 8 节待决问题的数据回答: + +- 问题 4(无表头表格怎么处理):数据表明无表头占绝对多数(96%),"补合成表头转 GFM" + 是主流路径;空表头还是首行充当表头仍是 design 待决; +- 问题 1(简单/复杂分界线):分界线除了合并单元格,必须加"展开后网格是否矩形"—— + 本数据中它是比 span 更强的损坏信号(53% 对 40%); +- 问题 2、3(规范 HTML 程度、JSON schema)本次数据没有新增证据,维持开放。 + +## 9. 局限 + +- 全部数字只来自这 45 份文档,不得外推为一般结论(CLAUDE.md 第 5 节约束); +- 53/8/40 依赖"剔除空 ``"这条规则被采纳;不采纳则为 41/5/54; +- lxml recover 解析可能自行重排损坏片段,个别表格的行列统计是解析结果而非字节事实; +- 巨型片段内部的表格统计(如 001-2/file_0 被吞的 1294 个)已计入总数,但它们在原文中的 + 真实边界未经人工核对; +- 参差位置的四分类用的是简单规则(与列数众数比较),是启发式归类,不是语义判断。 + +## 10. 验证状态 + +- 第 3 至 7 节所有数字:2026-08-21 由只读脚本在真实数据上实际运行得出,脚本未入库; +- 分类谓词与第 2.1 节规则描述和脚本逻辑一致,可据此重建等价测量; +- 未验证:任何修复或转换策略的实际效果——A/B/C 分流、空行剔除、块边界截断都还没有实现, + 需要等对应 design 批准后用受控样本测试。