当前位置: 首页 > news >正文

win10程序(七)暴力xls转xlsx程序

'''

暴力转换,不论xls是不是这个格式。

因为有个的软件导出的xls实际上有xml压缩,一次会被错误识别,这个程序在转换时如果发现错误,直接强制转换扩展名,目前还没发现问题。同时避免了xlrd的1.2版本限制问题。

使用tkinter和 xls2xlsx制作python表格转格式程序。功能是将一个文件夹中一个或多个文件,不论是xls还是xlsx,统一另存为xlsx。窗口的字体均为宋体,字号12,窗口尺寸850*600,有执行按键,停止按键。适应大量数据,至少11万行。使用标准的Tkinter字体配置方法,避免处理过程中出错: self.tk.call(_tkinter.TclError: unknown option "-font"。避免处理过程中出错: '<' not supported between instances of 'int' and 'NoneType'。避免output_dir = self.custom_output_dir or self.  SyntaxError: invalid syntax错误
1.
文件选择框。应该支持可选一个或者多个文件。注意:维持源文件xls中的内容,尤其是其中的字体、字号、格式不要变化。
具备转换状态提示功能。如果文件格式与扩展名不符,例如xls文件实际是xlsx格式或者反之,或者转换出错时,则直接改变扩展名为xlsx另存,并提示处理方式是直接转扩展名。
“备注1”和“备注2”输入框,默认均为空,用于修改另存为文件夹的名称。
2.
另存文件位置选择框,可选择转换成的文件的另存位置,默认为空,如果为空则在原文件夹新建一个名为“备注”+原文件夹名+“xlsx”+“备注2”的文件夹,将转换后的文件按放入,不改变文件名。如果原来已经有改名字的文件夹则自动添加序号另存。注意:同一批转换的文件应另存入同一文件夹,而不是每个文件单独建文件夹。
3.数据i清理功能。
半角空格清理选项,默认选中,如单元格内容是半角空格,则另存时删除单元格内容及格式。
全角空格清理选项,默认选中,如单元格内容是全角空格,则另存时删除单元格内容及格式。
清理数功能输入框1:默认值为空,如果有内容,则删除所有单元格内对应的这个内容。
注意:软件窗口安排应紧凑,例如另存位置重置路径之类能放在同一行的尽量压缩窗口高度。
注意:检查所有功能是否实现,不可忽略。

'''

