1. 实战Word批量

需要处理批量替换word的一些数据,数据源从Excel中来。

Excel的百分数会变为数字,以及浮点数会多好多精度,为了原汁原味的数据,直接复制数据到文本文件。通过\t来分隔即可,最后一个值多\n得注意。

然后在Word中加变量用{XXXX}格式的得转一下{},时间关系,用了 TEMP_XXX之类的,str.replace()去替换模板数据即可。女朋友发现Word有邮件合并功能,类似模板替换。

2. 进阶-GUI工具

2.1 预备,查漏补缺

1)界面

看《PyQt快速开发与实战》学习Qt designer生成ui、通过eric6或者命令编译py文件、信号槽机制、简单的如何让界面和逻辑分离,以及以前的PyQt入门,打算直接上手做。

界面逻辑分离可以通过两种方式:(注 Ui_m为界面生成的py代码文件)

# coding:utf-8
from PyQt5.QtWidgets import QMainWindow, QApplication
from Ui_m import Ui_MainWindow
import sys class MainWindow(QMainWindow):
def __init__(self):
super().__init__() def open_file(self):
print('open file...') if __name__ == "__main__":
app = QApplication(sys.argv)
main_window_obj = MainWindow()
ui = Ui_MainWindow()
ui.setupUi(main_window_obj)
main_window_obj.show()
sys.exit(app.exec_())
# coding:utf-8
from PyQt5.QtWidgets import QMainWindow, QApplication
from Ui_m import Ui_MainWindow
import sys class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self) def open_file(self):
print('open file...') if __name__ == "__main__":
app = QApplication(sys.argv)
main_window_obj = MainWindow()
main_window_obj.show()
sys.exit(app.exec_())
2) Excel数据处理

用常规的sheet.cell_value(i, j)获取数据,会有一些意外的情况,比如有些数字之后会多.0,百分比会是小数,小数多精度,太大的数字为科学计数法,日期也为浮点数,总之就是所得非所见。要所见即所得的话,直接复制,存取文本文件吧,每一列默认通过\t区分。

3) 数据读取

基本的Excel和Word数据读取:

import xlrd
import docx def read_xls():
""" 读取excel """
workbook = xlrd.open_workbook(r'02.xls')
sheet = workbook.sheet_by_index(0) cols = sheet.ncols
rows = sheet.nrows data = []
for i in range(rows): if i==0:
continue
row_content = []
for j in range(1, cols):
# print(sheet.cell_value(i, j), sheet.cell(i, j).ctype)
row_content.append(str(sheet.cell_value(i, j))) data.append(row_content)
return data def read_docs():
""" 读取word数据 """
doc=docx.Document(r'./01.docx')
text = []
for i in doc.paragraphs:
text.append(i.text)
data = '\n'.join(text)
return data if __name__ == '__main__':
e_data = read_xls()
w_data = read_docs()

2.2 主要流程

  • 打开word模板(需要手动输入所有的模板变量)
  • 检测所有的模板变量
  • 添加一行数据
  • 定义路由规则
  • 测试数据
  • 执行多行

2.3 画界面

本来打算模板变量select选择后,radio选择相应的数据源,发现一篇Qt程序学习(三)------QTreeWidget、右键菜单、动态改变comboBox、QTreeWidgetItem的对应列的文字编辑,结合QTreeWidgetCombo Box 可以实现想要的一一对应功能。学习一番QTreeWidget和Combo Box基本操作(在逻辑小节)。

2.4 写逻辑

叮!项目压测时候,发现excel的csv文件,文本文件打开是用,逗号分隔的。可以直接处理excel了(虽然有局限,如果数据中有逗号就得更改系统配置,显然不现实)。

所以流程变为:

  • 读取docx文件和csv两个文件之后(word文件上面有,csv可以直接csv.reader()读取)
  • 添加规则(一个模板变量对应一个下拉框)
  • 生成数据

就可以了,第一条上面查漏补缺有,第三条刚开始的脚本逻辑处理都写过了,所以重点放在添加规则,首先需要熟悉一下QTreeWidgetCombo Box

1) QTreeWidget 操作

实例化

self.treeWidget = QtWidgets.QTreeWidget(self.gridLayoutWidget)
self.treeWidget.setObjectName("treeWidget")
self.gridLayout.addWidget(self.treeWidget, 3, 4, 1, 1)

