[python] python-docx-template模板化Word文档生成指北

python-docx库的核心功能是程序化创建全新的Word文档,但在基于已有模板替换其部分内容时,其操作会非常繁琐。用户需要先解析文档结构、定位具体位置、手动替换内容,并维护原有格式与布局,导致开发效率较低。相关使用方法可参考:基于python-docx库的Word文档自动处理全解

python-docx-template正是为解决这一痛点而设计的。它借鉴Jinja2模板引擎的思路,允许在Word文档中直接插入类似{{variable}}的占位符,随后仅用几行代码即可完成数据填充,无需关心底层文档结构,完美适配基于模板修改文档的场景。

python-docx-template基于python-docx实现文档读写功能,并借助jinja2提供灵活的模板标签支持,其设计思路如下:

  1. 用Word制作模板
    在Microsoft Word中自由设计文档格式,如插入图片、设置页眉页脚、调整表格样式,充分利用Word强大的排版功能。

  2. 插入模板变量
    在需要动态内容的位置,直接输入Jinja2风格的标签,例如{{company_name}}{%for item in list%}

  3. 保存为模板文件
    将文档保存为普通的.docx文件,该文件即成为可复用的模板。

  4. 用Python批量生成
    加载模板并传入字典或对象,python-docx-template会自动替换标签,生成最终文档。

python-docx-template的官方代码仓库地址为:python-docx-template,详细文档可参阅:python-docx-template doc。本文使用的python-docx-template版本为0.20.2,安装命令如下:

pip install docxtpl

其中,docxtpl是python-docx-template库的正式分发名称,二者指代同一工具。

1 使用说明

1.1 核心概念

1.1.1 标签说明

python-docx-template允许在Word文档中使用Jinja2标签和过滤器。但为确保其在Word中正常运作,需遵循若干限制。

常规Jinja2标签仅能在同一段落内且同一文本运行中使用,若需控制段落、表格行或包含样式的完整文本运行,则必须使用后续章节介绍的复杂元素标签语法。

举例而言,若创建一个所有字符样式相同的段落,Word内部只会生成一个文本运行对象。但若在该段落中将部分文字设置为加粗,Word会将原有文本运行拆分为三个独立部分,分别对应普通样式、加粗样式及恢复后的普通样式。

标签

若要对段落、表格行、表格列以及文本段进行管理,需使用以下专用语法:

  • 段落标签:{%p jinja2_tag %}
  • 表格行标签:{%tr jinja2_tag %}
  • 表格列标签:{%tc jinja2_tag %}
  • 文本段标签:{%r jinja2_tag %}

这些以{% %}包裹的内容是Jinja2模板语法标签,引擎会识别并执行其中的逻辑。一个完整的模板标签基本结构如下:

{% 指令关键字 参数/条件 %}  // 起始标签
内容                      // 被标签控制的文本
{% 结束关键字 %}          // 结束标签

通过此类标签,python-docx-template可自动将标准Jinja2标签,即去除前缀ptrtcr后的内容,精确嵌入到文档XML源码的相应位置。

假设模板内容如下:

{%p if display_paragraph %}
一段或多段文本内容
{%p endif %}

无论display_paragraph变量取值如何,首尾两个包含{%p ... %}标签的段落,都不会出现在最终生成的docx文档中。
只有当display_paragraph的值为True时,以下内容才会被保留在生成的文档里:

一段或多段文本内容

