如何用python在word插入复选框

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)

相关推荐
Yao.Li2 小时前
Dify 本地运行实操笔记
人工智能·笔记·python
Yao.Li2 小时前
Dify 请求主链路梳理
人工智能·python
Thomas.Sir2 小时前
第十二章:Prompt 提示工程 之 实战项目
python·prompt
第一程序员2 小时前
Python自动化办公:提升工作效率的利器
python·github
sg_knight10 小时前
设计模式实战:模板方法模式(Template Method)
python·设计模式·模板方法模式
FreakStudio11 小时前
ESP32居然能当 DNS 服务器用?内含NCSI欺骗和DNS劫持实现
python·单片机·嵌入式·面向对象·并行计算·电子diy
乐观勇敢坚强的老彭11 小时前
2026全国青少年信息素养大赛考纲
python·数学建模
YMWM_12 小时前
【问题】thor上的cubLas
linux·python·thor