添加头部(模板变量列和数据源列)

self.treeWidget.headerItem().setText(0, _translate("MainWindow", "模板变量"))
self.treeWidget.headerItem().setText(1, _translate("MainWindow", "数据源"))

增加一项数据:(上面的实例化和头部可以用UI生成,item数据需要动态在代码添加)

child = QTreeWidgetItem(self.treeWidget)
child.setText(0, 'TEMP_COMPUTER')
child.setText(1, 'PDP-01')

ok,还带有滚动条,这样到实际用的时候,左侧模板变量数据可以通过Word文件获取,数据源通过Excel头部数据(实际上为文本)指定,通过类似select的Combo box下拉框控件。

2)Combo box 操作

常规操作

# 实例化QComBox对象
self.cb=QComboBox()
# 单个添加条目
self.cb.addItem('C')
self.cb.addItem('C++')
self.cb.addItem('Python')
# 多个添加条目
self.cb.addItems(['Java','C#','PHP'])

将上面的添加QTreeWidget添加项目结合起来:左侧为模板变量,右侧为Combo box数据

def add_qtree_item(self,  item_data):
""" 新增item """
child = QTreeWidgetItem(self.treeWidget)
# 模板变量
child.setText(0 , 'TEMP_COMPUTER')
# 数据源Combox
cb = QComboBox()
cb.addItem('PDP-8')
cb.addItem('PDP-11')
self.treeWidget.setItemWidget(child, 1 , cb)

效果如下

3)结合正式数据来绑定规则

图中读取后缀为.docx的Word文件来获取模板变量,读取后缀为.csv的Excel文件来获取头部当做数据源。

因时间关系,不打算将已选择的项加标记,只获取已选择的,再次选择时,提醒即可。但是现在这种绑定

def define_combo(self):
""" 定义combo_box """
cb = QComboBox()
cb.addItems(self.combo_box_item) # 存储所有的 combo box 实例
self.all_cb.append(cb)
# 添加一个改变的信号
cb.currentIndexChanged.connect(MainWindow.slot_change_item)
return cb @staticmethod
def slot_change_item(index):
print(index)

选定之后,也只会收到一个被选定的项目索引的信号(整数,比如:3)。看不出来是哪个Combo box实例发的信号。发现可以用lambda添加参数,直接将cb实例传过去:

    # 添加一个改变的信号
cb.currentIndexChanged.connect(lambda : self.slot_change_item(cb))
return cb def slot_change_item(self, cb_obj):
print(cb_obj)
print(cb_obj.currentIndex())
for i in self.all_cb:
print(i)

输出:(第一行是激活的cb实例,第二行是点击的item索引,三四行为之前存储的所有cb实例,发现第三行和第一行是一个实例)

<PyQt5.QtWidgets.QComboBox object at 0x04B23DA0>
3
<PyQt5.QtWidgets.QComboBox object at 0x04B23DA0>
<PyQt5.QtWidgets.QComboBox object at 0x04B23E90>

至于模板变量列和数据源列的对应关系就不劳烦QTreeWidget了,自己直接处理了。

经过一番调整,终于初具雏形了

已存在模板的时候提醒了两次,因为是这么写的

# 判断是否已有模板对应该索引,已有重置为0
if index in self.rule.values():
self.error("已存在模板对应值,已重置,请重新选择")
cb_obj.setCurrentIndex(0)
return False

第一个为1,当前为(1,0), 修改第二个为1时,检测到 1 in (1,0), 然后置0,因0 in (1,0)再次触发,所以两次提醒:打日志self.log(f"{index} in {self.rule.values()}") 输出:

已存在模板对应值,已重置,请重新选择
1 in dict_values([1, 0])
已存在模板对应值,已重置,请重新选择
0 in dict_values([1, 0])

解决办法是:

# 添加一个改变的信号 currentIndexChanged 和 activated

# cb.currentIndexChanged.connect(lambda : self.slot_change_item(cb))
cb.activated.connect(lambda : self.slot_change_item(cb))

在发送信号的时候采用activated而不是 currentIndexChanged就好了,因为currentIndexChanged每当组合框中的当前索引通过用户交互或以编程方式更改时, 都会发送此信号

user interaction or programmatically

activated 仅仅是当用户在组合框中选择项目时, 将发送此信号

