经常上级会下发一些word表格让我们基层填写,涉及人数较多,表格下发给个人之后,格式啊字体啊大小啊都不统一,每次还得重复校对,我就想能不能创建一个大型Excel数据库完成平时信息采集和更新,一次性导入下发的word表格中,并生成新的文件。
使用Excel的原因主要是维护方便,并不是所有人都懂数据库操作,并且制作较为麻烦,尤其是遇到新加字段的时候,工作量大,不如就使用Excel存储数据,维护简单,更新及时。
现在就再想,我码个程序,输入我需要变更的数据列名,替换掉Word模板当中的相应内容,并另存为以名称命名的新文件。但由于数据格式多,所以要求EXCEL处理的时候全部都是文本格式,这样不容易出错,如果转换为.csv格式会更加简洁。
Python 3.7 (32bit)
Pycharm
Windows7 (64bit)
Office 2010
先看一下我的数据:
第一步,将Excel所有单元格设置为文本后,检查容易出错的几个类型,日期,数字,金额等;
第二步,将第一行全部改为英文描述,利于后期Python编译查找速度,及时不需要的也最好一次性都编译好,以免后期需要再重复工作;
第三步,另存为 csv 格式文件,一定选逗号分隔的,如图:
模板处理使用的是 jinja2 标签 { {xxx}}的替换模式,将所需要的替换的位置用数据的列名称替换,如图:
这里替换标签里面的内容一定要和数据源的一致,并且和后面程序中的一致。
使用32位Python的原因是兼容性,64位的Python打包的程序只能64位系统使用,32位的Python打包的程序可以在32位、64位系统上使用。
本程序一共需要额外安装3个库
pywin32(32位系统打包程序必须装)
pyinstaller(打包程序)
docxtpl(模板替换)
打开Pycharm的File->Settings
点击加号,依次添加3个额外库。
引用头:
from tkinter import * #tkinter-python内部GUI库
from tkinter import filedialog #tkinter-python内部GUI库获取文件绝对路径
import tkinter.messagebox #tkinter-python内部GUI库弹出消息库
import csv #数据CSV格式处理库
from docxtpl import DocxTemplate #Word模板替换库
先介绍一下docxtpl Word模板替换 最简单的使用方法:
tpl = DocxTemplate('leave_temp.docx')
#括号后是相对路径,我们采取选取绝对路径方式
# 需要替换内容以key:value的方式进行更换
context = {
'who': '程旭阳'
}
tpl.render(context)
#渲染替换
tpl.save('leave.docx')
#另存为
本文中需要替换的是 No Name Age Id Addr
我们这里考虑,以迭代的方式一个文件一个文件生成,context字典的键我们人工输入,这样有一个好处就是不需要处理多余的数据源数据,可以指定内容替换。那么久需要获取输入框的内容,也就是tkinter->entry组件,我们先建立一个Entry()组件和Button()按钮完成,输入需要更换N个变量,点击确定按钮后会更新出N个输入框来供我们填写。
#创建新的输入栏,输入变量名称
def newentry():
for i in range(int(replacenumber.get())):
entry_x = 'entry_' + str(i)#构造变量名称
entries.append(entry_x)#储存到变量列表中方便查询
if i<20 :
entries[i] = Entry(frm21, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
elif i<40:
entries[i] = Entry(frm22, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
else:
entries[i] = Entry(frm23, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
#
replacenumber.get()会获取输入框内输入了几个需要替换的变量,之后生成。
其中entry_x是传递变量名称的函数,然后储存到列表 entries中去
e.g. 如果是5,那么会创造5个变量,分别是
entry_0、entry_1、entry_2、entry_3、entry_4
Entry()和其他的组件的用法请参看https://www.jianshu.com/p/c9fb28367015
【由于tkinter没有自适应布局,只好选绝对布局的方式,摆放各个部件,这里只允许创建最多60个输入框,你也可以更改】
替换的程序主要内容:
def load():
filepath = filedialog.askopenfilename() #获取数据源文件绝对地址
with open(filepath,'r',encoding='gbk') as f:
reader = csv.DictReader(f,dialect='excel') #获取数据源内容
#使用csv不用pandas的原因是含pandas库打包exe程序出错
#遍历每一行将数据替换给新的Word
for row in reader:
#创建完整替换context字典,利用原先的列表
for entry in entries:
context[entry.get()] = row[entry.get()]
#写好输出文件夹的路径,且以 Name 一列命名,也可以使用其他的命名
outputpath = 'output/'+row['Name']+'.docx'
# 替换模板,地址为获取的模板绝对地址,docxpath是一储存绝对路径信息label
tpl = DocxTemplate(docxpath['text'])
tpl.render(context)
tpl.save(outputpath)
tkinter.messagebox.showinfo('成功', '已替换所有模板!')
#完成后弹窗提示
最后就是GUI tkinter布局的问题了,难度不大,大家稍微看了上面的链接就懂布局了。
最后我粘贴一下全部代码,解释内容较少
from tkinter import *
from tkinter import filedialog
import tkinter.messagebox
import csv
from docxtpl import DocxTemplate
#选取模板
def seltpl():
tplpath = filedialog.askopenfilename()
label['text'] = '4.已选择模板,请选择数据源CSV'
docxpath['text'] = tplpath
#读取CVS文件绝对路径后保存数据到df中
def load():
filepath = filedialog.askopenfilename()
with open(filepath,'r',encoding='gbk') as f:
reader = csv.DictReader(f,dialect='excel')
for row in reader:
#创建完整替换context字典
for entry in entries:
context[entry.get()] = row[entry.get()]
outputpath = 'output/'+row['Name']+'.docx'
# 替换模板
tpl = DocxTemplate(docxpath['text'])
tpl.render(context)
tpl.save(outputpath)
tkinter.messagebox.showinfo('成功', '已替换所有模板!')
#创建新的输入栏,输入变量名称
def newentry():
for i in range(int(replacenumber.get())):
entry_x = 'entry_' + str(i)#构造变量名称
entries.append(entry_x)#储存到变量列表中方便查询
if i<20 :
entries[i] = Entry(frm21, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
elif i<40:
entries[i] = Entry(frm22, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
else:
entries[i] = Entry(frm23, width=18)
entries[i].pack(side=TOP, expand=True, fill=X)
#主窗口top实例化
top = Tk()
top.geometry("400x600")
top.title("使用前必须阅读说明书")
context = {}
entries = []
#frm1
frm1 = Frame()
frm1.pack(side=TOP)
label2 = Label(frm1, text='1.请输入需要替换几个变量', font=("黑体", 20), wraplength=400, justify="left", anchor="nw")
label2.pack(side=TOP, expand=YES, fill=X)
#frm11\frm12
frm11 = Frame(frm1)
frm11.pack()
replacenumber = Entry(frm11)
replacenumber.pack(side=LEFT, fill=X, expand=YES)
Button(frm11, text='确定', command=newentry).pack(side=LEFT, fill=X, expand=YES)
#frm2
frm2 = Frame()
frm2.pack(side=TOP)
label3 = Label(frm2, text='2.请将变量名称填写在下列输入框框内', font=("黑体", 16), wraplength=400, justify="left", anchor="nw")
label3.pack(side=TOP, expand=YES, fill=X)
frm21 = Frame(frm2)
frm21.pack(side=LEFT)
frm22 = Frame(frm2)
frm22.pack(side=LEFT)
frm23 = Frame(frm2)
frm23.pack(side=LEFT)
frm3 = Frame()
frm3.pack(side=BOTTOM)
#储存模板位置标签栏(不显示)
docxpath = Label(top, text='', bg="pink", font=("黑体", 20), wraplength=1, justify="left", anchor="nw")
#frm3提示信息标签栏
label = Label(frm3, text='3.请先选择需要替换的DOCX模板', font=("黑体", 20), wraplength=400, justify="left", anchor="nw")
label.pack(side=TOP)
Button(frm3, text='3.选择模板', command=seltpl).pack(side=LEFT, fill=X, expand=YES)
Button(frm3, text='4.选择数据源并开始替换', command=load).pack(side=LEFT, fill=X, expand=YES)
#启动程序
top.mainloop()
在Pycharm 的终端Terminal 输入
pyinstaller -F -w ETK.py
完成打包exe文件,输出到项目地址dist文件夹中,此时人工添加output文件夹(没来及做,有时间的可以自己代码生成)
打开ETK.exe文件
输入变量5个,确定后输入变量名称,注意一定要写正确
点击选择模板,选择好模板文件 .docx,然后点击选择数据源CSV,之后就会弹窗说替换完成,我们可以去output文件夹下面去看一下。
确实生成了新的文件,打开其中一个。
已完成替换。