云计算百科
云计算领域专业知识百科平台

舞台工艺审图交付物的真实页码从哪来?python-docx 算不出,WorkBuddy 渲染闸门来补

舞台工艺审图交付物的真实页码从哪来?python-docx 算不出,WorkBuddy 渲染闸门来补

舞台工艺审图交付物(齐套性报告九节)要求页码连续、黑表线、宋体——这些是可机器校验项。但用 python-docx 批量生成时,第一个坑就让人懵:它根本算不出"第几页"。本文讲清为什么会这样,并给出 3 种能落地的补位方案,其中字段注入方案已实测生成合规 docx。

一、python-docx 为什么算不出页码

python-docx 本质是对 OOXML(Word 的底层 XML 结构)的读写器。它没有排版引擎,不知道一页能排多少字、哪里会换行分页,所以"第几页"这种渲染后才存在的信息,在生成阶段就拿不到。

新手常踩的三个误区:

  • 用 len(doc.paragraphs) 当页数 → 那是段落数,不是页数;
  • 想从 doc.core_properties 里找页数 → 该对象根本没有 page_count 字段;
  • 遍历表格数行数 → 同样和"页"没有对应关系。

结论很直接:页码、目录、总页数这类"渲染后才有"的信息,python-docx 生成阶段拿不到,必须靠"字段"或"渲染"两条路补。这跟它能不能改文字、插表格无关——那是结构层的事,排版是另一层。

二、方案 A:字段注入,让 Word 自己填(已实测)

OOXML 里 PAGE / NUMPAGES 是"简单字段",目录 TOC 是"复杂字段"。python-docx 没给现成 API,但可以直接改底层 XML 把字段指令写进去,Word/WPS 打开时按实际布局填充真实值。

核心 helper(注入 PAGE/NUMPAGES 到页脚、注入 TOC 到正文):

from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

def add_simple_field(run, instr):
"""插入简单字段(PAGE / NUMPAGES 等单值字段)。"""
fld = OxmlElement("w:fldSimple")
fld.set(qn("w:instr"), instr)
run._r.addprevious(fld)

def add_complex_field(run, instr, placeholder=""):
"""插入复杂字段(TOC 等需要 begin/separate/end 三段)。"""
for _ in ("begin", "separate", "end"):
fc = OxmlElement("w:fldChar")
fc.set(qn("w:fldCharType"), _)
run._r.append(fc)
begin = run._r.find(f'.//{qn("w:fldChar")}')
instr_el = OxmlElement("w:instrText")
instr_el.set(qn("xml:space"), "preserve")
instr_el.text = instr
begin.addnext(instr_el)
if placeholder:
sep = run._r.find(f'.//{qn("w:fldChar")}[2]')
t = OxmlElement("w:t")
t.text = placeholder
sep.addnext(t)

def build(path):
doc = Document()
p = doc.add_paragraph()
r = p.add_run("目录(在 Word/WPS 中右键「更新域」即生成)")
add_complex_field(r, 'TOC \\\\o "1-3" \\\\h \\\\z \\\\u', "【右键更新域以生成目录】")

for ch in range(1, 5):
doc.add_heading(f"第 {ch} 章 舞台工艺审图交付物要点", level=1)
for sub in range(1, 4):
doc.add_heading(f" {ch}.{sub} 子节说明", level=2)
for _ in range(40):
doc.add_paragraph(
"舞台工艺审核交付物须含齐套性报告九节结构,任一节缺失即视为不完整;"
"黑表线、宋体、页码连续是机器闸门的可校验项。"
)

footer = doc.sections[0].footer.paragraphs[0]
footer.alignment = 2 # 右对齐
fr = footer.add_run("第 ")
add_simple_field(fr, "PAGE")
fr2 = footer.add_run(" 页 / 共 ")
add_simple_field(fr2, "NUMPAGES")
footer.add_run(" 页")
doc.save(path)

实测结果:生成的 docx 在页脚 part 含 w:fldSimple w:instr="PAGE" 与 NUMPAGES,正文含 TOC 指令;用 Word/WPS 打开即显示真实"第 X 页 / 共 Y 页"并生成目录(右键"更新域"即可)。

注意:python-docx 自己读不到字段值(它不渲染),这是预期行为,不是 bug。要让程序打开即自动更新,可在 settings.xml 里加 <w:updateFields w:val="true"/>,或发布前在 Word 里手动更新一次。

三、方案 B:先渲染成 PDF 再读真实页码(程序内要拿到整数)

方案 A 解决了"显示正确",但闸门要在程序里断言"共 N 页""页码连续"时,不能依赖人工打开 Word 更新。这时走渲染链:

import subprocess, pdfplumber

def real_page_count(docx_path):
pdf = docx_path.with_suffix(".pdf")
# 用 LibreOffice 无头模式转 PDF(本地 CI 或带桌面环境的机器)
subprocess.run(
["soffice", "–headless", "–convert-to", "pdf", str(docx_path)],
check=True, cwd=str(docx_path.parent),
)
with pdfplumber.open(pdf) as pdf_doc:
return len(pdf_doc.pages) # 真实整数页数

def check_page_numbers(docx_path):
with pdfplumber.open(docx_path.with_suffix(".pdf")) as pdf_doc:
for i, page in enumerate(pdf_doc.pages, 1):
text = page.extract_text() or ""
assert f"第 {i} 页" in text, f"第 {i} 页页码缺失"
return True

这一步依赖外部环境(LibreOffice 或 Word),沙箱里没有就只能在本地跑,但逻辑是稳的。好处是能拿到确切整数页码,直接做断言,而不只是"显示给别人看"。

四、方案 C:把"渲染"做成闸门——黑表线真伪验证

呼应一个真实坑:python-docx 里 w:color 被 themeColor 覆盖时,表线在 Word 里显示成蓝色,但 XML 看似黑色。光看 XML 判断不了,批次一多就会漏。

解法:把 docx 渲染成图像(同方案 B 的渲染链),逐表比对边框像素是否纯黑 #000000,把"黑表线"从口头约定变成机器断言:

from PIL import Image

def assert_black_borders(page_image: Image.Image, table_bbox):
"""裁剪表格区域,断言四边像素为纯黑(非 themeColor 蓝)。"""
crop = page_image.crop(table_bbox)
px = crop.load()
w, h = crop.size
edges = (
[px[x, 0] for x in range(w)] + [px[x, h – 1] for x in range(w)] +
[px[0, y] for y in range(h)] + [px[w – 1, y] for y in range(h)]
)
for r, g, b in edges:
assert (r, g, b) == (0, 0, 0), f"检测到非黑边框像素 ({r},{g},{b}),疑似 themeColor 蓝线"
return True

这就是"渲染闸门":python-docx 管结构,渲染层管外观,两层都过才放行。

五、三个方案怎么选

  • 只要"显示页码 / 目录"给读者看 → 方案 A 最轻,零外部依赖;
  • 要"程序内拿到整数页码做断言" → 方案 B;
  • 要"校验渲染层外观(黑线 / 字体 / 版式)" → 方案 C。

舞台工艺交付物闸门的组合:A 负责显示正确,C 负责外观正确,B 负责数值可断言。三者配合,生成阶段和渲染阶段不再是两本账。

六、结语

python-docx 的边界(不排版)决定了页码 / 目录 / TOC 必须靠字段或渲染补位。把这套补位固化成可校验闸门,审图交付物才敢签字——结构正确不等于渲染正确,两层都过才是真交付。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 舞台工艺审图交付物的真实页码从哪来?python-docx 算不出,WorkBuddy 渲染闸门来补
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!