> ## Documentation Index
> Fetch the complete documentation index at: https://www.hellomaggie.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Word 自动排版：一次复杂习题册排版系统的重构实录

> 从 HTML 重建到 OOXML 定点修改：word-format skill 如何在保留 MathType OLE 的前提下，把复杂双栏习题册的版式迁移做成可审计的工程流程。

## 一次复杂习题册排版系统的重构实录

自动套用 Word 模板，听起来像是一个格式复制问题：读取参考文档的字体、字号、行距和页边距，再把这些参数写到目标文档中。

如果文档只有几段文字，这个理解大致成立。但当输入变成一份真实习题册——横向页面、双栏、页眉页脚、二维码、浮动图片、制表位、上千个可编辑 MathType 公式——“复制格式”很快会变成“如何在不破坏文档的前提下重写它”。

我们为此尝试的 `word-format` skill，最初走的是一条直观路线：让 Word 把参考文档导出为 HTML，替换文字，再通过浏览器和剪贴板粘回 Word。它在少量内容生成上工作得很好，却在完整习题册上暴露了根本缺陷：页面看起来仍有公式，但原来的 `Equation.DSMT4` OLE 对象已经退化成 PNG；页眉、关系、双栏结构和嵌入对象也无法得到可靠保证。

最终，系统的主线从“重建一个看起来相似的文档”，转向“保留源 DOCX 容器，只修改经过确认的 OOXML 节点”。

这次重构带来的最大认识是：

> 复杂 Word 排版不是视觉参数的集合，而是一次受约束的文档结构迁移。

本文讲清楚这个结论是如何得到的、当前方案怎样工作，以及为什么“结构审计 + 全页渲染”缺一不可。

***

## 第一版为什么看起来合理

Word 自己能够把 DOCX 导出为 Filtered HTML。导出的页面保留了大量段落标签、类名和行内样式，因此最初的处理链路非常自然：

```text theme={null}
参考 DOCX
  → Word 导出 Filtered HTML
  → 复制同类段落并替换文字
  → Chrome / Edge 渲染
  → 复制到系统剪贴板
  → Word 粘贴并保存为 DOCX
```

这个方案有三个现实优势。

首先，格式解释工作交给了 Word 和浏览器，而不是自行实现一套排版引擎。其次，新增少量内容时，可以直接复用参考段落的字体、缩进和行距。最后，在“续写模式”中先复制原 DOCX，再把新内容粘到副本末尾，原文档的页面设置、样式表、主题和页眉页脚仍然存在。

Windows 版本还解决了几项自动化细节：使用 Word COM 导出和保存文档；通过隔离的浏览器 profile 找到本轮渲染窗口，避免误复制用户已有的 Chrome 页面；完成后只关闭脚本自己创建的 Word 和浏览器实例。

在“根据参考格式追加几段内容”这个问题上，HTML 路线至今仍有价值。真正的问题，是我们一度试图让它承担整篇复杂文档的重建。

***

## 一个仍能打开的 DOCX，也可能已经坏了

第一次处理含大量 MathType 公式的习题册时，输出文件可以打开，页面上也能看到公式。只看截图，转换似乎成功。

但双击公式后，MathType 无法再编辑它。检查 DOCX 内部结构才发现，Word 导出的 HTML 只保留了公式的预览图，浏览器剪贴板传回的也是图片。原来的 OLE 二进制对象没有穿过这条转换链。

这类失败危险之处在于，它不是明显的文件损坏，而是能力的静默退化：

* 公式仍然可见，但已经失去可编辑性；
* 标题看起来在正确位置，实际可能依赖错误的分页符；
* 页面样式大致相似，母题间距和选项对齐却被直接格式覆盖；
* OLE 数量可能没有显著异常，公式仍可能在页面中截断或重叠。

因此，“Word 能打开”和“第一页看起来正常”都不是合格的验收条件。

要理解为什么，必须先把 DOCX 当成它真正的样子：一个由 XML、关系文件、媒体和二进制对象共同构成的 ZIP 容器。

***

## DOCX 不是一份文本，而是一张关系网

一个典型 DOCX 可以简化为下面的结构：