import os
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, font
import threading
from xls2xlsx import XLS2XLSX
import openpyxl# 检测真实文件格式
def is_xlsx_file(file_path):try:with open(file_path, 'rb') as f:return f.read(4) == b'PK\x03\x04'except Exception:return Falsedef is_xls_file(file_path):try:with open(file_path, 'rb') as f:return f.read(8) == b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'except Exception:return Falseclass ExcelConverterApp:def __init__(self, root):self.root = rootself.root.title("Excel 批量转 XLSX(保留格式)")self.root.geometry("850x600")self.root.minsize(750, 550)# 设置全局字体:宋体 12号default_font = font.nametofont("TkDefaultFont")default_font.configure(family="宋体", size=12)self.root.option_add("*Font", default_font)self.is_running = Falseself.stop_requested = Falseself.file_list = []self.custom_output_dir = ""  # 用户自选路径self.final_output_dir = ""   # 实际使用的输出文件夹(统一一个)self.setup_ui()def setup_ui(self):main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)# === 1. 文件选择区 ===file_frame = ttk.LabelFrame(main_frame, text="1. 选择文件", padding="5")file_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 8))# 文件列表显示self.file_text = tk.Text(file_frame, height=6, wrap=tk.WORD)self.file_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)btn_frame1 = ttk.Frame(file_frame)btn_frame1.pack(side=tk.RIGHT, padx=(10, 0))ttk.Button(btn_frame1, text="选择文件", command=self.select_files).pack(fill=tk.X, pady=2)ttk.Button(btn_frame1, text="清空列表", command=self.clear_files).pack(fill=tk.X, pady=2)# === 2. 命名与输出路径 ===path_frame = ttk.LabelFrame(main_frame, text="2. 命名与保存位置", padding="5")path_frame.pack(fill=tk.X, pady=(0, 8))# 备注输入ttk.Label(path_frame, text="备注1:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))self.remark1_var = tk.StringVar()ttk.Entry(path_frame, textvariable=self.remark1_var, width=12).grid(row=0, column=1, padx=(0, 10))ttk.Label(path_frame, text="备注2:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))self.remark2_var = tk.StringVar()ttk.Entry(path_frame, textvariable=self.remark2_var, width=12).grid(row=0, column=3, padx=(0, 10))# 输出路径显示output_label_frame = ttk.Frame(path_frame)output_label_frame.grid(row=1, column=0, columnspan=4, sticky=tk.EW, pady=(5, 0))# 更新提示文本self.output_var = tk.StringVar(value="(默认:在原文件夹创建 '备注1+原文件夹名+xlsx+备注2' 文件夹)")ttk.Label(output_label_frame, textvariable=self.output_var, wraplength=600).pack(fill=tk.X)# 按钮行(紧凑)btn_frame2 = ttk.Frame(path_frame)btn_frame2.grid(row=2, column=0, columnspan=4, pady=(5, 0), sticky=tk.E)ttk.Button(btn_frame2, text="选择另存位置", command=self.select_output_dir).pack(side=tk.LEFT, padx=(0, 5))ttk.Button(btn_frame2, text="重置路径", command=self.reset_output_dir).pack(side=tk.LEFT)path_frame.columnconfigure(0, weight=1)# === 3. 数据清理 ===clean_frame = ttk.LabelFrame(main_frame, text="3. 数据清理", padding="5")clean_frame.pack(fill=tk.X, pady=(0, 8))self.clean_halfspace = tk.BooleanVar(value=True)self.clean_fullspace = tk.BooleanVar(value=True)ttk.Checkbutton(clean_frame, text="清理半角空格", variable=self.clean_halfspace).pack(side=tk.LEFT)ttk.Checkbutton(clean_frame, text="清理全角空格", variable=self.clean_fullspace).pack(side=tk.LEFT, padx=(10, 0))#ttk.Label(clean_frame, text="清理指定内容:").pack(anchor=tk.W, pady=(3, 0))self.clean_text_var = tk.StringVar()ttk.Entry(clean_frame, textvariable=self.clean_text_var).pack(fill=tk.X, pady=(0, 0))# === 4. 操作按钮 ===action_frame = ttk.Frame(main_frame)action_frame.pack(fill=tk.X, pady=(0, 8))self.execute_btn = ttk.Button(action_frame, text="▶ 执行转换", command=self.start_conversion)self.execute_btn.pack(side=tk.LEFT)self.stop_btn = ttk.Button(action_frame, text="■ 停止", command=self.request_stop, state=tk.DISABLED)self.stop_btn.pack(side=tk.LEFT, padx=(5, 0))# === 5. 日志区域 ===log_frame = ttk.LabelFrame(main_frame, text="4. 转换状态", padding="5")log_frame.pack(fill=tk.BOTH, expand=True)self.log_text = tk.Text(log_frame, wrap=tk.WORD, height=10, state=tk.DISABLED)scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)self.log_text.configure(yscrollcommand=scrollbar.set)self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)def log(self, message):self.log_text.config(state=tk.NORMAL)self.log_text.insert(tk.END, message + "\n")self.log_text.see(tk.END)self.log_text.config(state=tk.DISABLED)self.root.update_idletasks()def select_files(self):files = filedialog.askopenfilenames(title="选择 Excel 文件",filetypes=[("Excel 文件", "*.xls *.xlsx"), ("所有文件", "*.*")])if files:self.file_list = list(files)self.file_text.delete(1.0, tk.END)for f in self.file_list:self.file_text.insert(tk.END, os.path.basename(f) + "\n")def clear_files(self):self.file_list = []self.file_text.delete(1.0, tk.END)def select_output_dir(self):selected = filedialog.askdirectory(title="选择统一保存文件夹")if selected:self.custom_output_dir = selectedself.output_var.set(selected)def reset_output_dir(self):self.custom_output_dir = ""# 更新提示文本self.output_var.set("(默认:在原文件夹创建 '备注1+原文件夹名+xlsx+备注2' 文件夹)")def get_output_folder(self):"""为整批文件生成统一的输出文件夹"""if self.custom_output_dir:os.makedirs(self.custom_output_dir, exist_ok=True)return self.custom_output_dirif not self.file_list:return Nonefirst_file = self.file_list[0]dir_path = os.path.dirname(first_file)folder_name = os.path.basename(dir_path)  # 原文件夹名remark1 = self.remark1_var.get().strip()remark2 = self.remark2_var.get().strip()# 新的命名规则:备注1 + 原文件夹名 + xlsx + 备注2new_folder_name = ""if remark1:new_folder_name += remark1new_folder_name += folder_namenew_folder_name += "xlsx"if remark2:new_folder_name += remark2safe_name = "".join(c for c in new_folder_name if c.isalnum() or c in (' ', '-', '_'))base_path = os.path.join(dir_path, safe_name)output_dir = base_pathcounter = 1while os.path.exists(output_dir):output_dir = f"{base_path}_{counter}"counter += 1os.makedirs(output_dir, exist_ok=True)return output_dirdef start_conversion(self):if not self.file_list:messagebox.showwarning("警告", "请先选择至少一个文件!")returnif self.is_running:messagebox.showinfo("提示", "转换已在进行中,请勿重复点击。")return# 生成统一输出文件夹self.final_output_dir = self.get_output_folder()if not self.final_output_dir:messagebox.showerror("错误", "无法确定输出文件夹,请检查文件列表。")returnself.is_running = Trueself.stop_requested = Falseself.execute_btn.config(state=tk.DISABLED)self.stop_btn.config(state=tk.NORMAL)self.log(f"📁 所有文件将统一保存至:{self.final_output_dir}")thread = threading.Thread(target=self.convert_files, daemon=True)thread.start()def request_stop(self):self.stop_requested = Trueself.log("⏹ 正在请求停止...")def convert_files(self):total = len(self.file_list)self.log(f"开始转换,共 {total} 个文件...")for idx, file_path in enumerate(self.file_list):if self.stop_requested:self.log("🛑 用户停止,终止转换。")breakself.log(f"[{idx+1}/{total}] 处理: {os.path.basename(file_path)}")try:self.process_single_file(file_path)except Exception as e:self.log(f"❌ 失败: {str(e)}")self.finish_conversion()def process_single_file(self, file_path):ext = os.path.splitext(file_path)[1].lower()name = os.path.splitext(os.path.basename(file_path))[0]output_path = os.path.join(self.final_output_dir, f"{name}.xlsx")try:if is_xlsx_file(file_path):wb = openpyxl.load_workbook(file_path, keep_vba=False)self.apply_cleaning(wb)wb.save(output_path)wb.close()self.log(f"✅ 已处理: {output_path}")elif is_xls_file(file_path):temp_xlsx = os.path.join(self.final_output_dir, f"{name}_temp.xlsx")x2x = XLS2XLSX(file_path)x2x.to_xlsx(temp_xlsx)wb = openpyxl.load_workbook(temp_xlsx)self.apply_cleaning(wb)wb.save(output_path)wb.close()os.remove(temp_xlsx)self.log(f"✅ 已转换: {output_path}")else:# 格式异常,直接复制with open(file_path, 'rb') as src, open(output_path, 'wb') as dst:dst.write(src.read())self.log(f"📎 格式异常,已复制为: {output_path}")except Exception as e:self.log(f"⚠️ 失败,尝试直接复制: {e}")try:with open(file_path, 'rb') as src, open(output_path, 'wb') as dst:dst.write(src.read())self.log(f"📎 已直接复制为: {output_path}")except Exception as e2:self.log(f"❌ 直接复制也失败: {e2}")def apply_cleaning(self, wb):clean_half = self.clean_halfspace.get()clean_full = self.clean_fullspace.get()clean_text = self.clean_text_var.get().strip()space_half, space_full = ' ', ' 'for sheet in wb.worksheets:for row in sheet.iter_rows():for cell in row:val = cell.valueif not isinstance(val, str):continueval = val.strip()if (clean_half and val == space_half) or \(clean_full and val == space_full) or \(clean_text and val == clean_text):cell.value = Noneself.clear_cell_style(cell)@staticmethoddef clear_cell_style(cell):cell.font = openpyxl.styles.Font()cell.fill = openpyxl.styles.PatternFill()cell.border = openpyxl.styles.Border()cell.alignment = openpyxl.styles.Alignment()cell.number_format = 'General'def finish_conversion(self):self.is_running = Falseself.stop_requested = Falseself.execute_btn.config(state=tk.NORMAL)self.stop_btn.config(state=tk.DISABLED)if not self.stop_requested:self.log(f"🎉 全部完成!文件已保存至:{self.final_output_dir}")else:self.log("⏹ 转换已停止。")def run(self):self.root.mainloop()# === 启动应用 ===
if __name__ == "__main__":root = tk.Tk()app = ExcelConverterApp(root)app.run()
http://www.dtcms.com/a/391923.html

