1. 插入可点击的复选框(Word内容控件),这需要直接操作底层XML
核心原理
Word 的可点击 复选框不是普通字符,而是 结构化文档标记(SDT / Content Control) ,需要在 <w:p> 内直接插入 XML,python-docx 本身不封装这个功能,所以通过 OxmlElement + lxml 直接操作底层 XML。
关键 XML 结构如下:
xml
<w:sdt>
<w:sdtPr>
<w14:checkbox>
<w14:checked w14:val="0"/> <!-- 0=未勾, 1=已勾 -->
<w14:checkedState w14:val="2611" w14:font="MS Gothic"/> <!-- ☑ -->
<w14:uncheckedState w14:val="2610" w14:font="MS Gothic"/> <!-- ☐ -->
</w14:checkbox>
</w:sdtPr>
<w:sdtContent>
<w:r><w:t>☐</w:t></w:r> <!-- 当前显示字符 -->
</w:sdtContent>
</w:sdt>
复用方法
脚本中的核心函数 insert_checkboxes_in_paragraph() 可以直接复用:
python
# 在已有段落的任意位置插入
para = doc.add_paragraph()
insert_checkboxes_in_paragraph(para, [
{"before": "是否同意 ", "checked": False, "after": " "}, # 空白框
{"before": "已确认 ", "checked": True, "after": ""}, # 已勾选
])
before/after:复选框前后的文字(支持空格对齐)checked=True:初始显示 ☑;False:显示 ☐- 打开 Word 后单击方框,即可实时切换对号
代码:
python
"""
在 Word 文档指定位置插入可点击复选框(内容控件)
依赖:pip install python-docx
用法:
python insert_checkbox.py
说明:
- 使用 Word 结构化文档标记(SDT / Content Control)实现可点击复选框
- 支持初始状态:已勾选(☑)或空白(☐)
- 打开生成的 .docx 后,在 Word 中单击方框即可切换对号
- insert_checkboxes_in_paragraph() 可复用,插入到任意段落的任意位置
"""
from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from lxml import etree
import copy
# ── 命名空间 ──────────────────────────────────────────────────────────────────
W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml"
def make_checkbox_sdt(checked: bool = False) -> etree._Element:
"""
构建一个 Word 复选框内容控件(SDT)的 XML 元素。
参数
----
checked : bool
True → 初始显示 ☑(已勾选)
False → 初始显示 ☐(空白)
返回
----
lxml Element:<w:sdt> ... </w:sdt>
"""
# ── <w:sdt> 根 ────────────────────────────────────────────────────────────
sdt = OxmlElement("w:sdt")
# ── <w:sdtPr>:控件属性 ───────────────────────────────────────────────────
sdtPr = OxmlElement("w:sdtPr")
# <w14:checkbox>
checkbox = etree.SubElement(sdtPr, f"{{{W14_NS}}}checkbox")
# <w14:checked w14:val="1/0"/>
checked_el = etree.SubElement(checkbox, f"{{{W14_NS}}}checked")
checked_el.set(f"{{{W14_NS}}}val", "1" if checked else "0")
# <w14:checkedState> ☑ U+2611
checked_state = etree.SubElement(checkbox, f"{{{W14_NS}}}checkedState")
checked_state.set(f"{{{W14_NS}}}val", "2611")
checked_state.set(f"{{{W14_NS}}}font", "MS Gothic")
# <w14:uncheckedState> ☐ U+2610
unchecked_state = etree.SubElement(checkbox, f"{{{W14_NS}}}uncheckedState")
unchecked_state.set(f"{{{W14_NS}}}val", "2610")
unchecked_state.set(f"{{{W14_NS}}}font", "MS Gothic")
sdt.append(sdtPr)
# ── <w:sdtContent>:控件显示内容 ──────────────────────────────────────────
sdtContent = OxmlElement("w:sdtContent")
run = OxmlElement("w:r")
rPr = OxmlElement("w:rPr")
rFonts = OxmlElement("w:rFonts")
rFonts.set(qn("w:ascii"), "MS Gothic")
rFonts.set(qn("w:hAnsi"), "MS Gothic")
rFonts.set(qn("w:hint"), "eastAsia")
rPr.append(rFonts)
run.append(rPr)
t = OxmlElement("w:t")
t.text = "☑" if checked else "☐"
run.append(t)
sdtContent.append(run)
sdt.append(sdtContent)
return sdt
def insert_checkboxes_in_paragraph(
para,
items: list[dict],
):
"""
向段落内插入多个复选框及其标签文字。
参数
----
para : docx Paragraph 对象
items : list of dict,每项格式:
{
"before": "前置文字", # 复选框前的文字(可省略)
"checked": False, # 初始是否勾选
"after": "标签文字", # 复选框后的文字(可省略)
}
示例
----
insert_checkboxes_in_paragraph(para, [
{"before": "选项A ", "checked": False, "after": " "},
{"before": "选项B ", "checked": True, "after": ""},
])
"""
p_elem = para._p # 段落的原始 <w:p> 元素
def add_text_run(text: str):
"""在段落末尾追加一个普通文字 run。"""
r = OxmlElement("w:r")
t = OxmlElement("w:t")
t.set(qn("xml:space"), "preserve")
t.text = text
r.append(t)
p_elem.append(r)
for item in items:
before = item.get("before", "")
checked = item.get("checked", False)
after = item.get("after", "")
if before:
add_text_run(before)
p_elem.append(make_checkbox_sdt(checked=checked))
if after:
add_text_run(after)
# ── 演示:生成示例文档 ────────────────────────────────────────────────────────
def main():
doc = Document()
# ── 标题 ─────────────────────────────────────────────────────────────────
doc.add_heading("可点击复选框示例", level=1)
# ── 示例 1:同一行两个复选框,一空一勾 ───────────────────────────────────
doc.add_paragraph("示例 1:同行两个复选框(一空一勾)")
para1 = doc.add_paragraph()
insert_checkboxes_in_paragraph(
para1,
[
{"before": "选项 A ", "checked": False, "after": " "}, # 空白框
{"before": "选项 B ", "checked": True, "after": ""}, # 已勾选
],
)
doc.add_paragraph("") # 空行
# ── 示例 2:调查问卷风格 ──────────────────────────────────────────────────
doc.add_paragraph("示例 2:调查问卷")
questions = [
("您是否同意条款?", False),
("您是否已阅读说明?", True),
("您是否需要发票?", False),
]
for label, default_checked in questions:
para = doc.add_paragraph()
insert_checkboxes_in_paragraph(
para,
[{"before": f"{label} ", "checked": default_checked, "after": ""}],
)
doc.add_paragraph("")
# ── 示例 3:自定义位置------在文字中间插入复选框 ─────────────────────────────
doc.add_paragraph("示例 3:文字中间插入复选框")
para3 = doc.add_paragraph()
insert_checkboxes_in_paragraph(
para3,
[
{"before": "请勾选:", "checked": False, "after": " 同意 "},
{ "checked": False, "after": " 不同意"},
],
)
# ── 保存 ─────────────────────────────────────────────────────────────────
out_path = "/mnt/user-data/outputs/checkbox_demo.docx"
doc.save(out_path)
print(f"✅ 文档已生成:{out_path}")
print(" 用 Microsoft Word 打开后,单击方框即可切换对号。")
if __name__ == "__main__":
main()
2.替换指定位置法
python-docx 的高层 API 没有现成的"插入可点击复选框"接口;Word 里这种可点击方框本质上是 content control ,底层 OOXML 是 w:sdt + w14:checkbox。而且这种 checkbox content control 是 Word 支持的正式类型,Office 2010+ 可用。换句话说,做法是:用 python-docx 定位段落/文本位置,再用底层 XML 把复选框控件插进去 。(Microsoft Learn)
"指定位置"最稳的做法不是按页面坐标,而是先在 Word 模板里放占位符,比如同一行写成:是否同意:{``{CB1}} 是 {``{CB2}} 否。python-docx 的文本处理本来就是围绕 paragraph/run 结构来做的,所以按占位符替换最合适。(python-docx)
下面这份代码可以直接用。它会把模板中的 {``{CB1}}、{``{CB2}} 替换成内嵌、可在 Microsoft Word 里手动点击切换的方框控件,而且两个方框会保留在同一行。
python
# -*- coding: utf-8 -*-
import re
import random
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
def iter_paragraphs(parent):
"""
递归遍历 parent 下所有段落,包括表格里的段落
"""
if hasattr(parent, "paragraphs"):
for p in parent.paragraphs:
yield p
if hasattr(parent, "tables"):
for table in parent.tables:
for row in table.rows:
for cell in row.cells:
yield from iter_paragraphs(cell)
def iter_all_paragraphs(doc):
"""
遍历正文、页眉、页脚中的所有段落
"""
yield from iter_paragraphs(doc)
for section in doc.sections:
yield from iter_paragraphs(section.header)
yield from iter_paragraphs(section.footer)
def clear_paragraph(paragraph):
"""
清空一个段落中的所有内容
"""
p = paragraph._p
for child in list(p):
p.remove(child)
def make_checkbox_sdt(tag, alias=None, checked=False):
"""
构造一个 Word 可点击复选框内容控件
选中:☑
未选中:☐
"""
alias = alias or tag
# <w:sdt>
sdt = OxmlElement("w:sdt")
# <w:sdtPr>
sdt_pr = OxmlElement("w:sdtPr")
# 唯一 id
id_el = OxmlElement("w:id")
id_el.set(qn("w:val"), str(random.randint(100000000, 999999999)))
sdt_pr.append(id_el)
# 别名
alias_el = OxmlElement("w:alias")
alias_el.set(qn("w:val"), alias)
sdt_pr.append(alias_el)
# 标签
tag_el = OxmlElement("w:tag")
tag_el.set(qn("w:val"), tag)
sdt_pr.append(tag_el)
# checkbox 定义
checkbox = OxmlElement("w14:checkbox")
# 是否默认选中
checked_el = OxmlElement("w14:checked")
checked_el.set(qn("w14:val"), "1" if checked else "0")
checkbox.append(checked_el)
# 选中状态:☑ = U+2611
checked_state = OxmlElement("w14:checkedState")
checked_state.set(qn("w14:font"), "MS Gothic")
checked_state.set(qn("w14:val"), "2611")
checkbox.append(checked_state)
# 未选中状态:☐ = U+2610
unchecked_state = OxmlElement("w14:uncheckedState")
unchecked_state.set(qn("w14:font"), "MS Gothic")
unchecked_state.set(qn("w14:val"), "2610")
checkbox.append(unchecked_state)
sdt_pr.append(checkbox)
sdt.append(sdt_pr)
# <w:sdtContent>
sdt_content = OxmlElement("w:sdtContent")
r = OxmlElement("w:r")
t = OxmlElement("w:t")
t.text = "☑" if checked else "☐"
r.append(t)
sdt_content.append(r)
sdt.append(sdt_content)
return sdt
def replace_placeholders_with_checkboxes(doc_path, output_path, checkbox_map):
"""
用复选框替换模板中的占位符
checkbox_map 示例:
{
"{{CB1}}": {"tag": "cb_yes", "alias": "是否同意-是", "checked": True},
"{{CB2}}": {"tag": "cb_no", "alias": "是否同意-否", "checked": False},
}
"""
doc = Document(doc_path)
if not checkbox_map:
raise ValueError("checkbox_map 不能为空")
# 生成正则,用于拆分占位符
token_pattern = re.compile(
"(" + "|".join(re.escape(k) for k in checkbox_map.keys()) + ")"
)
for paragraph in iter_all_paragraphs(doc):
full_text = paragraph.text
if not full_text:
continue
if not any(token in full_text for token in checkbox_map):
continue
parts = token_pattern.split(full_text)
# 清空原段落并重建
clear_paragraph(paragraph)
for part in parts:
if not part:
continue
if part in checkbox_map:
cfg = checkbox_map[part]
checkbox_el = make_checkbox_sdt(
tag=cfg["tag"],
alias=cfg.get("alias"),
checked=cfg.get("checked", False),
)
paragraph._p.append(checkbox_el)
else:
paragraph.add_run(part)
doc.save(output_path)
if __name__ == "__main__":
# 输入模板文件
input_docx = "checkbox_demo2.docx"
# 输出文件
output_docx = "结果.docx"
# 占位符与复选框配置
checkbox_map = {
"{{CB1}}": {
"tag": "cb_yes",
"alias": "是否同意-是",
"checked": True
},
"{{CB2}}": {
"tag": "cb_no",
"alias": "是否同意-否",
"checked": False
}
}
replace_placeholders_with_checkboxes(
doc_path=input_docx,
output_path=output_docx,
checkbox_map=checkbox_map
)
print(f"已生成文件:{output_docx}")
你的 Word 模板里可以先写成这样:
text
是否同意:{{CB1}} 是 {{CB2}} 否
运行后,效果就是:
text
是否同意:☒ 是 ☐ 否
而且这两个方框不是普通字符,是 Word 的复选框内容控件 ;在 Microsoft Word 中可以直接点击切换。该方案本质上是插入 checkbox content control。(Microsoft Learn)
提醒两点。第一,这个写法会保留"同一行插入两个方框"的效果,但会重建该段落内容 ,所以如果这行里原来有很复杂的局部加粗、不同字体、超链接之类,可能会丢失。第二,若你只是插入 ☐ / ☑ 这样的普通 Unicode 字符,python-docx 很容易做,但那种不是可点击控件;现在这个需求必须走上面这种底层 OOXML 方案。(GitHub)