```text theme={null}
document.docx
├─ [Content_Types].xml
├─ _rels/.rels
└─ word/
   ├─ document.xml
   ├─ styles.xml
   ├─ settings.xml
   ├─ header*.xml / footer*.xml
   ├─ _rels/document.xml.rels
   ├─ media/*
   └─ embeddings/*
```

正文段落和分栏符主要位于 `word/document.xml`，样式定义位于 `word/styles.xml`。图片和公式预览位于 `word/media`，可编辑 MathType 对象位于 `word/embeddings`，XML 中的 `r:id` 再通过 `.rels` 文件指向这些部件。

这意味着一个公式不是单独的 `<w:object>` 标签。它同时依赖正文中的对象节点、关系条目、OLE 二进制和预览资源。只复制 XML 标签而没有复制关系与部件，或让 HTML 把对象扁平化为图片，都会破坏完整性。

样式也比想象中复杂。Word 的最终视觉效果通常由两层叠加：

* `styles.xml` 中的可复用样式；
* 具体段落或 run 上的直接格式。

同一个“母题”样式，可能在首段、题间或特殊位置带有不同的段前间距；选项对齐可能依赖段落制表位；页码可能位于页脚的 VML 文本框。只复制样式名称，无法复现这些实际行为。

系统因此从“转换格式”转向一个更保守的问题：哪些节点必须改，哪些部件必须原样保留？

***

## 从模板截图到结构合同

在 M1 纯享版模板上，我们没有继续凭肉眼抄录几个字号，而是做了一次只读“模板蒸馏”：把模板拆成可解释、可测量、可执行的规则。

提取范围包括：

* 页面方向、尺寸、页边距和文档网格；
* 双栏数量、栏宽与栏间距；
* 页眉、页脚、页码域和其中的图片关系；
* “章节名称”“题型”“母题”“子题”“解析”等语义样式；
* 样式之外的段落间距、缩进和制表位覆盖；
* 图片、二维码、MathType/OLE 和关系部件；
* 页面级与结构级验收条件。

模板中有 1,569 个 `Equation.DSMT4` 对象。这个数字并不是排版参数，却是非常重要的结构基线：任何全量格式迁移，都不能让这些对象在不知情的情况下变成图片或消失。

蒸馏后的模板不再只是一个“看起来正确的 Word 文件”，而是一份结构合同：规定页面应该如何组织、段落语义如何映射、哪些直接格式需要保留或清理，以及输出必须通过哪些检查。

这一步也改变了实现方式。程序不再试图复制模板中的全部内容，而是从模板读取规则，把这些规则应用到源文档自己的内容和容器上。

***

## 当前架构：保留源容器，只改明确目标

当前 OOXML 主线可以概括为四个阶段：检查、迁移、审计和渲染。

```text theme={null}
模板 DOCX ──读取样式与页面结构──┐
                                ├─→ 定点修改源 DOCX 副本
源 DOCX ───枚举内容与对象基线───┘          │
                                           ▼
                              结构审计 → 全页渲染 → 视觉检查
```

### 1. 检查：记录处理前的结构基线

系统先读取模板中的自定义样式和公式对象数量，同时检查源文档的 `Equation.DSMT4`、OLE、媒体和主要段落结构。

这个阶段不修改文件。它要回答的是：模板实际提供了哪些语义样式？源文档包含多少需要保留的对象？当前文档是否符合现有分类规则？

如果模板没有预期的“题型、母题、解析”等语义，继续批量执行只会让错误扩大。自动化的第一道安全门不是“脚本能不能跑”，而是输入是否属于它理解的文档类型。

### 2. 迁移：修改 `document.xml` 和 `styles.xml`

`apply_exercise_template_ooxml.ps1` 打开模板和源 DOCX 的 ZIP 包，读取双方的 `word/document.xml` 与 `word/styles.xml`，然后执行几类受控变更：

* 把模板的页面几何与自定义样式克隆到源文档；
* 为克隆样式分配稳定的 `m1_*` ID，并修复 `basedOn`、`next` 和 `link` 引用；
* 根据文本和旧样式特征，将段落识别为章节、题型、母题、子题、解析或正文；
* 清理会压过模板的直接缩进、间距和加粗；
* 删除错误的显式分页符，并在规定的语义边界插入分栏符；
* 标准化独立公式段的 Tab、间距、缩进和对齐。

