須要處理批量替換word的一些數據,數據源從Excel中來。html
Excel的百分數會變爲數字,以及浮點數會多好多精度,爲了原汁原味的數據,直接複製數據到文本文件。經過\t來分隔便可,最後一個值多\n得注意。python
而後在Word中加變量用{XXXX}
格式的得轉一下{}
,時間關係,用了 TEMP_XXX
之類的,str.replace()
去替換模板數據便可。女友發現Word有郵件合併功能,相似模板替換。git
看《PyQt快速開發與實戰》學習Qt designer生成ui、經過eric6或者命令編譯py文件、信號槽機制、簡單的如何讓界面和邏輯分離,以及之前的PyQt入門,打算直接上手作。github
界面邏輯分離能夠經過兩種方式:(注 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_())
用常規的sheet.cell_value(i, j)
獲取數據,會有一些意外的狀況,好比有些數字以後會多.0,百分比會是小數,小數多精度,太大的數字爲科學計數法,日期也爲浮點數,總之就是所得非所見。要所見即所得的話,直接複製,存取文本文件吧,每一列默認經過\t區分。segmentfault
基本的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()
原本打算模板變量select選擇後,radio選擇相應的數據源,發現一篇Qt程序學習(三)------QTreeWidget、右鍵菜單、動態改變comboBox、QTreeWidgetItem的對應列的文字編輯,結合QTreeWidget
、Combo Box
能夠實現想要的一一對應功能。學習一番QTreeWidget和Combo Box基本操做(在邏輯小節)。app
叮!項目壓測時候,發現excel的csv文件,文本文件打開是用,
逗號分隔的。能夠直接處理excel了(雖然有侷限,若是數據中有逗號就得更改系統配置,顯然不現實)。工具
因此流程變爲:學習
csv.reader()
讀取)就能夠了,第一條上面查漏補缺有,第三條剛開始的腳本邏輯處理都寫過了,因此重點放在添加規則,首先須要熟悉一下QTreeWidget
和 Combo Box
。
實例化
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
下拉框控件。
常規操做
# 實例化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)
效果以下
圖中讀取後綴爲.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
僅僅是當用戶在組合框中選擇項目時, 將發送此信號。
接下來就須要接入以前的處理邏輯
移植過來就能夠了
# 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}")
注意,遇到一個模板變量被拆分,看不出來。可是在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
則打包成功。
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))
設置程序圖標有多種方式,感受用不到Designer建qrc資源文件,直接用
self.setWindowIcon(QIcon('combine.ico')) # 設置圖標
用python直接運行程序是能夠有的,生成exe運行就無圖標了。
cx_freeze 加參數icon
只會在縮略圖中有,程序左上角仍是沒有,夠用了。
終於完整的完成了一個小GUI工具 :) 雖然實際用起來通常般,特別是數據多的時候,模板變量得從前日後打,以免在docx角度看到的模板變量是拆分的,但這只是一個開始。
參考