# Devlog · M5 多格式后端（DOCX + HTML）

> 日期：2026-06-09 · 里程碑：[plans/beating-docling.md](../plans/beating-docling.md) M5 · 状态：✅ 完成
> 结果：两个新 crate（docparse-docx / docparse-html）汇入同一 IR；标题/段落/列表/表格 → Markdown；确定性 20/20

---

## 1. 目标与决策

够到 Docling 的多格式广度。**OOXML/HTML 有显式结构、无需版面推断、ROI 最高**，且 `core` 阅读顺序/输出自动复用。依赖选型经用户确认走**高层库**：DOCX 用 `docx-rs`、HTML 用 `scraper`（html5ever）。

## 2. 实现

**共用基础**：新 `core::synth::PageBuilder`——把逻辑块（段落/标题/表格）按 PDF 约定**合成坐标**（612×792 页、72pt 边距、自上而下流式、超页分页）。坐标是诚实的合成物（用于相对顺序与引用锚点，不假装真实版面），让阅读顺序/输出层零改动复用。

| crate | 依赖 | 映射 |
|---|---|---|
| `docparse-docx` | docx-rs | `document.children`：Paragraph→段落（标题按 style 名 `Heading1`… 给字号）、Table→`ir::Table`（OOXML 表格显式，无需检测）|
| `docparse-html` | scraper + ego-tree | DOM 前序遍历：h1–h6→标题、p/li/blockquote→段落、table→`ir::Table`；script/style/head 丢弃；内联格式拍平为文本 |

CLI 注册表加两行（`DocxParser`/`HtmlParser`），`supports()` 按扩展名。

## 3. 顺带修复：标题检测改用众数

DOCX 短文档标题没被标 `##`：原 median 阈值被"多标题少正文"抬高。改 `layout::body_font_size` 为**众数**（最常见字号=正文，0.5pt 分箱，确定性 tie-break）——更稳健，PDF/DOCX/HTML 通用。改后 DOCX 标题正确 `##`，PDF 标题不回归（1901 仍 3 个）。

## 4. 验证

- **DOCX**（python-docx 造的 fixture，已提交 `tests/fixtures/sample.docx`）：标题→`##`、段落、3×2 表格→Markdown 表格；集成测试断言表头/单元格/provenance。
- **HTML**（内联串单测）：标题/段落/列表（`- `）/表格映射正确；script/style 丢弃。
- **确定性**：html + docx markdown 各 20/20 逐字节一致。
- **PDF 零回归**：lorem/1901/2408 chunk 数不变；bialetti +2 = 其 2 个 Table 元素入 IR（预期）。
- clippy 零 warning；单测 core 17 + pdf 14 + html 3 + docx 1 = **35**（起步 6）。

## 5. 已知限制

- **合成坐标非真实版面**：bbox 用于排序/引用，不反映原 DOCX/HTML 渲染几何。
- **内联格式拍平**：粗体/斜体/链接/代码样式丢失（只保文本）；图片、嵌套表、复杂列表层级未建模。
- **DOCX 列表**：编号/项目符号未加前缀（HTML `li` 加了 `- `）；DOCX numbering 属性未解析。
- **HTML CSS 布局**未考虑（display/float 等）——按 DOM 结构顺序，足够 RAG/文本用途。

## 6. 对记分牌（roadmap §6）

- **多格式广度持平**：PDF + DOCX + HTML 汇入同一带 provenance 的 IR，三种输出复用。Docling 的首选理由之一被够到。
- **MHS**：DOCX/HTML 标题层级有显式来源（style/标签），比 PDF 的字号推断更可靠。
- 加格式成本极低：`impl DocumentParser` + 注册一行 + 复用 synth/layout/output（印证 core 格式无关设计）。

## 7. 下一步

进入 **M6 RAG 切块 + chunk↔bbox 双向引用**（纯 core，无新依赖）——引用是相对 Docling 的杀手锏。