输出时，脚本从源 DOCX 复制全部 ZIP 条目，只为 `word/document.xml` 和 `word/styles.xml` 写入新内容。`word/embeddings`、`word/media`、关系文件、页眉页脚和其他部件继续来自源文档。

这不是“所有非目标字节都不变”的强哈希证明，因为 ZIP 条目会被重新压缩；它保证的是非目标部件的解压内容来自源包、目标 XML 的修改范围明确，并通过后续计数和结构审计检查关键对象是否保留。

写入先落到临时文件，完成后再移动为正式输出，避免失败时留下一个名称正确、内容不完整的 DOCX。

### 3. 审计：把排版规则变成可判定条件

脚本执行成功并不代表排版正确。`audit_exercise_template_ooxml.ps1` 会重新打开输出包，将关键要求转换为机器可以判断的条件，例如：

* 显式分页符应被清除；
* 分栏符数量应与题型、检测题和反馈通道边界匹配；
* 解析正文和 `Step n` 后续内容不能残留直接加粗；
* 普通正文不能继续携带异常的直接缩进；
* 独立公式段不能保留错误的定位 Tab；
* 可删除的真空段不能继续制造版面间隙；
* 与源文档相比，公式标记、OLE 部件和媒体部件数量不能异常变化。

任一条件失败，审计器返回非零状态。这样，“排版要求”不再只存在于人的经验里，而成为可重复执行的门禁。

### 4. 渲染：结构正确仍不等于页面正确

审计通过后，系统用 Word COM 将 DOCX 导出为 PDF，再通过 Windows PDF 渲染 API生成逐页 PNG、检查页和 JSON 报告。

页面检查关注的是机器计数很难发现的问题：

* 题型是否真正从下一栏顶部开始；
* 母题是否被不必要地拆开；
* 公式是否缺失、截断、重叠或偏移；
* 二维码和图片是否完整；
* 是否出现异常空白页；
* 解析正文是否因为 run 级粗体残留而整段变黑。

渲染工具给出的 `PASS` 只表示 PDF 和页面图片完整生成，不能代替逐页视觉检查。结构审计证明“文档内部符合规则”，全页渲染证明“Word 实际把它画对了”。两者回答的是不同问题。

***

## 三个看似局部、实际决定可靠性的细节

### 分页符和分栏符不是同一件事

双栏习题册中，新题型通常应从下一栏开始，而不是跳到下一页。源文档却可能使用 page break 强行制造位置，甚至把分页符藏在一个非空标题段的 run 中。

早期清理逻辑只删除“空白分页段”，因此漏掉了这些隐藏节点。当前实现直接查找所有显式 `<w:br w:type="page"/>`，删除断点但保留所在段落和文字；随后再根据“题型、检测题、检测题答案、反馈通道”等语义插入真正的 column break。

这个修复说明，Word 自动化不能只按段落外观推断结构。一个段落有文字，不代表其中没有控制页面流的隐藏节点。

### 粗体必须在 run 层处理

“解析：D”在人眼中是一段连续文字，在 OOXML 中却可能被拆成多个 `<w:r>`。如果直接给整段取消粗体，会把“解析：”标签的强调也删掉；如果只改段落样式，后续 run 上的直接 `w:b` 和 `w:bCs` 仍会压过样式。

当前实现先识别解析标签所在的 run，再只清理标签之后的直接粗体，并将处理范围延续到下一个语义标题。15 份解析版文档的批量纠偏，正是由这个细节推动完成的。

### 公式段不能只依赖样式名

`MTDisplayEquation` 表示独立公式段，但源样式可能带居中制表位，公式前还可能存在定位 Tab。直接复制样式会把错误位置一起保留下来。

当前规则在保留公式 OLE 本身的前提下，删除公式前的定位 Tab，将段前段后和缩进归零，并显式左对齐。与此同时，它不会全局删除 `xml:space="preserve"`，因为行内公式前后的保留空格可能正是正文与对象之间所需的分隔。

