今天替老婆做一个小小的功能:
需求是这样的,她在银行对一个检查事项需要出具几份文档,例如《报告A》,《报告B》,《报告C》等。
但是这几份报告里面其实很多内容是重复的,例如人名,或者一些评价等等若干信息。
她希望可以不重复输入这些东西,而制作一些模板,来批量完成这项工作。这样对于大量的检查来说可以省掉不少工作量。
我隐约记得 word 里面有一插入域的功能,通过不同的 word 模板里面插入一些域作为钩子,然后引用到一个 access 文件,这样来进行批量的更新,但是由于其实我还是不太懂 word,于是浅尝辄止。
然后正好 python 的 office api 也是很快需要派上用场的,而且 python 都比较容易绿化所以干脆就写一个 python 的替换器吧。
简单的设想是这样的,做一个配置文件 conf.xlsx:
模板文件
输出目录
替换字段
替换内容
form1.docx
out
a
a for aster
form2.docx
b
b for bullets
c
c for ceasar
d
d for demon
稍微简单解释一下:
第一列是模板的文件,可以是若干个 word 文件,通过相对路径或者绝对路径指定。word 文件里面可以填写若干个 { {替换字段}} 这样的标签用于匹配替换。
第二列是输出目录,替换之后的文件将会被输出到指定的目录另存,也可以接受相对目录或者绝对路径的目录。
第三第四列是替换的字典,key 是第三列“替换字段”,value 第四列是替换内容。然后对于所有的模板文件中的 { {key}} 将会被全部替换成 value。
这样用起来已经可以省掉相当多的工作了,下面是实施这个脚本。
首先是找一个恰当的 python office api,以前没搞过,但是一下子就找到一个合适的了:
下载合适的版本进行安装,然后具体的使用可以 import win32com 这个包。
为了简化操作,我还在百度文库找到了一个简易版 word 封装:
# easyword.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import win32com.client
import os
#--------------------------------------------------------------------------
class easyWord:
'''
Some convenience methods for Excel documents accessed
through COM.
'''
def __init__(self,visible=False):
self.wdApp = win32com.client.DispatchEx('Word.Application')
self.wdApp.Visible = visible
self.wdApp.DisplayAlerts = False
def new(self,filename=None):
'''
Create a new Word document. If 'filename' specified,
use the file as a template.
'''
if filename:
return self.wdApp.Documents.Add(filename)
else:
return self.wdApp.Documents.Add()
def open(self,filename):
'''
Open an existing Word document for editing.
'''
return self.wdApp.Documents.Open(filename)
def visible(self,visible=True):
self.wdApp.Visible = visible
def find(self,text,MatchWildcards=False):
'''
Find the string
'''
find = self.wdApp.Selection.Find
find.ClearFormatting()
find.Execute(text, False, False, MatchWildcards, False, False, True, 0)
return self.wdApp.Selection.Text
def replaceAll(self,oldStr,newStr):
'''
Find the oldStr and replace with the newStr.
'''
find = self.wdApp.Selection.Find
find.ClearFormatting()
find.Replacement.ClearFormatting()
find.Execute(oldStr, False, False, False, False, False, True, 1, True, newStr, 2)
def updateToc(self):
for tocitem in self.wdApp.ActiveDocument.TablesOfContents:
tocitem.Update()
def save(self):
'''
Save the active document
'''
self.wdApp.ActiveDocument.Save()
def saveAs(self,filename,delete_existing=True):
'''
Save the active document as a different filename.
If 'delete_existing' is specified and the file already
exists, it will be deleted before saving.
'''
if delete_existing and os.path.exists(filename):
os.remove(filename)
self.wdApp.ActiveDocument.SaveAs(FileName=filename)
def close(self):
'''
Close the active workbook.
'''
self.wdApp.ActiveDocument.Close()
def quit(self):
'''
Quit Word
'''
return self.wdApp.Quit()
这个作为一个模块,我们通过 from easyword import easyWord 即可导入这个类,这下就简单了。
然后实现一个方法 replaceWord:
# replace.py
import os
from easyword import easyWord
def replaceWord(data, tmpl_path=[], export_path=['export']):
for path in tmpl_path:
path = os.path.relpath(path)
if os.path.exists(path):
w = easyWord()
w.open(os.path.abspath(path))
print('打开:%s' % os.path.abspath(path))
for k in data:
w.replaceAll('{ {%s}}'%k, data[k])
for ex in export_path:
ex = os.path.join(os.path.abspath(ex), path)
if not os.path.exists(os.path.dirname(ex)):
os.makedirs(os.path.dirname(ex))
print('输出:%s' % ex)
w.saveAs(ex)
w.close()
w.quit()
然后我们调用 replaceWord 函数:
data 是替换的字典 {'替换字段': '替换内容', ...}
tmpl_path 是模板文件的列表(支持相对或者绝对路径)
export_path 是输出目录的路径
举个例子:
replaceWord(
{
'a':'a for apple',
'b':'b for bullets',
'c':'c for ceaser',
'd': 'd for damn',
'e': 'e for eclipse',
},
(
'form1.docx',
'form2.docx',
),
(
'a/a',
'b/b',
)
)
这样调用就可以实现一个批量操作。
好了,最后一步,我们需要从指定的 conf.xlsx 来读取这些参数并且按照参数执行这个过程。
同样,在百度轻易找到了一个建议封装的 Excel 类库:
# easyexcel.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from win32com.client import Dispatch
import win32com.client
class easyExcel:
"""A utility to make it easier to get at Excel. Remembering
to save the data is your problem, as is error handling.
Operates on one workbook at a time."""
def __init__(self, filename=None):
self.xlApp = win32com.client.DispatchEx('Excel.Application')
if filename:
self.filename = filename
self.xlBook = self.xlApp.Workbooks.Open(filename)
else:
self.xlBook = self.xlApp.Workbooks.Add()
self.filename = ''
def save(self, newfilename=None):
if newfilename:
self.filename = newfilename
self.xlBook.SaveAs(newfilename)
else:
self.xlBook.Save()
def close(self):
self.xlBook.Close(SaveChanges=0)
del self.xlApp
def getCell(self, sheet, row, col):
"Get value of one cell"
sht = self.xlBook.Worksheets(sheet)
return sht.Cells(row, col).Value
def setCell(self, sheet, row, col, value):
"set value of one cell"
sht = self.xlBook.Worksheets(sheet)
sht.Cells(row, col).Value = value
def getRange(self, sheet, row1, col1, row2, col2):
"return a 2d array (i.e. tuple of tuples)"
sht = self.xlBook.Worksheets(sheet)
return sht.Range(sht.Cells(row1, col1), sht.Cells(row2, col2)).Value
def addPicture(self, sheet, pictureName, Left, Top, Width, Height):
"Insert a picture in sheet"
sht = self.xlBook.Worksheets(sheet)
sht.Shapes.AddPicture(pictureName, 1, 1, Left, Top, Width, Height)
def cpSheet(self, before):
"copy sheet"
shts = self.xlBook.Worksheets
shts(1).Copy(None,shts(1))
于是我们可以通过如下方式获取到配置(在 replace.py 的后面加上这一段代码,然后就可以直接跑出结果来了):
# replace.py
from easyexcel import easyExcel
x = easyExcel(os.path.abspath('conf.xlsx'))
tmpl_path = []
while x.getCell('sheet1', len(tmpl_path)+2, 1):
tmpl_path.append(x.getCell('sheet1', len(tmpl_path)+2, 1))
export_path = []
while x.getCell('sheet1', len(export_path)+2, 2):
export_path.append(x.getCell('sheet1', len(export_path)+2, 2))
if not export_path:
export_path = ['export']
data = {}
while x.getCell('sheet1', len(data)+2, 3):
data[x.getCell('sheet1', len(data)+2, 3)] = x.getCell('sheet1', len(data)+2, 4)
x.close()
replaceWord(data, tmpl_path, export_path)
【转载请附】愿以此功德,回向 >>