win10程序(十)慢速xls转xlsx
'''
每次转换都模拟打开并关闭xlsx文件,导致速速变慢。但是文件内容不再报错。能够顺利被xlsx读取程序识别。
'''
import os import tkinter as tk from tkinter import ttk, filedialog, messagebox import threading import queue from pathlib import Path import shutil import tempfiletry:import xlrdfrom openpyxl import Workbook, load_workbookfrom openpyxl.utils import get_column_letter except ImportError:messagebox.showerror("错误", "请先安装所需库: pip install xlrd openpyxl")exit()try:from xls2xlsx import XLS2XLSX except ImportError:messagebox.showerror("错误", "请先安装xls2xlsx库: pip install xls2xlsx")exit()class ETConverter:"""专门处理WPS ET文件的转换类"""@staticmethoddef convert_et_to_xlsx(et_file_path, xlsx_file_path):"""将ET文件转换为XLSX格式使用更稳定的转换方法避免WPS兼容性问题"""try:# 方法1: 使用xls2xlsx转换converter = XLS2XLSX(et_file_path)converter.to_xlsx(xlsx_file_path)return True, "成功转换"except Exception as e1:try:# 方法2: 尝试通过xlrd和openpyxl转换return ETConverter._convert_via_xlrd(et_file_path, xlsx_file_path)except Exception as e2:# 方法3: 最后尝试直接复制并修改扩展名try:shutil.copy2(et_file_path, xlsx_file_path)return True, "通过复制文件处理(可能丢失格式)"except Exception as e3:return False, f"所有转换方法都失败: {e1}, {e2}, {e3}"@staticmethoddef _convert_via_xlrd(et_file_path, xlsx_file_path):"""通过xlrd读取并重新创建xlsx文件"""try:# 尝试以xls格式读取ET文件workbook = xlrd.open_workbook(et_file_path)wb = Workbook()# 删除默认创建的工作表wb.remove(wb.active)for sheet_index in range(workbook.nsheets):sheet = workbook.sheet_by_index(sheet_index)# 创建新工作表if sheet_index == 0:ws = wb.activews.title = sheet.nameelse:ws = wb.create_sheet(sheet.name)# 复制数据for row in range(sheet.nrows):for col in range(sheet.ncols):try:cell_value = sheet.cell_value(row, col)if cell_value is not None and cell_value != "":ws.cell(row=row + 1, column=col + 1, value=cell_value)except:continuewb.save(xlsx_file_path)return True, "通过xlrd转换成功"except Exception as e:raise eclass ExcelConverter:def __init__(self, root):self.root = rootself.root.title("WPS表格格式慢速转xlsx工具能解决格式错误问题")self.root.geometry("850x600")# 设置字体 - 使用标准方法self.font_style = ("宋体", 12)self.small_font_style = ("宋体", 10)# 配置ttk样式self._configure_styles()# 文件列表self.file_list = []# 处理控制self.queue = queue.Queue()self.process_running = Falseself.stop_requested = False# 创建界面self.create_widgets()# 启动队列处理器self.process_queue()def _configure_styles(self):"""配置ttk样式"""self.style = ttk.Style()# 为不同组件配置样式self.style.configure('TLabel', font=self.font_style)self.style.configure('TButton', font=self.font_style)self.style.configure('TCheckbutton', font=self.font_style)self.style.configure('TEntry', font=self.font_style)def create_widgets(self):"""创建界面组件"""# 主框架main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)# 文件选择部分file_frame = ttk.LabelFrame(main_frame, text="文件选择", padding="5")file_frame.pack(fill=tk.X, pady=5)# 文件列表框listbox_frame = ttk.Frame(file_frame)listbox_frame.pack(fill=tk.X, pady=5)self.file_listbox = tk.Listbox(listbox_frame, height=8, font=self.small_font_style)self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)scrollbar = ttk.Scrollbar(listbox_frame, command=self.file_listbox.yview)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.file_listbox.config(yscrollcommand=scrollbar.set)# 文件操作按钮file_btn_frame = ttk.Frame(file_frame)file_btn_frame.pack(fill=tk.X, pady=5)ttk.Button(file_btn_frame, text="添加文件",command=self.add_files).pack(side=tk.LEFT, padx=5)ttk.Button(file_btn_frame, text="移除选中",command=self.remove_files).pack(side=tk.LEFT, padx=5)ttk.Button(file_btn_frame, text="清空列表",command=self.clear_files).pack(side=tk.LEFT, padx=5)# 输出目录和备注部分 - 放在同一行output_remark_frame = ttk.Frame(main_frame)output_remark_frame.pack(fill=tk.X, pady=5)# 输出目录选择output_frame = ttk.LabelFrame(output_remark_frame, text="输出目录", padding="5")output_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))output_subframe = ttk.Frame(output_frame)output_subframe.pack(fill=tk.X)self.output_var = tk.StringVar()output_entry = ttk.Entry(output_subframe, textvariable=self.output_var, font=self.font_style)output_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))ttk.Button(output_subframe, text="浏览",command=self.browse_output).pack(side=tk.RIGHT)# 备注部分remark_frame = ttk.LabelFrame(output_remark_frame, text="文件夹命名备注", padding="5")remark_frame.pack(side=tk.RIGHT, fill=tk.X, padx=(5, 0))remark_subframe = ttk.Frame(remark_frame)remark_subframe.pack(fill=tk.X)ttk.Label(remark_subframe, text="备注1:").pack(side=tk.LEFT)self.remark1_var = tk.StringVar()ttk.Entry(remark_subframe, textvariable=self.remark1_var,width=10, font=self.font_style).pack(side=tk.LEFT, padx=5)ttk.Label(remark_subframe, text="备注2:").pack(side=tk.LEFT)self.remark2_var = tk.StringVar()ttk.Entry(remark_subframe, textvariable=self.remark2_var,width=10, font=self.font_style).pack(side=tk.LEFT, padx=5)# 清理选项部分 - 放在同一行clean_frame = ttk.LabelFrame(main_frame, text="数据清理选项", padding="5")clean_frame.pack(fill=tk.X, pady=5)clean_subframe = ttk.Frame(clean_frame)clean_subframe.pack(fill=tk.X)self.clean_space_var = tk.BooleanVar(value=True)ttk.Checkbutton(clean_subframe, text="清理空格",variable=self.clean_space_var).pack(side=tk.LEFT, padx=10)self.clean_empty_var = tk.BooleanVar(value=True)ttk.Checkbutton(clean_subframe, text="清理空字符串",variable=self.clean_empty_var).pack(side=tk.LEFT, padx=10)ttk.Label(clean_subframe, text="清理指定内容:").pack(side=tk.LEFT, padx=(20, 5))self.clean_content_var = tk.StringVar()ttk.Entry(clean_subframe, textvariable=self.clean_content_var,width=15, font=self.font_style).pack(side=tk.LEFT, padx=5)# 状态显示status_frame = ttk.LabelFrame(main_frame, text="转换状态", padding="5")status_frame.pack(fill=tk.BOTH, expand=True, pady=5)self.status_text = tk.Text(status_frame, height=12, font=self.small_font_style)self.status_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)status_scrollbar = ttk.Scrollbar(status_frame, command=self.status_text.yview)status_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.status_text.config(yscrollcommand=status_scrollbar.set)# 按钮部分button_frame = ttk.Frame(main_frame)button_frame.pack(fill=tk.X, pady=10)self.convert_btn = ttk.Button(button_frame, text="开始转换",command=self.start_conversion)self.convert_btn.pack(side=tk.LEFT, padx=20)self.stop_btn = ttk.Button(button_frame, text="停止转换",command=self.stop_conversion, state=tk.DISABLED)self.stop_btn.pack(side=tk.LEFT, padx=20)def add_files(self):"""添加文件到列表"""files = filedialog.askopenfilenames(title="选择Excel文件",filetypes=[("Excel files", "*.xls *.xlsx *.et"), ("All files", "*.*")])if files:for file in files:if file not in self.file_list:self.file_list.append(file)self.file_listbox.insert(tk.END, file)def remove_files(self):"""移除选中的文件"""selected = self.file_listbox.curselection()if selected:for index in selected[::-1]:self.file_list.pop(index)self.file_listbox.delete(index)def clear_files(self):"""清空文件列表"""self.file_list.clear()self.file_listbox.delete(0, tk.END)def browse_output(self):"""选择输出目录"""directory = filedialog.askdirectory(title="选择输出目录")if directory:self.output_var.set(directory)def start_conversion(self):"""开始转换"""if not self.file_list:messagebox.showwarning("警告", "请先添加要转换的文件")returnself.process_running = Trueself.stop_requested = Falseself.convert_btn.config(state=tk.DISABLED)self.stop_btn.config(state=tk.NORMAL)# 清空状态文本self.status_text.delete(1.0, tk.END)# 启动转换线程thread = threading.Thread(target=self.convert_files)thread.daemon = Truethread.start()def stop_conversion(self):"""停止转换"""self.stop_requested = Trueself.add_status_message("正在停止转换...")def add_status_message(self, message):"""添加状态消息"""self.queue.put(("message", message))def process_queue(self):"""处理消息队列"""try:while True:task = self.queue.get_nowait()if task[0] == "message":self.status_text.insert(tk.END, task[1] + "\n")self.status_text.see(tk.END)elif task[0] == "progress":self.update_progress(task[1])except queue.Empty:passfinally:self.root.after(100, self.process_queue)def update_progress(self, progress_info):"""更新进度"""current, total = progress_infoself.convert_btn.config(text=f"转换中 ({current}/{total})")def get_output_directory(self, input_file):"""获取输出目录"""if self.output_var.get():output_dir = Path(self.output_var.get())else:# 获取原文件目录input_path = Path(input_file)parent_dir = input_path.parent# 构建新目录名remark1 = self.remark1_var.get()remark2 = self.remark2_var.get()new_dir_name = f"{remark1}{parent_dir.name}xlsx{remark2}"# 确保目录名唯一output_dir = parent_dir / new_dir_namecounter = 1while output_dir.exists():output_dir = parent_dir / f"{new_dir_name}_{counter}"counter += 1# 创建目录(如果不存在)output_dir.mkdir(parents=True, exist_ok=True)return output_dirdef should_clean_cell(self, cell_value):"""判断是否应该清理单元格"""if cell_value is None:return Falseif not isinstance(cell_value, str):return Falsecontent = str(cell_value).strip()# 检查清理指定内容clean_pattern = self.clean_content_var.get().strip()if clean_pattern and clean_pattern in content:return True# 检查空格和空字符串清理if self.clean_space_var.get() and self.clean_empty_var.get():# 同时清理空格和空字符串if not content or content.replace(' ', '').replace(' ', '') == '':return Trueelif self.clean_space_var.get():# 只清理空格if content.replace(' ', '').replace(' ', '') == '' and content != '':return Trueelif self.clean_empty_var.get():# 只清理空字符串if content == '':return Truereturn Falsedef convert_files(self):"""转换文件的主函数"""total_files = len(self.file_list)processed_files = 0for file_path in self.file_list:if self.stop_requested:breaktry:self.add_status_message(f"正在处理: {os.path.basename(file_path)}")# 获取输出目录output_dir = self.get_output_directory(file_path)input_path = Path(file_path)output_file = output_dir / f"{input_path.stem}.xlsx"# 根据文件扩展名选择转换方法file_ext = input_path.suffix.lower()success = Falsemessage = ""if file_ext == '.et':# 使用专门的ET转换器success, message = ETConverter.convert_et_to_xlsx(file_path, output_file)elif file_ext == '.xls':# 使用xls2xlsx转换XLS文件success, message = self._convert_xls_to_xlsx(file_path, output_file)elif file_ext == '.xlsx':# 处理XLSX文件success, message = self._process_xlsx_file(file_path, output_file)else:message = f"不支持的文件格式: {file_ext}"if success:# 应用清理选项if (self.clean_space_var.get() or self.clean_empty_var.get() orself.clean_content_var.get()):self._apply_cleaning(output_file)self.add_status_message(f"✓ {os.path.basename(file_path)} - {message}")else:self.add_status_message(f"✗ {os.path.basename(file_path)} - {message}")processed_files += 1self.queue.put(("progress", (processed_files, total_files)))except Exception as e:self.add_status_message(f"✗ 处理文件时出错 {os.path.basename(file_path)}: {str(e)}")# 恢复按钮状态self.process_running = Falseself.root.after(100, self._reset_buttons)self.add_status_message("转换完成!" if not self.stop_requested else "转换已停止")def _reset_buttons(self):"""重置按钮状态"""self.convert_btn.config(state=tk.NORMAL, text="开始转换")self.stop_btn.config(state=tk.DISABLED)def _convert_xls_to_xlsx(self, xls_file, xlsx_file):"""转换XLS文件到XLSX"""try:converter = XLS2XLSX(xls_file)converter.to_xlsx(xlsx_file)return True, "成功转换"except Exception as e:# 备用方案:尝试直接复制try:shutil.copy2(xls_file, xlsx_file)return True, "通过复制文件处理"except Exception as e2:return False, f"转换失败: {e}, 备用方案也失败: {e2}"def _process_xlsx_file(self, source_file, target_file):"""处理XLSX文件"""try:shutil.copy2(source_file, target_file)return True, "文件已复制"except Exception as e:return False, f"处理失败: {e}"def _apply_cleaning(self, file_path):"""应用清理选项"""try:wb = load_workbook(file_path)modified = Falsefor sheet_name in wb.sheetnames:ws = wb[sheet_name]for row in ws.iter_rows():for cell in row:if cell.value is not None and self.should_clean_cell(cell.value):cell.value = Nonemodified = Trueif modified:wb.save(file_path)except Exception as e:self.add_status_message(f"清理失败 {os.path.basename(file_path)}: {str(e)}")def main():"""主函数"""root = tk.Tk()app = ExcelConverter(root)root.mainloop()if __name__ == "__main__":main()