Background
- 最近做的项目,需要根据模板生成报表。思路就和Java中使用EasyExcel一样,模板中使用占位符,然后替换成数据。
- python3中操作docx可以使用python-docx。但是替换占位符时出现了问题,查找的基本都是基于
paragraph
替换,会改变原有的格式,自己经过摸索,最终找到一个解决办法,是基于run
。- python-docx官方文档地址
1、python-docx中概念介绍
python-docx在操作docx文档时分为Document - Paragraph - Run三级结构
- 文档 Document:就是docx文件的逻辑表示;
- 段落 Paragraph:docx文件中的段落;
- 文字块 Run:Run 的概念比较难理解,且文字块间的划分也是很神奇的,所以我们后面需要验证下模板是否可用,即判断我们的占位符是否被划分成了一个文字块。
2、开始准备模板
如下图模板中,以
ph_
开头的斜体字即为占位符,后面需要替换成相应的数据。
3、测试模板是否可用
为什么需要测试模板可用?
- 因为这个run文字块的划分真的很神奇。
- 例如:
ph_date_start
这个占位符,如果ph_date
和_start
不是同一时间写的即使你肉眼看着格式是一样的也会被划分为不同的文字块,遇到这种情况,只需要删除用键盘再敲出来就行了。- 如果我们的占位符没有被拆成多个文字块就算测试通过了(即使多个占位符划分在了一个文字块也没关系),如下图所示。
- 测试源码
from docx import Document
def main():
"""主函数"""
file = 'reports/tpl/template.docx'
doc = Document(file)
paragraphs = doc.paragraphs
for paragraph in paragraphs:
for run in paragraph.runs:
if 'ph_' in run.text:
print(f'{run.italic}==={run.text}')
if __name__ == '__main__':
main()
4、替换占位符
-
最终效果
-
替换源码
from docx import Document
def replace_placeholder(doc, params):
"""替换占位符"""
for paragraph in doc.paragraphs:
for param in params:
pv = str(params[param])
ph = f'ph_{param}'
if ph in paragraph.text:
for run in paragraph.runs:
if ph in run.text:
run.text = run.text.replace(ph, pv)
run.italic = False
def main():
"""主函数"""
tpl_doc = 'reports/tpl/建研院通用报表-template.docx'
res_doc = 'reports/res/result.docx'
doc = Document(tpl_doc)
params = {
"num_section": 1,
"subject": "监测数据分析",
"date_start": "2022年02月",
"date_start": "2022年02月",
"date_end": "2022年03月",
"name_project": "塔筒结构健康监测",
"names_types": "应变,索力,GPS,风速,风向",
"count_target_all": 100,
}
replace_placeholder(doc, params)
doc.save(res_doc)
if __name__ == '__main__':
main()