公文格式skill
本 skill 用于将 Word 文档按《党政机关公文格式(GB/T 9704-2012)》及配套操作指南调整为目标格式:方正小标宋简体二号居中标题、仿宋_GB2312 三号首行缩进 2 字符正文、Word 页边距上 3.35 / 下 3.25 / 左 2.7 / 右 2.5 厘米、文档网格"只指定行网格"每页 22 行,最终打印成品满足每页 22 行 × 每行 26 字。
适用范围:Office 2013 及以上版本(演示基于 Office 2013 + 佳能 C3530 打印机;其他 Word 版本或打印机可结合实际微调页面设置,保证打印件达到 GB/T 9704-2012 页面标准)。
何时触发本 skill
- 用户给出 Word 文档,要求按公文格式(GB/T 9704-2012)调整排版。
- 用户给出 docx 草稿,要求"调成公文格式""规范正文""公文标题字体改正""按每页 22 行格式排版"。
- 用户要求批量校验/批量调整多份公文是否符合格式。
- 用户要求生成符合公文格式的 docx 模板或样张。
不适用:纯学术论文、合同、报告、PPT、Excel。公文之外的 Word 排版走通用排版思路。
核心规范速查表(必读)
| 项目 | 设置项 | 取值 |
|---|---|---|
| 标题 | 中文字体 | 方正小标宋简体 |
| 字号 | 二号(约 22pt) | |
| 字符间距 | 加宽 0.5 磅(字体对话框 → 高级) | |
| 对齐方式 | 居中 | |
| 行距 | 最小值 29.5 磅 | |
| 段落选项 | 取消勾选"如果定义了文档网格,则对齐到网格" | |
| 正文(结构层次标题除外) | 中文字体 | 仿宋_GB2312 |
| 字号 | 三号(约 16pt) | |
| 字符间距 | 加宽 0.5 磅(字体对话框 → 高级) | |
| 对齐方式 | 两端对齐 | |
| 特殊格式 | 首行缩进 2 字符 | |
| 段落选项 | 取消勾选"如果定义了文档网格,则自动调整右缩进" | |
| 行距 | 最小值 29.5 磅 | |
| 段落选项 | 取消勾选"如果定义了文档网格,则对齐到网格" | |
| 页面(Word 中设置) | 上边距 | 3.35 厘米 |
| 下边距 | 3.25 厘米 | |
| 左边距(订口) | 2.7 厘米 | |
| 右边距(翻口) | 2.5 厘米 | |
| 文档网格 | "只指定行网格" | |
| 每页行数 | 22 行 | |
| 成品目标 | 上白边(天头) | 3.7 ~ 3.8 厘米 |
| 下白边(地脚) | 3.5 厘米 | |
| 左白边 | 2.7 厘米 | |
| 右白边 | 2.7 厘米 | |
| 每页行数 | 22 行 | |
| 每行字数 | 26 字 | |
| 适用标准 | GB/T 9704-2012 |
> 上述"Word 中设置值"是输入参数,"成品目标"是打印后实际要求(GB/T 9704-2012 印刷标准)。Word → 打印存在微小偏差属正常,以打印件的实际效果为准;打印机型号/装订方式不同时,应微调 Word 页边距直至打印成品达到目标。
Word 中按规范调整格式的标准步骤(指南复刻)
1. 标题
- 选中文档标题全部文字。
- 字体对话框(开始 → 字体组右下角小箭头):中文字体 = 方正小标宋简体;字号 = 二号。
- 字体对话框 → 高级:字符间距 = 加宽 0.5 磅 → 确定。
- 段落对话框(开始 → 段落组右下角小箭头):
- 对齐方式 = 居中。
- 行距 = 最小值 29.5 磅。
- 取消勾选"如果定义了文档网格,则对齐到网格"。
2. 正文(结构层次标题除外)
- 选中正文全部文字(结构层次标题如"一、(一)1.(1)"按组织规定或 GB/T 9704-2012 单独处理)。
- 字体对话框:中文字体 = 仿宋_GB2312;字号 = 三号。
- 字体对话框 → 高级:字符间距 = 加宽 0.5 磅 → 确定。
- 段落对话框:
- 对齐方式 = 两端对齐。
- 特殊格式 = 首行缩进 2 字符。
- 取消勾选"如果定义了文档网格,则自动调整右缩进"。
- 行距 = 最小值 29.5 磅。
- 取消勾选"如果定义了文档网格,则对齐到网格"。
3. 页边距与文档网格
- 布局 → 页面设置组右下角小箭头,打开"页面设置"对话框。
- "页边距"选项卡:
- 上 = 3.35 厘米
- 下 = 3.25 厘米
- 左 = 2.7 厘米
- 右 = 2.5 厘米 - "文档网格"选项卡:
- 网格 = 只指定行网格
- 每页 = 22 行 - 单击"确定"。
4. 成品校验
将文件用本单位打印机(演示用佳能 C3530)打印后核对:
- 每页 22 行,每行 26 字。
- 版心四周白边近似 上 3.7-3.8 / 下 3.5 / 左 2.7 / 右 2.7 厘米。
- 标题方正小标宋二号居中,正文仿宋_GB2312 三号首行缩进 2 字符。
白边未达标时,微调 Word 页边距(一般上下±0.1 ~ 0.2 厘米即可)。
用 python-docx 套用规范(自动排版 / 批量处理)
当用户提供 docx 文件并希望自动调整,或需要批量处理时,按下述规范在脚本中设置 OOXML/docx 属性。
关键属性映射
| 规范项 | python-docx / OOXML 设置 |
|---|---|
| 中文字体(方正小标宋简体 / 仿宋_GB2312) | run.font.name 设置西文/亚洲字体名同值;通过 XML 直接设 w:rFonts w:eastAsia=... w:ascii=... w:hAnsi=...,确保中文生效 |
| 字号二号 | Pt(22)(约二号) |
| 字号三号 | Pt(16)(约三号) |
| 字符间距加宽 0.5 磅 | OOXML <w:spacing w:val="5"/>(半磅单位,加宽 5 = 0.5pt) |
| 行距最小值 29.5 磅 | OOXML <w:spacing w:lineRule="exact|atLeast" w:line="472"/> 25 磅以下用 atLeast;演示指南用 atLeast 29.5pt(即 <w:spacing w:line="472" w:lineRule="atLeast"/>) |
| 段落居中 | paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER |
| 段落两端对齐 | paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY |
| 首行缩进 2 字符 | paragraph.paragraph_format.first_line_indent = Cm(... );以三号 16pt 字号计 2 字符约 32pt ≈ 1.13 cm。或直接以"字符"为单位:<w:ind w:firstLineChars="200" w:firstLine="0"/>(200 = 2 字符 × 100) |
| 上 / 下 / 左 / 右 页边距 | section.top_margin = Cm(3.35) / Cm(3.25) / Cm(2.7) / Cm(2.5) |
| 文档网格"只指定行网格" + 每页 22 行 | OOXML <w:docGrid w:type="lines" w:linePitch="..."/> 与 <w:pgSz> 配合;python-docx 不直接暴露,需 section._sectPr 注入 XML |
参考代码片段(标题段落)
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.oxml.ns import qn
doc = Document()
title = doc.add_paragraph()
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
run = title.add_run("关于××××××××××××××的通知")
run.font.size = Pt(22) # 二号
run.font.name = "方正小标宋简体"
# 关键:让中文字体(eastAsia)也使用方正小标宋简体
rpr = run._element.get_or_add_rPr()
rfonts = rpr.find(qn("w:rFonts"))
if rfonts is None:
rfonts = rpr.makeelement(qn("w:rFonts"), {})
rpr.insert(0, rfonts)
rfonts.set(qn("w:ascii"), "方正小标宋简体")
rfonts.set(qn("w:hAnsi"), "方正小标宋简体")
rfonts.set(qn("w:eastAsia"), "方正小标宋简体")
rfonts.set(qn("w:cs"), "方正小标宋简体")
# 字符间距加宽 0.5 磅
spacing = rpr.makeelement(qn("w:spacing"), {"w:val": "5"})
rpr.append(spacing)
# 行距最小值 29.5 磅 + 取消对齐到文档网格
ppr = title._element.get_or_add_pPr()
spacing_p = ppr.makeelement(
qn("w:spacing"),
{"w:line": "472", "w:lineRule": "atLeast"},
)
ppr.append(spacing_p)
参考代码片段(正文段落)
body = doc.add_paragraph()
body.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
run = body.add_run("正文内容……")
run.font.size = Pt(16) # 三号
run.font.name = "仿宋_GB2312"
rpr = run._element.get_or_add_rPr()
rfonts = rpr.makeelement(qn("w:rFonts"), {})
for k in ("w:ascii", "w:hAnsi", "w:eastAsia", "w:cs"):
rfonts.set(qn(k), "仿宋_GB2312")
rpr.insert(0, rfonts)
# 字符间距加宽 0.5 磅
rpr.append(rpr.makeelement(qn("w:spacing"), {"w:val": "5"}))
# 首行缩进 2 字符(firstLineChars=200 = 2 字符)
ppr = body._element.get_or_add_pPr()
ppr.append(ppr.makeelement(qn("w:ind"),
{"w:firstLineChars": "200", "w:firstLine": "0"}))
# 行距最小值 29.5 磅
ppr.append(ppr.makeelement(qn("w:spacing"),
{"w:line": "472", "w:lineRule": "atLeast"}))
参考代码片段(页面设置:页边距 + 文档网格)
from docx.shared import Cm
section = doc.sections[0]
section.top_margin = Cm(3.35)
section.bottom_margin = Cm(3.25)
section.left_margin = Cm(2.7)
section.right_margin = Cm(2.5)
# 文档网格:只指定行网格(OOXML 直接注入)
sect_pr = section._sectPr
doc_grid = sect_pr.find(qn("w:docGrid"))
if doc_grid is None:
doc_grid = sect_pr.makeelement(qn("w:docGrid"), {})
sect_pr.append(doc_grid)
doc_grid.set(qn("w:type"), "lines")
doc_grid.set(qn("w:linePitch"), "595") # 版心高 23.1cm ÷ 22 行 ≈ 595 twips/行(推荐值)
doc_grid.set(qn("w:charSpace"), "0")
> 字符间距的 w:val 单位 = 1/100 磅,"加宽 0.5 磅"对应 w:val=5。行距的 w:line 单位 = 1/20 磅,最小值 29.5 磅对应 w:line=590(lineRule="atLeast"),本 skill 实际采用 w:line="590"。
>
> linePitch 按版心高度反算:A4 高 29.7cm − 上 3.35 − 下 3.25 = 23.1cm ≈ 6544 twips(1cm ≈ 567 twips),6544 ÷ 22 行 ≈ 595 twips/行。用 595 可让 Word 网格与实际行距自洽。
校验清单(交付前必查)
完成排版后,按下列清单逐项确认;任意一项不符,先调整再交付。
- [ ] 标题字体 = 方正小标宋简体,字号 = 二号
- [ ] 标题字符间距 = 加宽 0.5 磅
- [ ] 标题居中
- [ ] 标题行距 = 最小值 29.5 磅
- [ ] 正文(除结构层次标题外)字体 = 仿宋_GB2312,字号 = 三号
- [ ] 正文字符间距 = 加宽 0.5 磅
- [ ] 正文两端对齐,首行缩进 2 字符
- [ ] 正文行距 = 最小值 29.5 磅
- [ ] 上页边距 3.35 cm、下页边距 3.25 cm、左页边距 2.7 cm、右页边距 2.5 cm
- [ ] 文档网格 = 只指定行网格,每页 22 行
- [ ] 打印成品满足:每页 22 行 × 每行 26 字,四周白边近似 上 3.7-3.8 / 下 3.5 / 左右 2.7 厘米
表格格式调整(常见追加需求)
公文中表格常需与正文区分,常见追加要求:表格内字号改小四(12pt)、表格中文字前面不要有空白。
诊断先行(避免误判)
"文字前面有空白"有四类来源,处理前必须逐一定位,不要凭猜测改。按隐蔽程度从低到高排查:
| # | 来源 | 检查方法 | 正确处理 |
|---|---|---|---|
| 1 | 真实前导空白字符 | 遍历 cell.paragraphs[].text,看首字符是否为 ' ' / '\u3000' / '\t' / '\u00a0' |
run.text = run.text.lstrip(...),只处理段首 run |
| 2 | 段落级缩进 | 查 w:pPr/w:ind 是否存在 |
显式写 0(见下,不要删除元素) |
| 3 | 样式继承缩进(最隐蔽、最易漏) | 段落级无 w:ind 时,检查 Normal 样式的 w:pPr/w:ind、docDefaults/pPrDefault、表格样式 |
在段落级显式写 w:ind 各项=0 覆盖继承;或在样式层清理 |
| 4 | 单元格左内边距 | 查 w:tblPr/w:tblCellMar/w:left(表格级)与 w:tcPr/w:tcMar(单元格级,优先级更高);Word 默认 108 dxa(≈0.19cm) |
把 w:w 置 0 |
> ⚠️ 最容易踩的坑:直接把段落级的 w:ind 删除(ppr.remove(ind))看似"清掉缩进",实则会让段落回落到样式继承——若 Normal 样式定义了 firstLineChars=200(首行缩进 2 字符,很多从 Markdown/网页转换来的文档都有),删除后文字反而凭空多出 2 字符缩进。必须显式写 0,不能删。
>
> ⚠️ 另一个坑:w:firstLineChars 的优先级高于 w:firstLine。若样式继承的是 firstLineChars=200,只写 w:firstLine="0" 不生效,必须同时写 w:firstLineChars="0"。
>
> 实测经验:正文类文档表格通常没有前导空格字符;视觉上的"空格"绝大多数来自来源 3(样式继承缩进)和来源 4(单元格内边距)。先用脚本把四类各统计一遍,再动手。
样式继承缩进的排查代码:
import re
# Normal 样式
print(re.search(r'<w:pPr>.*?</w:pPr>', doc.styles['Normal'].element.xml, re.S).group(0))
# docDefaults
print(re.search(r'<w:pPrDefault>.*?</w:pPrDefault>', doc.styles.element.xml, re.S))
# 所有含 w:ind 的样式
for st in doc.styles:
x = st.element.xml
if '<w:ind ' in x:
print(st.name, re.findall(r'<w:ind[^>]*/>', x))
# 表格样式
for t in doc.tables:
s = t._tbl.tblPr.find(qn('w:tblStyle'))
print(s.get(qn('w:val')) if s is not None else '无')
关键代码
from docx.shared import Pt
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
SIZE_TABLE = Pt(12) # 小四
def set_run_size(run, size):
"""字号双写 w:sz / w:szCs,保证中英文均生效。"""
run.font.size = size
rpr = run._element.get_or_add_rPr()
for tag in ('w:sz', 'w:szCs'):
el = rpr.find(qn(tag))
if el is None:
el = OxmlElement(tag)
rpr.append(el)
el.set(qn('w:val'), str(int(size.pt * 2))) # 半磅单位
def set_cell_margin_left_zero(table):
"""去掉单元格左内边距(消除文字前的视觉空白)。"""
tblPr = table._tbl.tblPr
cm = tblPr.find(qn('w:tblCellMar'))
if cm is None:
cm = OxmlElement('w:tblCellMar')
tblPr.append(cm)
left = cm.find(qn('w:left'))
if left is None:
left = OxmlElement('w:left')
cm.append(left)
left.set(qn('w:w'), '0')
left.set(qn('w:type'), 'dxa')
def zero_indent(para):
"""表格段落显式清零缩进 —— 覆盖 Normal 样式继承的 firstLineChars=200。
⚠️ 必须是"写 0"而非"删元素":删掉 w:ind 会让段落回落到样式继承。"""
ppr = para._element.get_or_add_pPr()
ind = ppr.find(qn('w:ind'))
if ind is None:
ind = OxmlElement('w:ind')
# pPr schema 顺序:... snapToGrid → spacing → ind → contextualSpacing → ... → jc
jc = ppr.find(qn('w:jc'))
if jc is not None:
jc.addprevious(ind)
else:
sp = ppr.find(qn('w:spacing'))
if sp is not None:
sp.addnext(ind)
else:
ppr.append(ind)
# firstLineChars 优先级高于 firstLine,两者都要写 0
for k in ('w:left', 'w:leftChars', 'w:firstLine', 'w:firstLineChars'):
ind.set(qn(k), '0')
def strip_leading_ws(para):
"""清除段首空白字符;只动段首 run,不误删段中 run 的前导空白。"""
removed = 0
for run in para.runs:
if not run.text:
continue
stripped = run.text.lstrip(' \u3000\t\u00a0')
if stripped != run.text:
removed += len(run.text) - len(stripped)
run.text = stripped
if stripped: # 已有实际内容 → 段首处理完毕
break
return removed
> 字号对照:小四 = 12pt,三号 = 16pt,四号 = 14pt,五号 = 10.5pt。
>
> 注意:改动表格字号后,表格行距若仍为"最小值 29.5 磅"(公文正文标准),小四字号配此值会明显偏松。若用户未明确要求,先保持行距不改,并在交付时提醒;用户觉得过松再改单倍行距。
表格改单倍行距(必须同时取消网格对齐)
⚠️ 只设 line_spacing = 1.0 是无效的:当文档设了 <w:docGrid w:type="lines">(公文标准配置)时,Word 默认让段落「对齐到网格」,会用网格行高(linePitch)覆盖单倍行距,视觉上毫无变化。
必须同时做两件事:
from docx.oxml import OxmlElement
def set_single_line_spacing(para):
"""单倍行距 + 取消网格对齐 —— 两步缺一不可。"""
# 1) 单倍行距:w:line=240 + w:lineRule=auto
para.paragraph_format.line_spacing = 1.0
# 2) 取消「对齐到网格」:<w:snapToGrid w:val="0"/>
ppr = para._element.get_or_add_pPr()
sg = ppr.find(qn('w:snapToGrid'))
if sg is None:
sg = OxmlElement('w:snapToGrid')
sp = ppr.find(qn('w:spacing'))
if sp is not None:
sp.addprevious(sg) # 必须插在 w:spacing 之前(pPr schema 顺序)
else:
ppr.append(sg)
sg.set(qn('w:val'), '0')
> pPr 子元素顺序(易踩坑):… snapToGrid → spacing → ind → contextualSpacing → jc …。用 addprevious() 精确插入比 append() 安全,append() 可能写出违反 schema 的顺序。
>
> 验证方法:改完回读,表格段落应为 line=240 rule=auto 且 snapToGrid=0。若 snapToGrid 缺失,说明该段仍跟随网格,单倍行距不会生效。
>
> 同理:正文如需脱离网格的自定义行距(30 磅固定值等),也要一并设 snapToGrid=0。
执行路径:editor_sdk(MCP) 与 python-docx 的取舍
本地 docx 处理有两条路径。经验结论:公文格式全量套用优先 python-docx。
editor_sdk MCP 的能力缺口
| 规范项 | MCP 工具 | 是否支持 |
|---|---|---|
| 字体、字号 | doc_update_text_property(font_family / font_size) |
✅ |
| 对齐、行距、首行缩进、段落样式 | doc_modify_paragraph(jc / line_spacing / first_line_indent_chars / heading_lvl) |
✅ |
| 页边距 | doc_modify_section |
✅ |
| 字符间距加宽 0.5 磅 | — | ❌ 无字段 |
| 文档网格 docGrid(每页 22 行) | — | ❌ 无字段 |
| 表格格式批量处理 | doc_modify_table_region + doc_set_table_properties |
⚠️ 需逐表读 JSON 再写回,14 张表/386 单元格规模下调用量大、JSON 易错 |
字符间距与文档网格都是公文规范硬指标,MCP 无法覆盖 → 全流程应走 python-docx(OOXML 直接注入)。
MCP 更适合小范围、确定性的编辑(改某段文字、调某段对齐)。
文件被占用(PermissionError)的绕开方案
现象:预览面板 / Word 客户端打开文档时,python-docx doc.save(原路径) 报
PermissionError: [Errno 13] Permission denied。
关键判断:若目录可写(能新建文件)且原文件可重命名,说明是共享读锁(非独占),可用"重命名替换"绕开:
# 1. python-docx 先保存到临时文件(脚本内:dst = src.replace('.docx', '.__new__.docx'))
# 2. 把原文件改名(共享读锁下允许 rename)
mv "原文件.docx" "原文件.oldlocked.docx"
# 3. 把新文件改名为原文件名
mv "原文件.__new__.docx" "原文件.docx"
# 4. 清理(通常可删;失败可忽略)
rm -f "原文件.oldlocked.docx"
> 若先走 MCP 编辑过,写盘前建议 close_file 释放实例;若报 no editor open 说明已释放,直接写盘。
标准工作流(本 skill 实操验证)
present_files打开文档(让用户实时看到)get_pool_status拿file_id(或close_file释放)- python-docx 脚本处理 → 若遇锁,走上面的重命名替换
- 脚本验证关键属性(字号分布 / 缩进 / 边距 / 网格)
present_files重新展示结果- 备份原稿:
<原文件名>_原稿备份.docx放同目录,处理前先cp
注意事项与边界
- 结构层次标题格式(如"一、(一)1.(1)")在本指南中未单独规定,正文段落设置时已"(结构层次标题除外)"。如组织另有规定或需严格遵循 GB/T 9704-2012,使用下列通用方案:
- 第一层 "一、":黑体三号
- 第二层 "(一)":楷体_GB2312 三号
- 第三层 "1.":仿宋_GB2312 三号加粗
- 第四层 "(1)":仿宋_GB2312 三号
- 上述层级不缩进、不顶格书写;具体以单位最终要求为准。
- 页码、抄送、印发机关、印发日期、附件说明、版记等 GB/T 9704-2012 其他要素,按组织规范另行处理,本 skill 仅覆盖标题 / 正文 / 页面三大块的版心格式。
- 未安装方正小标宋简体 / 仿宋_GB2312 字体时,Word 会自动替换字体,导致打印件字号偏差、每页行数与每行字数不再准确;务必在目标终端安装字体后再排版与打印。
- WPS / 飞书文档 / 腾讯文档等非 Word 编辑器的设置项名称与位置略有差异,但上述核心参数(字体名、字号、字符间距、行距、首行缩进、页边距、文档网格)含义一致,可按本 skill 思路映射。
- 演示指南中"标题 / 正文"的字符间距与行距单位易混淆:字符间距 0.5 磅通过字体对话框"高级"设置;行距 29.5 磅通过段落对话框"行距→最小值"设置。两者单位虽同为"磅",但所属对话框不同。
适用文件参考
- 源依据文件:用户提供的《公文格式调整指南.pdf》(桌面)。
- 国家标准:《党政机关公文格式 GB/T 9704-2012》。