工作需要,每次项目报告都需要填写许多表单,其中有很多字段仅需要简单替换,因此自己做了个根据提示符替换多个word多个内容的小程序,并保留原格式。
首先我把模板word中的代替换部分全部改为带有启动子#的提示符,如#name,#add,再创建一个字典,其键值对分别为提示符和替换内容。
dic = {'#co_name': '名称', '#co_add': '地址'}
现在提供三种替换方法:
方法1:段落替换
doc = docx.Document('test.docx')
for para in doc.paragraphs:
for key,value in dic.items():
if key in para:
para.text=para.text.replace(key,value)
这么做的优点是方便快捷,如果你不在乎替换掉的格式,替换内容的样式默认为段落样式,你可以在模板word中设置段落的样式进行统一控制。
若段落样式和替换内容的样式不同,该方法就无法实现。例:这是要替换的内容#name。替换后的#name并不会保持红色加粗,而是和段落样式保持一致。
方法2:runs替换(简易)
在paragraphs之下,还可以分为runs,runs即为连续的字块。
doc = docx.Document('test.docx')
for para in doc.paragraphs:
for run in para.runs:
if run.text in list(dic.keys())
run.text=run.text.replace(run.text,dic[run.text])
这样即可实现替换内容保持原样式,但这么做存在弊端,即提示符可能会被拆分为多个run。如果只做简单替换,提示符为name,采用上述方法即可。若替换内容众多,且提示符需要在word中一目了然,如上述采用了#co_name,那么该提示符会被分成4个runs,如#,co,_,name。此时就需要采用方法3。
方法3:runs替换(复杂)
for para in doc.paragraphs:
runs = para.runs
for i, run in enumerate(runs):
if run.text == '#':
count = i # 记录启动子位置
tmp = '#' # tmp写入启动子
while tmp not in list(dic.keys()): # tmp继续写入启动子后的run,直到tmp和dic中的键匹配
count += 1
tmp += runs[count].text
runs[count].clear()
runs[i].text = runs[i].text.replace(runs[i].text, dic[tmp])
方法3实现了复杂提示符的替换功能,并保留原样式,实现逻辑如下:
- 启动子#会被单独识别为一个run,故遍历到启动子#时,替换过程开启;
- count记录该提示符#是第几个run;
- 不断向tmp中添加runs,直到tmp等于dic中的某个键名(while循环)
- 向tmp添加run的同时,不断删除原本的run,即runs[count].clear()。可以理解为把启动子#后的run剪切到tmp中了;
- while循环结束后开始替换,还记的count是记录提示符#的位置吗,直接将提示符#替换为替换内容,替换终止。
以上,我们便完成了三种方法的word文档内容替换,可以做如下总结:
- 如果不关心替换内容样式,用方法1
- 如果要求替换内容保留原样式,但提示符可以被识别为一个run,用方法2
- 如果要求替换内容保留原样式,但提示符不能被识别为一个run,用方法3
注:提示符之间不能有包含关系,如#co_name和#co_name_en,这样会导致后者无法被识别,这在word中手工替换也是同样的道理。
此外,如果你还想替换word中的表格、页眉、页脚内容,只需要在上面多加几个循环:
1、表格内容替换
for table in doc.tables:
for row in table.rows:
for cell in row.cells
for para in cell.paragraphs:
'''
后续内容同上述三个方法
'''
2、页眉页脚内容替换
for sec in doc.sections:
for para in sec.header.paragraphs:
'''
后续内容同上述三个方法,如果想替换页脚,把header改为footer
'''