相关文章:

  • PINN物理信息神经网络驱动的Burgers偏微分方程求解MATLAB代码
  • Linux系统多线程的同步问题
  • Anaconda下载及使用详细教程
  • 第二部分:VTK核心类详解(第43章 vtkCharArray字符数组类)
  • 2025年9月19日NSSCTF之[陇剑杯 2021]日志分析(问1)
  • TDesign学习:(五)设置三级菜单的坑
  • 两步构建企业级AI知识库:技术实战与权限管理指南
  • 乐华显示工业一体机 10 大维护要点
  • 【MySQL ✨】MySQL 入门之旅 · 第七篇:MySQL 更新与删除数据(UPDATE / DELETE 语句)
  • 关于C++的入门基础
  • TDengine 标准差 STDDEV 函数使用场景及意义
  • color-printf一个轻量级、高兼容的终端彩色打印工具
  • Python实现等离子体反应优化 (Plasma Generation Optimization, PGO)(附完整代码)
  • 【C++】vector
  • LeeCode120. 三角形最小路径和
  • 元启发式算法分类
  • Ansible-file模块
  • Vue项目使用Coze的聊天窗(一)
  • 关于将tomcat、nginx 实现 注册window服务并实现自启动
  • 【精品资料鉴赏】358页 数字政府大数据中心资源平台治理建设方案
  • 关于Spring Bean之间的循环依赖
  • pake将前端web项目打包成windows可安装文件
  • 低轨卫星应用:MCU、CANFD与DCDC芯片的集成解决方案
  • AI 编程Claude Code使用详细教程
  • vue3 下载文件方式(包括通过url下载文件并修改文件名称,和文件流下载方式)
  • 如何高效筛选海量文献,避免浪费时间?
  • heyday
  • Go语言结构体初始化全面指南与最佳实践
  • 神经网络学习笔记15——高效卷积神经网络架构GhostNet
  • Mysql的Exists条件子查询