接下来就需要接入之前的处理逻辑

4)接入处理Word文件逻辑

移植过来就可以了

# coding:utf-8
""" word替换处理 """ from docx import Document
import os class DocxHandle(object): def __init__(self, doc_file, data, rule, out=print):
""" DocxHandle.
Args:
doc_file: template doc file.
data: 数据
rule: 规则
out: 输出重定向
"""
self.document = Document(doc_file) # 打开文件docx文件
self.rule = rule
self.data = data
self.log = out def save(self, title):
# 新建目录
img_dir = './docxdata/'
if not os.path.exists(img_dir):
os.mkdir(img_dir) self.document.save(f"{img_dir}{title}.docx") # 保存文档
self.log(f"存储文件:{img_dir}{title}.docx") def test_rule_template(self):
for x,y in self.rule.items():
value = self.data[y]
self.log(f"{x},[{value}]") def docx(self):
data = self.data for x,y in self.rule.items():
value = data[y]
self.log(f"{x},[{value}]")
self.replace_text(x, value, self.document) def replace_text(self, old_text, new_text, file):
"""# 传入三个参数, 旧字符串, 新字符串, 文件对象"""
# 遍历文件对象
for f in file.paragraphs:
# 如果 旧字符串 在 某个段落 中
if old_text in f.text:
# self.log(f"替换前===>{f.text}")
# 遍历 段落 生成 i
for i in f.runs:
# 如果 旧字符串 在 i 中
if old_text in i.text:
# 替换 i.text 内文本资源
i.text = i.text.replace(old_text, new_text)
# self.log(f"替换后===>{f.text}")

2.5 最终结果

注意,遇到一个模板变量被拆分,看不出来。但是在Word分段解析的时候,会拆分开数据,导致不能替换。

所以如果有未检测出来的模板变量,则报错。

最终效果:

在打包之前多加了一个生成文档模式功能:参考邮件合并。(合并文档当模板有非默认字体时,得注意样式问题)

最后,需要打包exe文件:pyinstaller不支持3.7,需要下载 pyinstaller。有奇怪报错,最后采用cx_Freeze来打包:

只需要编写文件install.py

from cx_Freeze import setup, Executable

setup(  name = "Combine_docx",
version = "1.0",
description = "类似Word邮件合并功能",
executables = [Executable("./mainwindow.py")])

运行python install.py build则打包成功。

3. 其他错误

1)打包之后导入csv文件会报错
Traceback (most recent call last):
File "./mainwindow.py", line 78, in slot_open_excel_file
File "D:\Software\python3\lib\codecs.py", line 322, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd0 in position 0: invalid continuation byte

先获取到文件编码,再次使用编码打开csv文件

# 获取文件编码
en_code = 'utf-8'
with open(self.excel_file, 'rb') as f:
en_code = chardet.detect(f.read())['encoding'] with open(self.excel_file, encoding=en_code) as f:
# 解析csv文件
data = list(csv.reader(f))
2)设置程序图标

设置程序图标有多种方式,感觉用不到Designer建qrc资源文件,直接用

self.setWindowIcon(QIcon('combine.ico'))  # 设置图标

用python直接运行程序是可以有的,生成exe运行就无图标了。

cx_freeze 加参数icon只会在缩略图中有,程序左上角还是没有,够用了。

终于完整的完成了一个小GUI工具

