python使用python-docx-template實(shí)現(xiàn)模板化Word文檔生成指南
python-docx庫(kù)的核心功能是程序化創(chuàng)建全新的Word文檔,但在基于已有模板替換其部分內(nèi)容時(shí),其操作會(huì)非常繁瑣。用戶需要先解析文檔結(jié)構(gòu)、定位具體位置、手動(dòng)替換內(nèi)容,并維護(hù)原有格式與布局,導(dǎo)致開(kāi)發(fā)效率較低。相關(guān)使用方法可參考:python基于python-docx庫(kù)自動(dòng)化處理Word文檔的完整指南
python-docx-template正是為解決這一痛點(diǎn)而設(shè)計(jì)的。它借鑒Jinja2模板引擎的思路,允許在Word文檔中直接插入類似{{variable}}的占位符,隨后僅用幾行代碼即可完成數(shù)據(jù)填充,無(wú)需關(guān)心底層文檔結(jié)構(gòu),完美適配基于模板修改文檔的場(chǎng)景。
python-docx-template基于python-docx實(shí)現(xiàn)文檔讀寫(xiě)功能,并借助jinja2提供靈活的模板標(biāo)簽支持,其設(shè)計(jì)思路如下:
- 用Word制作模板:在Microsoft Word中自由設(shè)計(jì)文檔格式,如插入圖片、設(shè)置頁(yè)眉頁(yè)腳、調(diào)整表格樣式,充分利用Word強(qiáng)大的排版功能。
- 插入模板變量:在需要?jiǎng)討B(tài)內(nèi)容的位置,直接輸入Jinja2風(fēng)格的標(biāo)簽,例如
{{company_name}}或{%for item in list%}。 - 保存為模板文件:將文檔保存為普通的.docx文件,該文件即成為可復(fù)用的模板。
- 用Python批量生成:加載模板并傳入字典或?qū)ο螅琾ython-docx-template會(huì)自動(dòng)替換標(biāo)簽,生成最終文檔。
python-docx-template的官方代碼倉(cāng)庫(kù)地址為:python-docx-template,詳細(xì)文檔可參閱:python-docx-template doc。本文使用的python-docx-template版本為0.20.2,安裝命令如下:
pip install docxtpl
其中,docxtpl是python-docx-template庫(kù)的正式分發(fā)名稱,二者指代同一工具。
核心概念
標(biāo)簽說(shuō)明
python-docx-template允許在Word文檔中使用Jinja2標(biāo)簽和過(guò)濾器。但為確保其在Word中正常運(yùn)作,需遵循若干限制。
常規(guī)Jinja2標(biāo)簽僅能在同一段落內(nèi)且同一文本運(yùn)行中使用,若需控制段落、表格行或包含樣式的完整文本運(yùn)行,則必須使用后續(xù)章節(jié)介紹的復(fù)雜元素標(biāo)簽語(yǔ)法。
舉例而言,若創(chuàng)建一個(gè)所有字符樣式相同的段落,Word內(nèi)部只會(huì)生成一個(gè)文本運(yùn)行對(duì)象。但若在該段落中將部分文字設(shè)置為加粗,Word會(huì)將原有文本運(yùn)行拆分為三個(gè)獨(dú)立部分,分別對(duì)應(yīng)普通樣式、加粗樣式及恢復(fù)后的普通樣式。
標(biāo)簽
若要對(duì)段落、表格行、表格列以及文本段進(jìn)行管理,需使用以下專用語(yǔ)法:
- 段落標(biāo)簽:
{%p jinja2_tag %} - 表格行標(biāo)簽:
{%tr jinja2_tag %} - 表格列標(biāo)簽:
{%tc jinja2_tag %} - 文本段標(biāo)簽:
{%r jinja2_tag %}
這些以{% %}包裹的內(nèi)容是Jinja2模板語(yǔ)法標(biāo)簽,引擎會(huì)識(shí)別并執(zhí)行其中的邏輯。一個(gè)完整的模板標(biāo)簽基本結(jié)構(gòu)如下:
{% 指令關(guān)鍵字 參數(shù)/條件 %} // 起始標(biāo)簽
內(nèi)容 // 被標(biāo)簽控制的文本
{% 結(jié)束關(guān)鍵字 %} // 結(jié)束標(biāo)簽
通過(guò)此類標(biāo)簽,python-docx-template可自動(dòng)將標(biāo)準(zhǔn)Jinja2標(biāo)簽,即去除前綴p、tr、tc、r后的內(nèi)容,精確嵌入到文檔XML源碼的相應(yīng)位置。
假設(shè)模板內(nèi)容如下:
{%p if display_paragraph %}
一段或多段文本內(nèi)容
{%p endif %}
無(wú)論display_paragraph變量取值如何,首尾兩個(gè)包含{%p ... %}標(biāo)簽的段落,都不會(huì)出現(xiàn)在最終生成的docx文檔中。
只有當(dāng)display_paragraph的值為T(mén)rue時(shí),以下內(nèi)容才會(huì)被保留在生成的文檔里:
一段或多段文本內(nèi)容
對(duì)于模板里的標(biāo)簽格式需遵循如下要求,否則無(wú)法生成正確結(jié)果:
起始標(biāo)簽分隔符后必須加空格,結(jié)束標(biāo)簽分隔符前必須加空格:
錯(cuò)誤示例:
{%if something%}
{%pif display_paragraph%}正確示例:
{% if something %}
{%p if display_paragraph %}
同一段落、行、列或文本段內(nèi),禁止連續(xù)使用標(biāo)簽分隔符:
錯(cuò)誤示例:
{%p{%tr{%tc{%r
標(biāo)簽與內(nèi)容不可寫(xiě)在同一行,需換行排版:
錯(cuò)誤示例:
{%p if display_paragraph %}Here is my paragraph {%p endif %}
正確示例:
{%p if display_paragraph %}
Here is my paragraph
{%p endif %}
常見(jiàn)元素
顯示變量
Jinja2模板里,可以用雙大括號(hào)來(lái)顯示變量:
{{ <變量名> }}
如果變量是普通字符串,字符串里的特殊符號(hào)會(huì)自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的格式:
\n→ 換行\a→ 分段\t→ 制表符(按一下 Tab 鍵的效果)\f→ 分頁(yè)符
如果變量是富文本(RichText)對(duì)象,必須在變量名前加一個(gè)r,明確表示要渲染這個(gè)富文本內(nèi)容:
{{r <變量名> }}
注意,r要緊跟在左大括號(hào)的后面。
此外,變量名中禁止直接使用<,>, &這類字符,除非用了轉(zhuǎn)義語(yǔ)法。
注釋
可以在模板中添加類Jinja2風(fēng)格的注釋,注釋不會(huì)被渲染到最終文件:
{#p 這是一個(gè)段落類型的注釋 #}
{#tr 這是一個(gè)表格行類型的注釋 #}
{#tc 這是一個(gè)表格單元格類型的注釋 #}
在執(zhí)行如下代碼對(duì)模板文件進(jìn)行渲染操作時(shí),模板中原有的所有注釋信息均會(huì)被清除,不會(huì)保留在最終的渲染結(jié)果中:
# 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標(biāo)簽的文本過(guò)長(zhǎng),會(huì)導(dǎo)致可讀性下降,例如:
我的房子位于{% if living_in_town %}城市區(qū)域{% else %}鄉(xiāng)村地區(qū){% endif %},我非常喜歡它。
借助{%-語(yǔ)法,可將Jinja2標(biāo)簽與上一行內(nèi)容合并,同時(shí)借助-%}語(yǔ)法,可將Jinja2標(biāo)簽與下一行內(nèi)容合并。此時(shí)可使用回車鍵(Enter)或Shift+Enter對(duì)文本進(jìn)行拆分排版,再通過(guò)上述的標(biāo)簽語(yǔ)法將拆分后的內(nèi)容合并為一個(gè)整體,示例如下:
我的房子位于
{%- if living_in_town -%}
城市區(qū)域
{%- else -%}
鄉(xiāng)村地區(qū)
{%- endif -%}
,我非常喜歡它。
渲染代碼如下:
from docxtpl import DocxTemplate
tpl = DocxTemplate("template.docx")
context = {"living_in_town": True}
tpl.render(context)
tpl.save("output/output.docx")
表格
可以使用colspan標(biāo)簽實(shí)現(xiàn)表格單元格的水平合并:
{% colspan <var> %}
<var>必須為整數(shù),用于指定需要合并的列數(shù)。也可通過(guò)Jinja2模板引擎自動(dòng)計(jì)算,如內(nèi)置過(guò)濾器count通過(guò)管道符|接收col_labels,用于統(tǒng)計(jì)其元素?cái)?shù)量:
{% colspan col_labels|count %}
以下示例展示了如何利用colspan標(biāo)簽、tr和tc標(biāo)簽動(dòng)態(tài)填充一個(gè)表格:
# 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循環(huán)中實(shí)現(xiàn)單元格水平合并:
{% hm %}
以下代碼展示了如何利用hm標(biāo)簽實(shí)現(xiàn)單元格水平合并:
# 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循環(huán)中實(shí)現(xiàn)單元格垂直合并:
{% vm %}
以下示例展示了如何實(shí)現(xiàn)表格單元格的垂直合并效果:
# 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")
若需要修改表格單元格的背景色,必須將以下標(biāo)簽放置在單元格內(nèi)容的最開(kāi)頭位置:
{% cellbg <var> %}
<var>必須填寫(xiě)顏色的十六進(jìn)制編碼,且無(wú)需包含井號(hào)(#)。
復(fù)雜元素
富文本
富文本(Rich Text)是相對(duì)于純文本(Plain Text)而言的一種文本格式。它不僅包含文字內(nèi)容,還支持為文字添加各種格式屬性及多媒體元素,從而呈現(xiàn)出更為豐富的視覺(jué)效果。這種格式的編輯和處理也可以通過(guò)在Microsoft Word軟件中預(yù)先定義字符樣式來(lái)實(shí)現(xiàn)。
在python-docx-template中使用富文本的步驟如下:
- 在Word模板中,為富文本定義一個(gè)占位符。例如,使用
{{r rich_text_var}},其中的r表示富文本。 - 在Python代碼中,利用RichText類構(gòu)建帶有格式的文本內(nèi)容。
- 渲染模板時(shí),占位符會(huì)被替換為富文本內(nèi)容,并保留所有已定義的格式樣式。
在代碼中使用RichText類時(shí),還可通過(guò)以下標(biāo)記控制文本格式:
- 換行:
\n; - 換段:
\a; - 分頁(yè):
\f。
以下為生成富文本W(wǎng)ord文檔的示例:
# 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()
# 向富文本對(duì)象中添加內(nèi)容,依次演示不同的文本格式設(shè)置
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") # 紫色文本(十六進(jìn)制顏色碼)
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) # 小字號(hào)文本(14磅)
rt.add(" or ")
rt.add("big", size=60) # 大字號(hào)文本(60磅)
rt.add(" text.")
rt.add("\nYou can add an hyperlink, here to ")
# 添加帶超鏈接的文本,build_url_id 用于創(chuàng)建URL標(biāo)識(shí)并關(guān)聯(lián)到指定鏈接
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 用于創(chuàng)建新段落
rt.add("\aA new paragraph : <cool>\a")
# \f 用于插入分頁(yè)符
rt.add("--- A page break here (see next page) ---\f")
# 循環(huán)演示不同類型的下劃線樣式
for ul in [
"single", # 單下劃線
"double", # 雙下劃線
"thick", # 粗下劃線
"dotted", # 點(diǎn)下劃線
"dash", # 短劃線下劃線
"dotDash", # 點(diǎn)劃線下劃線
"dotDotDash", # 雙點(diǎn)劃線下劃線
"wave", # 波浪線下劃線
]:
rt.add("\nUnderline : " + ul + " \n", underline=ul)
# 演示不同字體的設(shè)置
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")
# 演示上標(biāo)和下標(biāo)文本
rt.add("\n\nHere some")
rt.add("superscript", superscript=True) # 上標(biāo)文本
rt.add(" and some")
rt.add("subscript", subscript=True) # 下標(biāo)文本
# 創(chuàng)建一個(gè)新的富文本對(duì)象,并將之前創(chuàng)建的rt對(duì)象嵌入其中,實(shí)現(xiàn)富文本嵌套
rt_embedded = RichText("an example of ")
rt_embedded.add(rt)
# 構(gòu)建渲染上下文,將嵌套的富文本對(duì)象賦值給模板中的 "example" 變量
context = {
"example": rt_embedded,
}
# 將上下文數(shù)據(jù)渲染到Word模板中
tpl.render(context)
# 保存渲染后的Word文檔到指定路徑
tpl.save("output/richtext.docx")
富文本段落
若要精細(xì)控制單個(gè)段落的樣式,可在代碼中使用RichTextParagraph()或RP()對(duì)象。此對(duì)象需通過(guò){{p <var> }}語(yǔ)法添加至模板。示例代碼如下:
# 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")
# 創(chuàng)建富文本段落對(duì)象組合不同樣式的段落
rtp = RichTextParagraph()
# 創(chuàng)建富文本對(duì)象,用于設(shè)置文本的字符樣式
rt = RichText()
# 向富文本段落中添加文本,并指定自定義樣式myrichparastyle
rtp.add(
"The rich text paragraph function allows paragraph styles to be added to text",
parastyle="myrichparastyle",
)
# 添加文本并使用內(nèi)置段落樣式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"
)
# 添加文本并使用默認(rèn)普通段落樣式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") # 方形項(xiàng)目符號(hào)樣式
rtp.add("Numbered Bullet Points", parastyle="BasicNumbered") # 數(shù)字編號(hào)樣式
rtp.add("and Alpha Bullet Points.", parastyle="alphaBracketNumbering") # 字母編號(hào)樣式
# 演示文本對(duì)齊方式的設(shè)置
rtp.add("You can", parastyle="normal") # 默認(rèn)左對(duì)齊
rtp.add("set the", parastyle="centerAlign") # 應(yīng)用自定義樣式居中對(duì)齊
rtp.add("text alignment", parastyle="rightAlign") # 應(yīng)用自定義樣式右對(duì)齊
# 演示行間距樣式:緊湊行間距
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"
)
# 構(gòu)建富文本字符串,演示字符級(jí)樣式
rt.add("This works with ")
rt.add("Rich ", bold=True) # 加粗
rt.add("Text ", italic=True) # 斜體
rt.add("Strings", underline="single") # 單下劃線
rt.add(" too.")
# 將富文本對(duì)象添加到富文本段落中,并指定方形項(xiàng)目符號(hào)樣式
rtp.add(rt, parastyle="SquareBullet")
# 構(gòu)建渲染上下文,將富文本段落對(duì)象綁定到模板中的example變量
context = {
"example": rtp,
}
# 將上下文數(shù)據(jù)渲染到 Word 模板中
tpl.render(context)
# 保存渲染后的 Word 文檔到指定路徑
tpl.save("output/richtext_paragraph.docx")
浮動(dòng)對(duì)象
圖片插入
可在文檔中動(dòng)態(tài)插入單張或多張圖片,當(dāng)前已支持JPEG和PNG格式。只需在模板中使用{{ <var> }}格式的標(biāo)簽即可。圖片嵌入代碼如下:
myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))
使用時(shí)應(yīng)傳入模板對(duì)象與圖片文件路徑,寬高為可選參數(shù)。寬度和高度需通過(guò)Mm(毫米)、Inches(英寸)或 Pt(磅)等類進(jìn)行單位定義。示例代碼如下:
# 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")
# 定義渲染模板所需的上下文數(shù)據(jù)
context = {
# 只設(shè)置寬或者高,則圖片自適應(yīng)變化
"myimage": InlineImage(tpl, "templates/python_logo.png", width=Mm(20)),
# 手動(dòng)指定寬高度
"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是否開(kāi)啟自動(dòng)轉(zhuǎn)義,把模板變量中的特殊字符轉(zhuǎn)換成無(wú)害的HTM實(shí)體
jinja_env = jinja2.Environment(autoescape=True)
# 使用上下文數(shù)據(jù)和自定義的jinja2環(huán)境渲染W(wǎng)ord模板
tpl.render(context, jinja_env)
tpl.save("output/inline_image.docx")
替換文檔中的圖片
可對(duì)文檔中的現(xiàn)有圖片進(jìn)行替換,具體操作如下:先在模板中插入一張占位圖,按常規(guī)流程渲染模板,之后再將占位圖替換為目標(biāo)圖片。該方法支持批量處理文檔內(nèi)的所有媒體文件,且替換后的圖片將維持原占位圖的縱橫比。在模板中插入圖片時(shí),只需指定文件名即可。該替換操作同時(shí)對(duì)頁(yè)眉、頁(yè)腳及正文區(qū)域生效。
例如,替換占位圖dummy_header_pic.jpg的語(yǔ)法示例如下:
tpl.replace_pic('dummy_header_pic.jpg', 'header_pic_i_want.jpg')
注意,在將圖片手動(dòng)插入Word模板時(shí),某些版本的Word會(huì)自動(dòng)對(duì)其重命名并存儲(chǔ)。這導(dǎo)致圖片在docx文件內(nèi)的實(shí)際文件名與原文件名完全不同,從而可能引發(fā)找不到圖片的問(wèn)題。若不確定圖片的具體位置,可將docx文件視為zip壓縮包進(jìn)行解壓,隨后在word\document.xml文件中,通過(guò)查找pic:nvPicPr節(jié)點(diǎn)來(lái)定位圖片信息。具體可以參考:python使用docxtpl庫(kù)實(shí)現(xiàn)圖片替換功能
在找到目標(biāo)圖片對(duì)應(yīng)的pic:nvPicPr節(jié)點(diǎn)后,其name屬性的值即為圖片在文檔中實(shí)際存儲(chǔ)的文件名。此處的文件名可能不包含圖片格式后綴,替換圖片的示例代碼可能如下:
tpl.replace_pic('Picture 1','header_pic_i_want.jpg')
子文檔
模板變量可包含復(fù)雜的子文檔對(duì)象,且能通過(guò)python-docx的文檔操作方法從零構(gòu)建。具體步驟為:先從模板對(duì)象中獲取子文檔對(duì)象,再將其當(dāng)作python-docx文檔對(duì)象使用。該功能需要安裝額外依賴:
pip install "docxtpl[subdoc]"
以下代碼展示了如何創(chuàng)建子文檔,并將該子文檔嵌入到主模板中,最終生成并保存新的文檔:
# 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. 創(chuàng)建一個(gè)新的子文檔對(duì)象
sd = tpl.new_subdoc()
# 3. 向下文檔中添加段落內(nèi)容
p = sd.add_paragraph("This is a sub-document inserted into a bigger one")
p = sd.add_paragraph("It has been ")
# 為文本片段設(shè)置自定義樣式
p.add_run("dynamically").style = "dynamic"
p.add_run(" generated with python by using ")
# 為文本片段設(shè)置斜體樣式
p.add_run("python-docx").italic = True
p.add_run(" library")
# 4. 向下文檔添加標(biāo)題
sd.add_heading("Heading, level 1", level=1)
# 向下文檔添加帶樣式的段落(IntenseQuote樣式為內(nèi)置樣式)
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 :")
# 創(chuàng)建表格(初始1行3列,作為表頭)
table = sd.add_table(rows=1, cols=3)
# 獲取表頭單元格并設(shè)置內(nèi)容
hdr_cells = table.rows[0].cells
hdr_cells[0].text = "Qty"
hdr_cells[1].text = "Id"
hdr_cells[2].text = "Desc"
# 定義表格數(shù)據(jù)
recordset = ((1, 101, "Spam"), (2, 42, "Eggs"), (3, 631, "Spam,spam, eggs, and ham"))
# 循環(huán)添加數(shù)據(jù)行到表格
for item in recordset:
row_cells = table.add_row().cells
row_cells[0].text = str(item[0]) # 數(shù)量(轉(zhuǎn)為字符串)
row_cells[1].text = str(item[1]) # ID(轉(zhuǎn)為字符串)
row_cells[2].text = item[2] # 描述
context = {
"mysubdoc": sd,
}
tpl.render(context)
tpl.save("output/subdoc.docx")
此外自python-docx-template V0.12.0版本起,支持將已有的.docx文件作為子文檔進(jìn)行合并,如下所示:
# 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")
補(bǔ)充操作
轉(zhuǎn)義操作
默認(rèn)情況下,python-docx-template不會(huì)對(duì)內(nèi)容進(jìn)行轉(zhuǎn)義。這是因?yàn)樵谑褂媚0逭Z(yǔ)法修改Word文檔時(shí),底層實(shí)際上是在處理XML結(jié)構(gòu),而像<、> 和&這樣的字符在XML中具有特殊含義,必須經(jīng)過(guò)轉(zhuǎn)義才能正確顯示。以下提供幾種實(shí)現(xiàn)轉(zhuǎn)義的方法:
上下文定義時(shí)使用R()包裹內(nèi)容,模板中添加r標(biāo)記:
context = { 'var': R('my text') }
模板中寫(xiě)法:{{r <var> }}
上下文保持原始字符串,模板中使用|e過(guò)濾器:
context = { 'var':'my text' }
模板中寫(xiě)法:{{ <var>|e }}
上下文使用escape()函數(shù)處理內(nèi)容,模板直接引用:
context = { 'var': escape('my text') }
模板中寫(xiě)法:{{ <var> }}
調(diào)用render方法時(shí)啟用自動(dòng)轉(zhuǎn)義,默認(rèn)值為autoescape=False:
tpl.render(context, autoescape=True)
在文檔中插入代碼清單時(shí),如果需要同時(shí)轉(zhuǎn)義文本并處理?yè)Q行符(\n)、段落符(\a)和換頁(yè)符(\f),可以在Python代碼中使用Listing類。
例如:
context = {
'mylisting': Listing('the listing\nwith\nsome\nlines \a and some paragraph \a and special chars : <>&')
}
在模板中直接引用即可:
{{ mylisting }}
使用Listing()能夠保持當(dāng)前字符樣式,除非遇到\a分隔符,它會(huì)在該位置開(kāi)始一個(gè)新的段落。
使用示例如下:
# 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渲染后的特殊字符%、{或},可以使用以下方式進(jìn)行轉(zhuǎn)義:
- 顯示
%%→ 使用{_% %_} - 顯示
{{ }}→ 使用{_{ }_}
獲取未定義變量
要獲取模板渲染時(shí)所需的未定義變量,可以按照以下步驟操作:
from docxtpl import DocxTemplate
tpl = DocxTemplate('template.docx')
context = {'name': '張三', 'age': 30}
# 注意:department變量未定義
# 檢測(cè)缺失變量
missing = tpl.get_undeclared_template_variables(context=context)
print(f"缺失的變量: {missing}")
# 獲取所有變量(不傳context)
all_vars = tpl.get_undeclared_template_variables()
print(f"模板所有變量: {all_vars}")
模板文檔的內(nèi)容包含:
姓名:{{name}}
年齡:{{age}}
部門(mén):{{department}}
若未傳遞 context 參數(shù),該方法將返回模板中所有需要賦值的變量名的集合。此結(jié)果可用于向用戶提示輸入,或?qū)懭胛募┖罄m(xù)手動(dòng)處理。
Jinja自定義過(guò)濾器
Python-docx-template的render方法支持一個(gè)可選參數(shù)jinja_env,作為其第二個(gè)參數(shù)。通過(guò)傳遞自定義的Jinja環(huán)境對(duì)象,可以實(shí)現(xiàn)自定義過(guò)濾器的添加,從而靈活擴(kuò)展數(shù)據(jù)處理邏輯。以下為具體示例。
模板word內(nèi)容如下:
=== 姓名展示對(duì)比 ===
原始名字: {{ name }}
方法A (Python處理): {{ name_upper }}
方法B (模板過(guò)濾器): {{ name|upper }}
對(duì)應(yīng)的Python渲染代碼:
from docxtpl import DocxTemplate
import jinja2
# 準(zhǔn)備數(shù)據(jù)
data = {'name': 'zhangsan'}
# Python中處理
data['name_upper'] = data['name'].upper()
# 定義過(guò)濾器
def my_upper(text):
return text.upper()
doc = DocxTemplate("template.docx")
#創(chuàng)建環(huán)境并注冊(cè)過(guò)濾器
env = jinja2.Environment()
env.filters['upper'] = my_upper
doc.render(data, env)
doc.save("output/compare.docx")
print(f"原始數(shù)據(jù): {data['name']}")
print(f"Python處理結(jié)果: {data['name_upper']}")
print(f"模板過(guò)濾器結(jié)果: {data['name'].upper()}")
Microsoft Word 2016特殊行為說(shuō)明
MS Word 2016會(huì)忽略文檔中的制表符,這是該版本特有的情況。類似LibreOffice或WordPad等其他編輯工具則無(wú)此問(wèn)題。同樣,以Jinja2標(biāo)簽開(kāi)頭且?guī)в星皩?dǎo)空格的行,其空格也會(huì)被忽略。
為解決上述問(wèn)題,可使用RichText進(jìn)行處理:
tpl.render({
'test_space_r': RichText(' '),
'test_tabs_r': RichText(5 * '\t'),
})
在模板中,通過(guò){{r ... }}語(yǔ)法調(diào)用:
{{r test_space_r}} 空格將被保留
{{r test_tabs_r}} 制表符將正常顯示
到此這篇關(guān)于python使用python-docx-template實(shí)現(xiàn)模板化Word文檔生成指南的文章就介紹到這了,更多相關(guān)python模板化生成Word內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python高效轉(zhuǎn)換Word表格為Excel的方案全解析
Python通過(guò)python-docx庫(kù)讀取Word表格,用openpyxl或pandas寫(xiě)入Excel可自動(dòng)化完成將Word表格轉(zhuǎn)為Excel的需要,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2026-02-02
pytorch 實(shí)現(xiàn)多個(gè)Dataloader同時(shí)訓(xùn)練
這篇文章主要介紹了pytorch 實(shí)現(xiàn)多個(gè)Dataloader同時(shí)訓(xùn)練的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05
Python讀取Ansible?playbooks返回信息示例解析
這篇文章主要為大家介紹了Python讀取Ansible?playbooks返回信息示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
pyqt5讓圖片自適應(yīng)QLabel大小上以及移除已顯示的圖片方法
今天小編就為大家分享一篇pyqt5讓圖片自適應(yīng)QLabel大小上以及移除已顯示的圖片方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06
使用tqdm顯示Python代碼執(zhí)行進(jìn)度功能
在使用Python執(zhí)行一些比較耗時(shí)的操作時(shí),為了方便觀察進(jìn)度,通常使用進(jìn)度條的方式來(lái)可視化呈現(xiàn)。這篇文章主要介紹了使用tqdm顯示Python代碼執(zhí)行進(jìn)度,需要的朋友可以參考下2019-12-12
Python操作json數(shù)據(jù)的一個(gè)簡(jiǎn)單例子
這篇文章主要介紹了Python操作json數(shù)據(jù)的一個(gè)簡(jiǎn)單例子,需要的朋友可以參考下2014-04-04