这里的原则是：按语义缩小修改范围。只处理确认属于独立公式段的几何属性，不对所有 Tab 和空格进行粗暴清洗。

***

## 两条路线，而不是一个万能转换器

OOXML 主线并没有让 HTML 路线失去价值。它只是把两类不同任务分开处理。

| 场景                        | 推荐路线               | 原因                             |
| ------------------------- | ------------------ | ------------------------------ |
| 已有双栏习题册统一版式               | OOXML 定点修改         | 需要保留源文档的 OLE、媒体、关系和复杂结构        |
| 在参考文档末尾追加少量文字或表格          | Filtered HTML + 续写 | 复用段落视觉格式更直接，原容器继续承载既有内容        |
| 用 HTML 重建含 MathType 的整篇文档 | 明确拒绝               | `Equation.DSMT4` 会退化为图片        |
| 修改 MathType 内部字号、间距和字体配置  | 独立 MathType 流水线    | 这是 MTEF 与预览重渲染问题，不属于 Word 版式迁移 |

为了防止误用，HTML 新建模式检测到公式数量标记后会停止，而不是静默输出一个“看起来正常、公式已经不可编辑”的文件。

***

## 从单文件试验到批量验证

架构是否可靠，最终要看它在真实文档上的表现。

M1 模板的只读提取记录了 1,569 个 `Equation.DSMT4` 对象，并形成了人工可读规范与机器可读规格。M18 源文档记录了 1,433 个公式对象，用于验证版式迁移前后的对象保留边界。

在后续批量任务中：

* 15 份解析版文档完成了 run 级粗体纠偏和容器结构核验；
* 9 份待续排文档逐文件执行结构审计，9/9 通过；
* 这 9 份文档共渲染 307 页并完成逐页检查，未发现公式缺失、截断、重叠或异常空白页。

这些结果并不意味着脚本适用于任意 Word 模板。当前语义分类仍明确面向“章节、题型、母题、子题、解析、检测题、反馈通道”这一类习题册结构。

它们真正证明的是另一件事：当处理流程固定为“单文件试跑 → 结构审计 → 全页渲染 → 再批量”，复杂文档排版可以从一次性的人工操作，变成可重复验证的工程过程。

***

## 这次重构留下的工程原则

第一，先定义不变量，再讨论如何修改。对于习题册，不变量包括正文内容、公式可编辑性、OLE 与媒体部件、关系网络和未授权的用户文件。没有这些边界，“格式迁移”很容易变成不可控的文档重建。

第二，模板应该被蒸馏为规则，而不是只作为视觉参考。样式、直接格式、页面几何和语义槽位都应被记录；否则每一次套版都要重新猜测。

第三，最小修改面比万能转换更可信。保留源容器，只修改 `document.xml` 和 `styles.xml` 中确认过的节点，虽然需要理解 OOXML，却显著降低了公式、图片和关系被转换的风险。

第四，自动审计与视觉检查必须并存。对象计数、断点和直接格式适合机器验证；重叠、截断、错栏和页面节奏仍需要完整渲染来发现。

第五，把失败经验固化成门禁。公式退化为 PNG 后，系统增加了 HTML 模式保护；隐藏分页符导致空白页后，清理范围扩展到 run 内部；解析正文误加粗后，处理粒度下沉到 run。可靠性不是一次设计出来的，而是把每个真实故障转化为规则、测试和拒绝条件。

***

## 结语

`word-format` 最初解决的是“怎样把参考 Word 的格式复制过来”。真实文档迫使我们换了一个问题：怎样在改变版式的同时，证明复杂 DOCX 中不该改变的东西仍然存在？

这个问题的答案不再是一条 HTML 转换命令，而是一套分层方法：把模板蒸馏成结构合同，以源 DOCX 为容器做定点 OOXML 修改，用自动审计证明关键约束，再用全页渲染检查 Word 的真实输出。

自动化处理复杂文档，最有价值的能力不是“生成一个能打开的文件”，而是能够清楚说明：修改发生在哪里，哪些结构被保留，以及结果通过了什么证据。