PyQt5实现邮件合并功能(GUI)的更多相关文章

  1. Aspose.Words实现邮件合并功能和打印

    前言 最近公司要做一个B/S架构的web打印系统,主要是可以上传.下载.邮件合并.打印等等,还有就是角色的分配.用户的创建.日志记录等等,跟一般的web系统一样.可能不一样的就是需求:想把excel的 ...

  2. [原创]Devexpress XtraReports 系列 9 创建邮件合并报表

    昨天发表了Devexpress XtraReports系列第八篇[原创]Devexpress XtraReports 系列 8 创建Drill-Through报表,今天我们继续. 今天的主题是创建邮件 ...

  3. C# 创建邮件合并模板并合并文本、图片

    对于Word中的邮件合并功能,用户可以将邮件合并后的结果文档保存并打印,也可以通过邮件的形式发送,在很多场合需要使用到此功能.那对于编程人员,我们也可以在C#语言环境中通过代码的形式来实现.根据需要先 ...

  4. 第九周(1) Word邮件合并2

    第九周(1) Word邮件合并2 教学时间 2013-4-22 教学课时 2 教案序号 15 教学目标 1.进一步掌握邮件合并的技巧和方法.2.利用邮件合并制作准考证.3.掌握在同一页生成多个记录的方 ...

  5. 第八周(2) Word邮件合并1

    源自:http://www.sxszjzx.com/~c20/12-2/office-gj/files/8-2/8-2.html 第八周(2) Word邮件合并1 教学时间 2013-4-16 教学课 ...

  6. Java 在Word中创建邮件合并模板并合并文本和图片

    Word里面的邮件合并功能是一种可以快速批量操作同类型数据的方式,常见的如数据填充.打印等.其中必不可少的步骤包括用于填充的模板文档.填充的数据源以及实现邮件合并的功能.下面,通过Java程序展示如何 ...

  7. Word2010邮件合并制作成绩单

    原文链接: https://www.toutiao.com/i6488941003494392333/ 准备数据源: 选择"邮件"选项卡,"开始邮件合并"功能组 ...

  8. office------------word邮件合并(word2016版)

    虽然本人是学计算机的,但是office技能很一般,最近工作中用到了邮件合并这一功能,记录下来与大家分享. 我用到的邮件合并就是word中定好模板,从excel中批量导入数据,现实生活中,在录取通知书打 ...

  9. .NET开发邮件发送功能的全面教程(含邮件组件源码)

    今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能.在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下: 1)         邮件基础理论知识 2)         ...

随机推荐

  1. Mac os系统gdb调试器的安装与使用

    Mac os系统gdb调试器的安装与使用 1.简介 Mac os下的Terminal 终端中默认情况下并没有安装gdb调试器,但是可以通过gcc编译器进行代码编译,如果出错了就需要通过gdb调试器进行 ...

  2. position(3rd week blog)

    static:静态的默认属性,上(top)下(bottom)左(left)右(right)等参数不会执行没有影响.relative:此属性会根据上(top)下(bottom)左(left)右(righ ...

  3. HP Elitebook 830 G5/Win10蓝屏 UcmUcsi.sys 错误解决

    转自https://support.hp.com/cn-zh/document/c06038185 注意BIOS可能是英文的,别着急,对着找就好了.

  4. Web前端学习第三天(cookie 二)

    HttpOniy Cookie机制 在http层面传输cookie,当设置HttpOnly标志后,客户端脚本就无法读写该cookie.可以用此防止xss攻击获取cookie <?php setc ...

  5. 软件开发者路线图梗概&书摘chapter1

    软件技能的概念 本书目的:尝试为软件开发的新颖方法整理出一份宣言 原因:定义泛化 从敏捷开发运动中学到的经验:理解支撑规则的底层因素 软件技能的愿景:价值的提取&希望的表达 价值体系: 1.基 ...

  6. js比较洋气的写法

    案例一:使用js的构造方法生成js对象.给该对象赋属性值或function 案例二:使用json的代码风格,完成同样的功能. <!DOCTYPE html> <html> &l ...

  7. 20. Web proxies (网页代理 4个)

    用于评估Web应用程序漏洞的基于Java的Web代理. 它支持在运行时编辑/查看HTTP / HTTPS消息,以更改Cookie和表单字段等项. 它包括网络流量记录器,网络蜘蛛,哈希计算器和用于测试常 ...

  8. 联想拯救者r720+固态浦科特M8PeGN 的bios 设置

    最近笔记本左边的两个usb3.0接口都坏了,win10 报错usb端口上的电涌 真是一脸蒙B! 然后去打售后电话,说昌平没有售后,需要去海淀黄庄去维修,然后下午就去了那里,维修的人员说硬件坏了,需要1 ...

  9. 電腦清理緩存bat文件源碼

    @echo off echo 正在清除系統垃圾文件,請稍等 ...... del /f /s /q %systemdrive%\*.tmp del /f /s /q %systemdrive%\*._ ...

  10. c# Linq&Lambda

    0.写这个文章主要记录下常用Lambda的用法,能力有限,文中有问题的地方希望各位大神指出来谢谢!因为平时写代码的时候没有特地去用lambda,全是用一些循环,少量会用到lambda,虽然也能实现要的 ...