DOCX 的创建、编辑和分析
.docx 文件是 XML 文件的 ZIP 压缩包。根据任务选择合适的处理方式:
| 任务 | 处理方式 |
|---|---|
| 创建新文档 | 编写 docx (npm) 脚本 —— 请参见下文注意事项 |
| 编辑现有文档 | unzip → 编辑 word/document.xml → zip (docx-js 无法打开现有文件) |
| 读取内容 | pandoc -t markdown file.docx |
以下脚本路径均相对于此技能所在目录。
使用 docx-js 创建文档 —— 注意事项
docx 已预安装 —— 请勿先运行 npm install;直接编写脚本并使用 require('docx')。仅当 require 失败时:npm install docx。模型已了解该 API;以下是一些易错点:
- 页面大小默认为 A4。 如需美国信纸尺寸,请设置
page: { size: { width: 12240, height: 15840 } }(DXA;1440 = 1 英寸)。 - 横向: 传递纵向尺寸并设置
orientation: PageOrientation.LANDSCAPE—— docx-js 会在内部自动交换宽度/高度。 - 表格需要双重宽度: 在表格上设置
columnWidths,同时在每个单元格上设置width,两者均使用WidthType.DXA单位(PERCENTAGE会在 Google Docs 中出错)。各列宽度之和必须等于表格宽度。 - 表格阴影: 使用
ShadingType.CLEAR,切勿使用SOLID(会导致渲染为黑色)。 - 列表: 绝不要直接插入
•字符;应使用带有LevelFormat.BULLET的numbering配置。 ImageRun必须指定type: ("png","jpg", …)。PageBreak必须包含在Paragraph内部。- 绝不要使用
\n—— 应使用独立的Paragraph元素。 - 目录: 标题必须使用内置的
HeadingLevel.*;自定义标题样式需要设置outlineLevel,否则不会出现在目录中。 - 不要使用表格作为水平线 —— 应改用段落的底部边框。
- 点前导符 / 同一行右对齐: 使用
PositionalTab(alignment: PositionalTabAlignment.RIGHT,leader: PositionalTabLeader.DOT)包含在TextRun内部,不要使用文字.或空格填充。
验证输出
写入 .docx 文件后,渲染并查看它:
python scripts/office/soffice.py --headless --convert-to pdf output.docx
pdftoppm -jpeg -r 100 output.pdf page
ls page-*.jpg # 然后查看这些图片
pdftoppm 会用页数的位数对页码进行补零(如 page-01.jpg … page-12.jpg)。
编辑现有文档
旧版 .doc 文件必须首先转换:python scripts/office/soffice.py --headless --convert-to docx file.doc。
unzip -q doc.docx -d unpacked/
find unpacked -type l -delete # 删除符号链接条目 —— 来自外部的 docx 文件不可信
python scripts/merge_runs.py unpacked/ # 合并零散的运行节点,使文本可查找
# 在原地编辑 unpacked/word/document.xml —— 不要重新格式化或美化
(cd unpacked && rm -f ../out.docx && zip -Xr ../out.docx .)
python scripts/office/validate.py out.docx --original doc.docx # XSD 检查;--auto-repair 修复常见问题
# 需要修订痕迹?添加 --author "<您做修订时使用的名称>" 来检查每次编辑是否都被追踪
Word 会将文本分割到多个 <w:r> 运行节点中(由于修订 ID、拼写检查标记等),因此您在文档中看到的短语在 XML 中通常不是一个连续的字符串。merge_runs.py 脚本可以在不改变内容或渲染的前提下,将 word/document.xml 中相邻且格式相同的运行节点合并;它也可以直接处理 .docx 文件(python scripts/merge_runs.py doc.docx -o merged.docx)。
修订痕迹: 进行修订时,使用 --author "<您做修订时使用的名称>"(需要 --original 参数)进行验证 —— 它会报告您修改了但周围没有 <w:ins>/<w:del> 标记的文本。这种情况容易意外发生,且在“接受”视图中不可见。将运行节点包裹在带有 w:id、w:author、w:date 属性的 <w:ins>/<w:del> 标签内。在 <w:del> 内部,文本元素不是 <w:t>,而是 <w:delText>。一个被删除的段落标记(<w:pPr><w:rPr><w:del w:id=".." w:author=".." w:date=".."/></w:rPr></w:pPr>)意味着“将此段落合并到下一个段落”——因此直接删除一个段落就是这样做,再加上在每个运行节点周围包裹 <w:del>。<w:del/> 必须出现在 rPr 的其他子元素之前;它们的顺序由模式强制规定。
要生成一个接受所有修订痕迹的干净副本:python scripts/accept_changes.py in.docx out.docx。
接受一个被删除的段落标记应该将该段落与它下面的段落合并,因此所有运行节点都被删除的段落就会消失。Word 会这样做;而 accept_changes.py 和 pandoc --track-changes=accept 并不总是能做到。两者同样失败 —— 它们会删掉被删除的文本,但会留下空段落,当它是自动编号的段时,就会显示为一个多余的空白项目符号:
pandoc --track-changes=accept从不合并段落。accept_changes.py(LibreOffice) 可以正确合并它们,除非删除的段落后面跟着一个空的间隔段落。
在任一视图中的空白项目符号都是该视图的产物,并非文档的缺陷。请在 XML 中检查段落删除。
批注
批注需要六个相互关联的文件。使用辅助脚本 —— 当您也要编辑 document.xml 时使用目录模式(可节省解压/重新压缩的周期),否则使用 .docx 直接模式:
# 针对已解压的目录(当也需要放置标记时推荐使用)
python scripts/comment.py unpacked/ "费用与支出上限太低"
python scripts/comment.py unpacked/ "同意" --parent 0
# 直接针对 .docx 文件
python scripts/comment.py contract.docx "此上限太低" -o annotated.docx
该脚本会写入 comments.xml、commentsExtended.xml、commentsIds.xml、commentsExtensible.xml、关系文件以及内容类型覆盖文件。批注 ID 会自动分配。然后它会打印要添加到 word/document.xml 的 <w:commentRangeStart>/<w:commentRangeEnd>/<w:commentReference> 代码片段,以便将批注锚定到特定文本 —— 在放置这些标记之前,批注存在但不可见。
依赖项
docx (npm,已预装 —— 仅当 require('docx') 失败时安装) · pandoc · LibreOffice (soffice) · pdftoppm (Poppler)