直读 OOXML 而不用现成库:需求文档解析的取舍
今年的项目:客户的需求文档是 Word,要解析成需求条目树,写进项目管理工具,再让模型按条目生成测试用例。听起来解析 Word 是最简单的一步,实际是最花时间的一步。
现成库的问题
先用了最常见的 Python Word 库。跑了几份文档就发现三个拿不到的东西:
- 自动编号。 文档里的”3.2.1”很多是 Word 自动编号,库读出来的段落文本没有编号,只有”功能描述”四个字。层级信息全在编号里,没了编号就没了层级。
- 合并单元格。 需求表格大量用合并单元格表示分组。库读表格是按行列网格读的,合并的格子内容重复出现,结构对不上。
- 目录域。 有些文档的目录是 Word 自动生成的域,里面其实藏着完整的标题层级,库把它当普通文本。
三个问题都要绕到库的底层去改,不如直接读底层。
直读 OOXML
Word 文档就是个 zip,里面 document.xml 是正文,numbering.xml 是编号定义,styles.xml 是样式。用 lxml 直接解析,想要什么就取什么。
代价是要自己处理很多细节:编号的层级定义和实例引用是分开的,得关联起来算出实际编号;合并单元格要读 gridSpan 和 vMerge;样式有继承链。这些都不难,就是多。
标题识别四个通道
标题是条目树的骨架,识别错了整棵树就错。单靠一个信号不够,做了四个通道:
| 通道 | 信号 | 可靠性 |
|---|---|---|
| 样式 | 段落样式是 Heading 1/2/3 | 高,但很多文档不用标题样式 |
| 编号域 | 自动编号的层级 | 高,但手打编号识别不到 |
| 目录 | 目录域里的条目 | 高,但目录可能过期 |
| 版式 | 字号、加粗、缩进 | 低,兜底 |
四个通道各出一份候选,按可靠性加权合并。冲突时高可靠通道优先,但一个通道单独判定的标题要有另一个通道佐证才采信。
自检页
解析完不直接入库,先生成一个自检页:左边原文,右边解析出的树,对应关系用颜色标。客户的需求工程师扫一遍,点确认才入库。
这一步是从图纸审核项目里的规则草稿箱学来的,原则一样:模型或规则的输出进正式流程前过一道人。
入库幂等
条目写入项目管理工具是逐条调接口,几百条要跑几分钟,中途可能断。做法是每写一条就在本地落一条进度记录,重跑时跳过已写的。同一份文档解析两次,不会建出两套工作项。
数字
后端一万三千多行,其中 OOXML 解析相关的将近一半。272 个测试用例,大部分是拿真实文档的片段做的回归。
值不值
直读 OOXML 多花了大概三周。换来的是:客户送来的文档不管怎么排版,基本都能解,解不了的在自检页一眼能看出来。用现成库的话,那三周会变成后面每个月的”这份文档又解不了”。
文档解析这种活,前期多投入是划算的,因为文档的花样永远比你想的多。