对于模板里的标签格式需遵循如下要求,否则无法生成正确结果:

  • 起始标签分隔符后必须加空格,结束标签分隔符前必须加空格:
    • 错误示例:
      {%if something%}
      {%pif display_paragraph%}
      
    • 正确示例:
      {% if something %}
      {%p if display_paragraph %}
      
  • 同一段落、行、列或文本段内,禁止连续使用标签分隔符:
    • 错误示例:
      {%p{%tr{%tc{%r
      
  • 标签与内容不可写在同一行,需换行排版:
    • 错误示例:
      {%p if display_paragraph %}Here is my paragraph {%p endif %}
      
    • 正确示例:
      {%p if display_paragraph %}
      Here is my paragraph
      {%p endif %}
      

1.1.2 常见元素

显示变量

Jinja2模板里,可以用双大括号来显示变量:

{{ <变量名> }}

如果变量是普通字符串,字符串里的特殊符号会自动转换成对应的格式:

  • \n → 换行
  • \a → 分段
  • \t → 制表符(按一下 Tab 键的效果)
  • \f → 分页符

如果变量是富文本(RichText)对象,必须在变量名前加一个r,明确表示要渲染这个富文本内容:

{{r <变量名> }}

注意,r要紧跟在左大括号的后面。

此外,变量名中禁止直接使用<,>, &这类字符,除非用了转义语法。

注释

可以在模板中添加类Jinja2风格的注释,注释不会被渲染到最终文件:

{#p 这是一个段落类型的注释 #}
{#tr 这是一个表格行类型的注释 #}
{#tc 这是一个表格单元格类型的注释 #}

在执行如下代码对模板文件进行渲染操作时,模板中原有的所有注释信息均会被清除,不会保留在最终的渲染结果中:

# https://github.com/elapouya/python-docx-template/blob/master/tests/comments.py
from docxtpl import DocxTemplate
import os

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/comments_tpl.docx
tpl = DocxTemplate("templates/comments_tpl.docx")

tpl.render({})
os.makedirs("output",exist_ok=True)
tpl.save("output/comments.docx")

文本的拆分与合并

若包含Jinja2标签的文本过长,会导致可读性下降,例如:

我的房子位于{% if living_in_town %}城市区域{% else %}乡村地区{% endif %},我非常喜欢它。

借助{%-语法,可将Jinja2标签与上一行内容合并,同时借助-%}语法,可将Jinja2标签与下一行内容合并。此时可使用回车键(Enter)或Shift+Enter对文本进行拆分排版,再通过上述的标签语法将拆分后的内容合并为一个整体,示例如下:

我的房子位于
{%- if living_in_town -%}
城市区域
{%- else -%}
乡村地区
{%- endif -%}
,我非常喜欢它。

渲染代码如下:

from docxtpl import DocxTemplate
tpl = DocxTemplate("template.docx")
context = {"living_in_town": True}

tpl.render(context)
tpl.save("output/output.docx")

表格

可以使用colspan标签实现表格单元格的水平合并:

{% colspan <var> %}

<var>必须为整数,用于指定需要合并的列数。也可通过Jinja2模板引擎自动计算,如内置过滤器count通过管道符|接收col_labels,用于统计其元素数量:

{% colspan col_labels|count %}

以下示例展示了如何利用colspan标签、trtc标签动态填充一个表格:

# https://github.com/elapouya/python-docx-template/blob/master/tests/dynamic_table.py
from docxtpl import DocxTemplate
import os
# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/dynamic_table_tpl.docx
tpl = DocxTemplate("templates/dynamic_table_tpl.docx")

context = {
    "col_labels": ["fruit", "vegetable", "stone", "thing"],
    "tbl_contents": [
        {"label": "yellow", "cols": ["banana", "capsicum", "pyrite", "taxi"]},
        {"label": "red", "cols": ["apple", "tomato", "cinnabar", "doubledecker"]},
        {"label": "green", "cols": ["guava", "cucumber", "aventurine", "card"]},
    ],
}

tpl.render(context)
os.makedirs("output",exist_ok=True)
tpl.save("output/dynamic_table.docx")

也可以在for循环中实现单元格水平合并:

{% hm %}

以下代码展示了如何利用hm标签实现单元格水平合并:

# https://github.com/elapouya/python-docx-template/blob/master/tests/horizontal_merge.py
from docxtpl import DocxTemplate
import os
# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/horizontal_merge_tpl.docx
tpl = DocxTemplate("templates/horizontal_merge_tpl.docx")
tpl.render({})
os.makedirs("output",exist_ok=True)
tpl.save("output/horizontal_merge.docx")

此外,还可以在for循环中实现单元格垂直合并:

{% vm %}

以下示例展示了如何实现表格单元格的垂直合并效果:

# https://github.com/elapouya/python-docx-template/blob/master/tests/vertical_merge.py
from docxtpl import DocxTemplate

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/vertical_merge_tpl.docx
tpl = DocxTemplate("templates/vertical_merge_tpl.docx")

context = {
    "items": [
        {"desc": "Python interpreters", "qty": 2, "price": "FREE"},
        {"desc": "Django projects", "qty": 5403, "price": "FREE"},
        {"desc": "Guido", "qty": 1, "price": "100,000,000.00"},
    ],
    "total_price": "100,000,000.00",
    "category": "Book",
}

tpl.render(context)
tpl.save("output/vertical_merge.docx")

若需要修改表格单元格的背景色,必须将以下标签放置在单元格内容的最开头位置:

{% cellbg <var> %}

<var>必须填写颜色的十六进制编码,且无需包含井号(#)。

1.2 复杂元素

1.2.1 富文本

富文本(Rich Text)是相对于纯文本(Plain Text)而言的一种文本格式。它不仅包含文字内容,还支持为文字添加各种格式属性及多媒体元素,从而呈现出更为丰富的视觉效果。这种格式的编辑和处理也可以通过在Microsoft Word软件中预先定义字符样式来实现。

在python-docx-template中使用富文本的步骤如下:

  1. 在Word模板中,为富文本定义一个占位符。例如,使用{{r rich_text_var}},其中的r表示富文本。
  2. 在Python代码中,利用RichText类构建带有格式的文本内容。
  3. 渲染模板时,占位符会被替换为富文本内容,并保留所有已定义的格式样式。

在代码中使用RichText类时,还可通过以下标记控制文本格式:

  • 换行:\n
  • 换段:\a
  • 分页:\f

以下为生成富文本Word文档的示例:

# https://github.com/elapouya/python-docx-template/blob/master/tests/richtext.py
from docxtpl import DocxTemplate, RichText

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/richtext_tpl.docx
tpl = DocxTemplate("templates/richtext_tpl.docx")

rt = RichText()

# 向富文本对象中添加内容,依次演示不同的文本格式设置
rt.add("a rich text", style="myrichtextstyle")  # 使用自定义样式
rt.add(" with ")                                
rt.add("some italic", italic=True)              # 斜体文本
rt.add(" and ")
rt.add("some violet", color="#ff00ff")          # 紫色文本(十六进制颜色码)
rt.add(" and ")
rt.add("some striked", strike=True)             # 删除线文本
rt.add(" and ")
rt.add("some Highlighted", highlight="#ffff00") # 黄色高亮文本
rt.add(" and ")
rt.add("some small", size=14)                   # 小字号文本(14磅)
rt.add(" or ")
rt.add("big", size=60)                          # 大字号文本(60磅)
rt.add(" text.")
rt.add("\nYou can add an hyperlink, here to ")
# 添加带超链接的文本,build_url_id 用于创建URL标识并关联到指定链接
rt.add("bing", url_id=tpl.build_url_id("http://bing.com"))
rt.add("\nEt voilà ! ")
# 演示换行符效果
rt.add("\n1st line")
rt.add("\n2nd line")
rt.add("\n3rd line")
# \a 用于创建新段落
rt.add("\aA new paragraph : <cool>\a")
# \f 用于插入分页符
rt.add("--- A page break here (see next page) ---\f")

# 循环演示不同类型的下划线样式
for ul in [
    "single",       # 单下划线
    "double",       # 双下划线
    "thick",        # 粗下划线
    "dotted",       # 点下划线
    "dash",         # 短划线下划线
    "dotDash",      # 点划线下划线
    "dotDotDash",   # 双点划线下划线
    "wave",         # 波浪线下划线
]:
    rt.add("\nUnderline : " + ul + " \n", underline=ul)

# 演示不同字体的设置
rt.add("\nFonts :\n", underline=True)
rt.add("Arial\n", font="Arial")                
rt.add("Courier New\n", font="Courier New")    
rt.add("Times New Roman\n", font="Times New Roman") 

# 演示上标和下标文本
rt.add("\n\nHere some")
rt.add("superscript", superscript=True)        # 上标文本
rt.add(" and some")
rt.add("subscript", subscript=True)            # 下标文本

# 创建一个新的富文本对象,并将之前创建的rt对象嵌入其中,实现富文本嵌套
rt_embedded = RichText("an example of ")
rt_embedded.add(rt)

# 构建渲染上下文,将嵌套的富文本对象赋值给模板中的 "example" 变量
context = {
    "example": rt_embedded,
}

# 将上下文数据渲染到Word模板中
tpl.render(context)
# 保存渲染后的Word文档到指定路径
tpl.save("output/richtext.docx")

1.2.2 富文本段落

若要精细控制单个段落的样式,可在代码中使用RichTextParagraph()RP()对象。此对象需通过{{p <var> }}语法添加至模板。示例代码如下:

# https://github.com/elapouya/python-docx-template/blob/master/tests/richtextparagraph.py
from docxtpl import DocxTemplate, RichText, RichTextParagraph

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/richtext_paragraph_tpl.docx
tpl = DocxTemplate("templates/richtext_paragraph_tpl.docx")

# 创建富文本段落对象组合不同样式的段落
rtp = RichTextParagraph()
# 创建富文本对象,用于设置文本的字符样式
rt = RichText()

# 向富文本段落中添加文本,并指定自定义样式myrichparastyle
rtp.add(
    "The rich text paragraph function allows paragraph styles to be added to text",
    parastyle="myrichparastyle",
)
# 添加文本并使用内置段落样式IntenseQuote
rtp.add("Any built in paragraph style can be used", parastyle="IntenseQuote")
# 添加文本并使用自定义样式createdStyle
rtp.add(
    "or you can add your own, unlocking all style options", parastyle="createdStyle"
)
# 添加文本并使用默认普通段落样式normal
rtp.add(
    "To use, just create a style in your template word doc with the formatting you want "
    "and call it in the code.",
    parastyle="normal",
)

# 演示不同列表样式的使用
rtp.add("This allows for the use of")
rtp.add("custom bullet\apoints", parastyle="SquareBullet")  # 方形项目符号样式
rtp.add("Numbered Bullet Points", parastyle="BasicNumbered")  # 数字编号样式
rtp.add("and Alpha Bullet Points.", parastyle="alphaBracketNumbering")  # 字母编号样式

# 演示文本对齐方式的设置
rtp.add("You can", parastyle="normal")  # 默认左对齐
rtp.add("set the", parastyle="centerAlign")  # 应用自定义样式居中对齐
rtp.add("text alignment", parastyle="rightAlign")  # 应用自定义样式右对齐

# 演示行间距样式:紧凑行间距
rtp.add(
    "as well as the spacing between lines of text. Like this for example, "
    "this text has very tight spacing between the lines.\aIt also has no space between "
    "paragraphs of the same style.",
    parastyle="TightLineSpacing",
)
# 演示行间距样式:宽行间距
rtp.add(
    "Unlike this one, which has extra large spacing between lines for when you want to "
    "space things out a bit or just write a little less.",
    parastyle="WideLineSpacing",
)
# 演示段落背景色样式:绿色背景
rtp.add(
    "You can also set the background colour of a line.", parastyle="LineShadingGreen"
)

# 构建富文本字符串,演示字符级样式
rt.add("This works with ")
rt.add("Rich ", bold=True)  # 加粗
rt.add("Text ", italic=True)  # 斜体
rt.add("Strings", underline="single")  # 单下划线
rt.add(" too.")

# 将富文本对象添加到富文本段落中,并指定方形项目符号样式
rtp.add(rt, parastyle="SquareBullet")

# 构建渲染上下文,将富文本段落对象绑定到模板中的example变量
context = {
    "example": rtp,
}

# 将上下文数据渲染到 Word 模板中
tpl.render(context)
# 保存渲染后的 Word 文档到指定路径
tpl.save("output/richtext_paragraph.docx")

1.2.3 浮动对象

图片插入

可在文档中动态插入单张或多张图片,当前已支持JPEG和PNG格式。只需在模板中使用{{ <var> }}格式的标签即可。图片嵌入代码如下:

myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))

使用时应传入模板对象与图片文件路径,宽高为可选参数。宽度和高度需通过Mm(毫米)、Inches(英寸)或 Pt(磅)等类进行单位定义。示例代码如下:

# https://github.com/elapouya/python-docx-template/blob/master/tests/inline_image.py
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
import jinja2
# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/inline_image_tpl.docx
tpl = DocxTemplate("templates/inline_image_tpl.docx")

# 定义渲染模板所需的上下文数据
context = {
    # 只设置宽或者高,则图片自适应变化
    "myimage": InlineImage(tpl, "templates/python_logo.png", width=Mm(20)),
    # 手动指定宽高度
    "myimageratio": InlineImage(
        tpl, "templates/python_jpeg.jpg", width=Mm(30), height=Mm(60)
    ),
    # 定义图片+描述
    "frameworks": [
        {
            "image": InlineImage(tpl, "templates/django.png", height=Mm(10)),
            "desc": "The web framework for perfectionists with deadlines",
        },
        {
            "image": InlineImage(tpl, "templates/zope.png", height=Mm(10)),
            "desc": "Zope is a leading Open Source Application Server "
            "and Content Management Framework",
        },
        {
            "image": InlineImage(tpl, "templates/pyramid.png", height=Mm(10)),
            "desc": "Pyramid is a lightweight Python web framework aimed at taking "
            "small web apps into big web apps.",
        },
        {
            "image": InlineImage(tpl, "templates/bottle.png", height=Mm(10)),
            "desc": "Bottle is a fast, simple and lightweight WSGI micro web-framework "
            "for Python",
        },
        {
            "image": InlineImage(tpl, "templates/tornado.png", height=Mm(10)),
            "desc": "Tornado is a Python web framework and asynchronous networking "
            "library.",
        },
    ],
}

# autoescape是否开启自动转义,把模板变量中的特殊字符转换成无害的HTM实体
jinja_env = jinja2.Environment(autoescape=True)
# 使用上下文数据和自定义的jinja2环境渲染Word模板
tpl.render(context, jinja_env)
tpl.save("output/inline_image.docx")

替换文档中的图片

可对文档中的现有图片进行替换,具体操作如下:先在模板中插入一张占位图,按常规流程渲染模板,之后再将占位图替换为目标图片。该方法支持批量处理文档内的所有媒体文件,且替换后的图片将维持原占位图的纵横比。在模板中插入图片时,只需指定文件名即可。该替换操作同时对页眉、页脚及正文区域生效。

例如,替换占位图dummy_header_pic.jpg的语法示例如下:

tpl.replace_pic('dummy_header_pic.jpg', 'header_pic_i_want.jpg')

注意,在将图片手动插入Word模板时,某些版本的Word会自动对其重命名并存储。这导致图片在docx文件内的实际文件名与原文件名完全不同,从而可能引发找不到图片的问题。若不确定图片的具体位置,可将docx文件视为zip压缩包进行解压,随后在word\document.xml文件中,通过查找pic:nvPicPr节点来定位图片信息。具体步骤可参考:python使用docxtpl库对图片进行替换

在找到目标图片对应的pic:nvPicPr节点后,其name属性的值即为图片在文档中实际存储的文件名。此处的文件名可能不包含图片格式后缀,替换图片的示例代码可能如下:

tpl.replace_pic('Picture 1','header_pic_i_want.jpg')

1.2.4 子文档

模板变量可包含复杂的子文档对象,且能通过python-docx的文档操作方法从零构建。具体步骤为:先从模板对象中获取子文档对象,再将其当作python-docx文档对象使用。该功能需要安装额外依赖:

pip install "docxtpl[subdoc]"

以下代码展示了如何创建子文档,并将该子文档嵌入到主模板中,最终生成并保存新的文档:

# https://github.com/elapouya/python-docx-template/blob/master/tests/subdoc.py
from docxtpl import DocxTemplate
from docx.shared import Inches

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/subdoc_tpl.docx
# 1. 加载Word模板文件(指定模板文件路径)
tpl = DocxTemplate("templates/subdoc_tpl.docx")

# 2. 创建一个新的子文档对象
sd = tpl.new_subdoc()

# 3. 向下文档中添加段落内容
p = sd.add_paragraph("This is a sub-document inserted into a bigger one")
p = sd.add_paragraph("It has been ")
# 为文本片段设置自定义样式
p.add_run("dynamically").style = "dynamic"
p.add_run(" generated with python by using ")
# 为文本片段设置斜体样式
p.add_run("python-docx").italic = True
p.add_run(" library")

# 4. 向下文档添加标题
sd.add_heading("Heading, level 1", level=1)
# 向下文档添加带样式的段落(IntenseQuote样式为内置样式)
sd.add_paragraph("This is an Intense quote", style="IntenseQuote")

# 5. 向下文档插入图片
sd.add_paragraph("A picture :")
sd.add_picture("templates/python_logo.png", width=Inches(1.25))

# 6. 向下文档插入表格
sd.add_paragraph("A Table :")
# 创建表格(初始1行3列,作为表头)
table = sd.add_table(rows=1, cols=3)
# 获取表头单元格并设置内容
hdr_cells = table.rows[0].cells
hdr_cells[0].text = "Qty"
hdr_cells[1].text = "Id"
hdr_cells[2].text = "Desc"

# 定义表格数据
recordset = ((1, 101, "Spam"), (2, 42, "Eggs"), (3, 631, "Spam,spam, eggs, and ham"))
# 循环添加数据行到表格
for item in recordset:
    row_cells = table.add_row().cells
    row_cells[0].text = str(item[0])  # 数量(转为字符串)
    row_cells[1].text = str(item[1])  # ID(转为字符串)
    row_cells[2].text = item[2]       # 描述

context = {
    "mysubdoc": sd,
}

tpl.render(context)
tpl.save("output/subdoc.docx")

此外自python-docx-template V0.12.0版本起,支持将已有的.docx文件作为子文档进行合并,如下所示:

# https://github.com/elapouya/python-docx-template/blob/master/tests/merge_docx.py
from docxtpl import DocxTemplate

# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates
tpl = DocxTemplate("templates/merge_docx_master_tpl.docx")
sd = tpl.new_subdoc("templates/merge_docx_subdoc.docx")

context = {
    "mysubdoc": sd,
}

tpl.render(context)
tpl.save("output/merge_docx.docx")

1.3 补充操作

1.3.1 转义操作

默认情况下,python-docx-template不会对内容进行转义。这是因为在使用模板语法修改Word文档时,底层实际上是在处理XML结构,而像<>&这样的字符在XML中具有特殊含义,必须经过转义才能正确显示。以下提供几种实现转义的方法:

  1. 上下文定义时使用R()包裹内容,模板中添加r标记:

    context = { 'var': R('my text') }
    

    模板中写法:{{r <var> }}

  2. 上下文保持原始字符串,模板中使用|e过滤器:

    context = { 'var':'my text' }
    

    模板中写法:{{ <var>|e }}

  3. 上下文使用escape()函数处理内容,模板直接引用:

    context = { 'var': escape('my text') }
    

    模板中写法:{{ <var> }}

  4. 调用render方法时启用自动转义,默认值为autoescape=False

    tpl.render(context, autoescape=True)
    

在文档中插入代码清单时,如果需要同时转义文本并处理换行符(\n)、段落符(\a)和换页符(\f),可以在Python代码中使用Listing类。

例如:

context = { 
    'mylisting': Listing('the listing\nwith\nsome\nlines \a and some paragraph \a and special chars : <>&') 
}

在模板中直接引用即可:

{{ mylisting }}

使用Listing()能够保持当前字符样式,除非遇到\a分隔符,它会在该位置开始一个新的段落。

使用示例如下:

# https://github.com/elapouya/python-docx-template/blob/master/tests/escape.py
from docxtpl import DocxTemplate, R, Listing
# data: https://github.com/elapouya/python-docx-template/blob/master/tests/templates/escape_tpl.docx
tpl = DocxTemplate("templates/escape_tpl.docx")

context = {
    "myvar": R(
        '"less than" must be escaped : <, this can be done with RichText() or R()'
    ),
    "myescvar": 'It can be escaped with a "|e" jinja filter in the template too : < ',
    "nlnp": R(
        "Here is a multiple\nlines\nstring\aand some\aother\aparagraphs",
        color="#ff00ff",
    ),
    "mylisting": Listing("the listing\nwith\nsome\nlines\nand special chars : <>& ..."),
    "page_break": R("\f"),
    "new_listing": """
This is a new listing
Here is a \t tab\a
Here is a page break : \f
That's it
""",
    "some_html": (
        "HTTP/1.1 200 OK\n"
        "Server: Apache-Coyote/1.1\n"
        "Cache-Control: no-store\n"
    ),
}

tpl.render(context)
tpl.save("output/escape.docx")

此外,若需在最终生成的Word文档中显示由Jinja2渲染后的特殊字符%{},可以使用以下方式进行转义:

  • 显示%% → 使用{_% %_}
  • 显示{{ }} → 使用{_{ }_}

1.3.2 获取未定义变量

要获取模板渲染时所需的未定义变量,可以按照以下步骤操作:

from docxtpl import DocxTemplate
tpl = DocxTemplate('template.docx')

context = {'name': '张三', 'age': 30}
# 注意:department变量未定义

# 检测缺失变量
missing = tpl.get_undeclared_template_variables(context=context)
print(f"缺失的变量: {missing}")

# 获取所有变量(不传context)
all_vars = tpl.get_undeclared_template_variables()
print(f"模板所有变量: {all_vars}")

模板文档的内容包含:

姓名:{{name}}
年龄:{{age}}
部门:{{department}}

若未传递 context 参数,该方法将返回模板中所有需要赋值的变量名的集合。此结果可用于向用户提示输入,或写入文件供后续手动处理。

1.3.3 Jinja自定义过滤器

Python-docx-template的render方法支持一个可选参数jinja_env,作为其第二个参数。通过传递自定义的Jinja环境对象,可以实现自定义过滤器的添加,从而灵活扩展数据处理逻辑。以下为具体示例。

模板word内容如下:

=== 姓名展示对比 ===
原始名字: {{ name }}
方法A (Python处理): {{ name_upper }}
方法B (模板过滤器): {{ name|upper }}

对应的Python渲染代码:

from docxtpl import DocxTemplate
import jinja2

# 准备数据
data = {'name': 'zhangsan'}

# Python中处理
data['name_upper'] = data['name'].upper()

# 定义过滤器
def my_upper(text):
    return text.upper()

doc = DocxTemplate("template.docx")

#创建环境并注册过滤器
env = jinja2.Environment()
env.filters['upper'] = my_upper 

doc.render(data, env) 
doc.save("output/compare.docx")
print(f"原始数据: {data['name']}")
print(f"Python处理结果: {data['name_upper']}")
print(f"模板过滤器结果: {data['name'].upper()}")

1.3.4 Microsoft Word 2016特殊行为说明

MS Word 2016会忽略文档中的制表符,这是该版本特有的情况。类似LibreOffice或WordPad等其他编辑工具则无此问题。同样,以Jinja2标签开头且带有前导空格的行,其空格也会被忽略。

为解决上述问题,可使用RichText进行处理:

tpl.render({
    'test_space_r': RichText('          '),
    'test_tabs_r': RichText(5 * '\t'),
})

在模板中,通过{{r ... }}语法调用:

{{r test_space_r}} 空格将被保留
{{r test_tabs_r}} 制表符将正常显示

2 参考

posted @ 2026-01-19 18:51  落痕的寒假  阅读(16)  评论(0)    收藏  举报