使用Python解析macro.ini

本文介绍了一种使用Python解析MML配置文件的方法。通过定义MML类并利用正则表达式来提取MOC对象及其命令、属性等信息,并将这些信息组织成字典结构。此外,还提供了比较两个MML文件差异的功能,最终将比较结果输出到文本文件中。
#使用Python解析macro.ini
# -*- coding: utf-8 -*-
import os
import re

class MML:
    def __init__(self, FilePath):
        print FilePath
        if os.path.exists(FilePath) == False:
            self.create = False
        else:
            self.create = True
            self.fp = open(FilePath)

        #用于保存MOC对象的命令和属性,MOCdir = { 'MOCname':{'CMD':{attr:[obj,obj.attr]}}}
        self.MOCdir = {}
        self.Level = 4
        #当前读取的MOC对象名称
        self.CurrentMOC = ''
        #当前读取操作是:ADD/MOD/RMV
        self.CurrentOperator = ''
        #当前读取的属性
        self.CurrentAttr = ''
        #当前读取的是否为命令内容
        self.ContentFlag = False
        #读取MML文件
        self.ReadMML()
        #for key in self.MOCdir.keys():
            #self.PrintMOC(key)



    def isCreate(self):
        return self.create
    #读取MML文件
    def ReadMML(self):
        while True:
            line = self.fp.readline()
            if line:
                self.ReadLine(line)
            else:
                break
        self.fp.close()

    #读取一行MML文件内容
    def ReadLine(self, content):
        #获取MOC及命令
        if self.GetCMD(content) == True:
            return 0                
        if self.ContentFlag == False:
            return 0
        #跳过空行
        if len(content) < 3:
            return 0       
        #获取属性名称
        if self.GetCMDAttr(content) == False:
            return
        #获取属性联想对象
        self.GetAttrAssociation()


    #获取MOC及命令
    def GetCMD(self, content):
        if re.search('\[.*\]',content):
            ContentFlag = False
            #获取MOC名称
            #'\[ *(?P<opName>[^ ]*) (?P<MOCName>[^ ]*) *\]'去除可能存在的多余空格
            match = re.search('\[ *(?P<opName>[^ ]*) +(?P<MOCName>[^ ]*) *\]', content)
            if match:
                MOCName = match.group('MOCName')
                opName = match.group('opName')
                #增加MOC对象和命令
                if MOCName in self.MOCdir.keys():
                    self.MOCdir[MOCName][opName] = {}
                else:
                    self.MOCdir[MOCName] = {opName:{}}
                
                #当前读取的MOC和命令
                self.CurrentMOC = MOCName
                self.CurrentOperator = opName
                #标识下一行是数据
                self.ContentFlag = True
                return True
            return False

    #匹配属性名称       
    def GetCMDAttr(self, content):
        match =  re.search(' *[0-9]+ *=(?P<attrName>.*)', content)
        if match :
            result = match.group('attrName')
            result = result.replace(' ', '')
            #增加MOC对象命令的属性
            self.CurrentAttr = result
            self.MOCdir[self.CurrentMOC][self.CurrentOperator][self.CurrentAttr] = []
            return True
        else:
            print 'no match attrName'
            return False

    #读取属性联想的对象和属性
    def GetAttrAssociation(self):
        for i in range(2):
            line = self.fp.readline()
            #名字只包含字符数字,下划线和减号
            match = re.search('.*= *(?P<moc>[\w,_, -]*)', line)
            if match:
                self.MOCdir[self.CurrentMOC][self.CurrentOperator][self.CurrentAttr].append(match.group('moc'))

    #比较新旧MML文件,DiffDict 格式:{'ADD/MOD/RMV':{'MOCname':{'ADD/MOD/RMV':{'CMD': {'ADD/MOD/RMV':{atr:[obj,obj.attr]}}}}}}
    def CompareMML(self, oldMOC):
        #用于保存新旧文件比较结果
        DiffDict = {}
        #新旧对比
        self.iteCompareMML(self.MOCdir, oldMOC.MOCdir, 4, DiffDict)
        #由DiffDict中删除没有改变的项
        self.DelNoModify(DiffDict,4)
        #输出比较结果
        self.WriteDiffToFile(DiffDict)


    #递归比较MOC差异
    def iteCompareMML(self, new1, old, Level, DiffDict):
        if Level <= 0:
            return
        if Level == 1:
            #保存新旧联想对象和属性
            DiffDict['old'] = old
            DiffDict['new'] = new1
            return 0
        
        DiffDict['ADD'] = {}
        DiffDict['RMV'] = {}
        DiffDict['MOD'] = {}
        #判断新增或修改
        for key in new1.keys():
            if key in old.keys():
                #可能修改的
                DiffDict['MOD'][key] = {}
            else:
                DiffDict['ADD'][key] = new1[key]
        #判断为删除项
        for key in old.keys():
            if key not in new1.keys():            
                DiffDict['RMV'][key] = old[key]
        #继续判断是否有修改
        for key in DiffDict['MOD'].keys():
            self.iteCompareMML(new1[key], old[key], Level - 1, DiffDict['MOD'][key])
        


    def PrintMOC(self,MOCName):
        if MOCName in self.MOCdir.keys():
            print 'MOC Name: %s'%MOCName
            for cmd in self.MOCdir[MOCName]:
                print '%s %s'%(cmd, MOCName)
                for attr in self.MOCdir[MOCName][cmd]:
                    print 'attr: %s'%attr
                    print 'associate MOC: %s\nassociate attr: %s'%(self.MOCdir[MOCName][cmd][attr][0], self.MOCdir[MOCName][cmd][attr][1])

    #删除没有修改的项
    def DelNoModify(self, DiffDict, Level):
        if Level < 2:
            return

        for cmd in DiffDict.keys():
            #删除没有增加,或修改的命令
            if len(DiffDict[cmd]) == 0 and Level > 1:
                del DiffDict[cmd]
            #递归查找下一级    
            elif len(DiffDict[cmd]) != 0 and Level > 2 and cmd == 'MOD':
                for key in DiffDict[cmd].keys():
                    self.DelNoModify(DiffDict[cmd][key], Level - 1)
            #判断属性的联想是否修改        
            elif len(DiffDict[cmd]) != 0 and Level == 2 and cmd == 'MOD':
                for attr in DiffDict[cmd].keys():
                    AttrDict = DiffDict[cmd][attr]
                    if AttrDict['old'][0] == AttrDict['new'][0] and AttrDict['old'][1] == AttrDict['new'][1]:
                        del AttrDict
                                    
    #输出比较结果到result.txt   
    def WriteDiffToFile(self, DiffDict):
        fp = open('result.txt','w')
        
        if 'ADD' in DiffDict.keys() and len(DiffDict['ADD']) > 0:
            fp.write('================================================================\n')
            fp.write('新增MML命令联想\n')
            self.WriteNeworRmvToFile(DiffDict, 'ADD', fp)
            fp.write('================================================================\n')
            fp.write('\n\n')
            
        if 'RMV' in DiffDict.keys() and len(DiffDict['RMV']) > 0:
            fp.write('================================================================\n')
            fp.write('删除MML命令联想\n')
            self.WriteNeworRmvToFile(DiffDict, 'RMV', fp)
            fp.write('\n\n')
            fp.write('================================================================\n')

        if 'MOD' in DiffDict.keys() and len(DiffDict['MOD']) > 0:
            fp.write('================================================================\n')
            fp.write('修改MML命令属性联想\n')
            self.WriteModToFile(DiffDict, fp)
            fp.write('\n\n')
            fp.write('================================================================\n')
            
        fp.close()

    #写入新增命或删除命令到result.txt
    def WriteNeworRmvToFile(self, DiffDict, op, fp):        
        #打印新增或删除的MOC的所有命令
        for obj in DiffDict[op].keys():
            moc = DiffDict[op][obj]
            for cmd in moc.keys():
                CMDName = '%s %s\n'%(cmd,obj)
                fp.write(CMDName)
                
        #判断是否有修改命令的MOC
        if 'MOD' not in DiffDict.keys():
            return 0
              
        #打印已有MOC新增或删除的命令
        for obj in DiffDict['MOD'].keys():
            if op not in DiffDict['MOD'][obj]:
                continue
            moc = DiffDict['MOD'][obj][op]
            for cmd in moc.keys():
                CMDName = '%s %s\n'%(cmd,obj)
                fp.write(CMDName)

    #把修改的MOC命令写到result.txt
    def WriteModToFile(self, DiffDict, fp):
        for obj in DiffDict['MOD'].keys():
            #可能修改的命令
            moc = DiffDict['MOD'][obj]['MOD']
            for cmd in moc.keys():
                if self.CmdIsMod(moc[cmd]) != True:
                    continue
                CMDName = '%s %s\n'%(cmd,obj)
                fp.write(CMDName)
                self.WriteCMDModtoFile(moc[cmd], fp)
                
    #把增加,修改,或删除的属性及联想对象写的result.txt
    def WriteCMDModtoFile(self, CMDDict, fp):
        for op in CMDDict.keys():
            if len(CMDDict[op]) < 0:
                continue
            
            if op == 'ADD':
                fp.write('新增MML参数联想\n')
                for attr in CMDDict[op].keys():
                    AttrList = CMDDict[op][attr]
                    msgStr = u'%s \n联想对象为: %s 联想属性为:%s\n'%(attr, AttrList[0], AttrList[1])
                    msgStr += '\n'
                    fp.write(msgStr.encode('utf-8'))

            if op == 'RMV':
                fp.write('删除MML参数联想\n')
                for attr in CMDDict[op]:
                    msgStr = '%s\n'%attr
                    msgStr += '\n'
                    fp.write(msgStr)
              
            if op == 'MOD':
                if self.AttrIsMod(CMDDict[op]) == True:
                    fp.write('修改MML参数联想\n')
                else:
                    continue
                
                for attr in CMDDict[op]:
                    AttrDict = CMDDict[op][attr]
                    if AttrDict['new'][0] != AttrDict['old'][0] or AttrDict['new'][1] != AttrDict['old'][1]:
                        msgStr = '%s\n'%attr
                    else:
                        continue
                    if AttrDict['new'][0] != AttrDict['old'][0]:
                        msgStr += u'联想对象由 %s 改为 %s\n'%(AttrDict['old'][0],AttrDict['new'][0])
                    if AttrDict['new'][1] != AttrDict['old'][1]:
                        msgStr += u'联想属性由 %s 改为 %s\n'%(AttrDict['old'][1],AttrDict['new'][1])
                    msgStr += '\n'
                    fp.write(msgStr.encode('utf-8'))

    #判断命令属性是否修改
    def AttrIsMod(self, AttrDict):
        for attr in AttrDict.keys():
            if AttrDict[attr]['new'][0] != AttrDict[attr]['old'][0] or AttrDict[attr]['new'][1] != AttrDict[attr]['old'][1]:
                return True
        return False
    
    #判断命令是否修改
    def CmdIsMod(self, cmd):
        if 'ADD' in cmd.keys() and len(cmd['ADD']) > 0:
            return True
        if 'RMV' in cmd.keys() and len(cmd['RMV']) > 0:
            return True
        if self.AttrIsMod(cmd['MOD']) == True:
            return True
        return False
            

好的,现在能够显示宏了,并且宏名称为ExposedMacro,请结合我的下述代码,更新一个流程,打开excel文件后,自动运行宏ExposedMacro #软件著作人:杨晨 2025.07.11 import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import threading import queue import traceback import webbrowser import datetime import configparser class DiffProcessorApp: def __init__(self, root): self.root = root root.title("ファイル比較実施工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") self.style = ttk.Style() self.style.theme_use('clam') self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('View.TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#008000", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('View.TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('active', '#004D00'), ('disabled', '#808080')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="ファイル比較実施工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) self.old_folder_entry, _ = self.create_folder_selector(file_frame, "原始文件夹:", "old_folder") self.new_folder_entry, _ = self.create_folder_selector(file_frame, "修改后文件夹:", "new_folder") options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, "excel_file", [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) winmerge_frame = ttk.Frame(self.excel_frame) winmerge_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(winmerge_frame, text="WinMerge路径:").grid(row=0, column=0, sticky=tk.W) self.winmerge_entry = ttk.Entry(winmerge_frame, width=60) self.winmerge_entry.grid(row=0, column=1, padx=5) self.winmerge_entry.insert(0, r"E:\App\WinMerge\WinMerge2.16.46.0\WinMergeU.exe") ttk.Button(winmerge_frame, text="浏览...", command=lambda: self.select_file(self.winmerge_entry, "winmerge_path", [("WinMerge 可执行文件", "*.exe")])).grid(row=0, column=2) delete_frame = ttk.Frame(self.excel_frame) delete_frame.grid(row=2, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) self.delete_temp_files_var = tk.BooleanVar(value=False) delete_check = ttk.Checkbutton( delete_frame, text="完成后删除临时文件", variable=self.delete_temp_files_var, command=self.update_view_button_state ) delete_check.grid(row=0, column=0, padx=5, sticky=tk.W) self.delete_status_label = ttk.Label( delete_frame, text="(勾选后将删除报告文件,无法查看)", foreground="#FF0000", font=("Segoe UI", 9) ) self.delete_status_label.grid(row=0, column=1, padx=5) button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) self.view_folder_report_button = ttk.Button(button_frame, text="查看文件夹报告", command=lambda: self.view_report("folder"), width=15, state=tk.DISABLED, style='View.TButton') self.view_folder_report_button.pack(side=tk.LEFT, padx=10) self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) self.processing = False self.queue = queue.Queue() self.folder_report_path = None self.files_dir = None self.copied_html_files = [] self.config_file = "folder_compare_config.ini" self.load_paths() self.root.after(100, self.process_queue) def update_view_button_state(self): if self.delete_temp_files_var.get(): self.view_folder_report_button.config(state=tk.DISABLED) self.style.configure('View.TButton', foreground='#808080', background='#BDBDBD') else: if self.folder_report_path and os.path.exists(self.folder_report_path): self.view_folder_report_button.config(state=tk.NORMAL) self.style.configure('View.TButton', foreground='#008000', background='#4CAF50') else: self.view_folder_report_button.config(state=tk.DISABLED) self.style.configure('View.TButton', foreground='#808080', background='#BDBDBD') self.view_folder_report_button.configure(style='View.TButton') def create_folder_selector(self, parent, label_text, config_key): frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry, config_key)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry, config_key): initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() foldername = filedialog.askdirectory(initialdir=initial_dir) if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) self.populate_folder_tree(foldername) self.save_path(config_key, foldername) def select_file(self, entry, config_key, filetypes=None): if filetypes is None: filetypes = [("所有文件", "*.*")] initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() if os.path.isfile(initial_dir): initial_dir = os.path.dirname(initial_dir) filename = filedialog.askopenfilename(filetypes=filetypes, initialdir=initial_dir) if filename: entry.delete(0, tk.END) entry.insert(0, filename) self.save_path(config_key, filename) def get_last_path(self, config_key): config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if config.has_option('Paths', config_key): return config.get('Paths', config_key) return None def populate_folder_tree(self, path): self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): self.queue.put(("log", message)) def update_progress(self, value): self.queue.put(("progress", value)) def update_status(self, message): self.queue.put(("status", message)) def process_queue(self): try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def view_report(self, report_type): if report_type == "folder" and self.folder_report_path and os.path.exists(self.folder_report_path): try: webbrowser.open(self.folder_report_path) except Exception as e: messagebox.showerror("错误", f"无法打开文件夹报告: {str(e)}") else: messagebox.showwarning("警告", f"没有可用的{report_type}报告文件") def process_folders(self, old_path, new_path, excel_file): try: report_dir = os.path.dirname(excel_file) or os.getcwd() os.makedirs(report_dir, exist_ok=True) timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") self.folder_report_path = os.path.join(report_dir, f"folder_diff_report_{timestamp}.html") self.update_status("生成比较报告...") self.update_progress(30) winmerge_path = self.winmerge_entry.get() if not self.run_winmerge(winmerge_path, old_path, new_path): self.update_status("WinMerge执行失败") return if not os.path.exists(self.folder_report_path) or os.path.getsize(self.folder_report_path) == 0: self.log_message("警告: 文件夹报告为空或未生成") else: self.log_message(f"文件夹报告生成成功: {self.folder_report_path} ({os.path.getsize(self.folder_report_path)} bytes)") self.copy_detail_files(report_dir) self.update_status("打开Excel文件...") self.update_progress(80) if not self.open_excel_file(excel_file): self.update_status("打开Excel失败") return if self.delete_temp_files_var.get(): self.delete_winmerge_reports() self.log_message("已删除所有临时文件") else: self.log_message("保留临时文件,可查看报告") self.root.after(100, self.update_view_button_state) self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成报告并打开Excel文件") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: if self.processing: self.stop_processing() def delete_winmerge_reports(self): if not self.folder_report_path: return if os.path.exists(self.folder_report_path): try: os.remove(self.folder_report_path) self.log_message(f"已删除报告文件: {self.folder_report_path}") except Exception as e: self.log_message(f"删除报告文件失败: {str(e)}") base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if os.path.exists(files_dir): try: shutil.rmtree(files_dir) self.log_message(f"已删除.files目录: {files_dir}") except Exception as e: self.log_message(f"删除.files目录失败: {str(e)}") report_dir = os.path.dirname(self.folder_report_path) if os.path.exists(report_dir) and hasattr(self, 'copied_html_files') and self.copied_html_files: deleted_count = 0 for file_path in self.copied_html_files: if os.path.exists(file_path): try: os.remove(file_path) deleted_count += 1 self.log_message(f"已删除复制的HTML文件: {file_path}") except Exception as e: self.log_message(f"删除HTML文件失败: {file_path} - {str(e)}") self.log_message(f"已删除 {deleted_count}/{len(self.copied_html_files)} 个复制的HTML文件") self.copied_html_files = [] self.folder_report_path = None self.view_folder_report_button.config(state=tk.DISABLED) self.root.after(100, self.update_view_button_state) def copy_detail_files(self, report_dir): base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if not os.path.exists(files_dir): self.log_message(f"警告: 详细文件目录不存在 {files_dir}") return html_files = [f for f in os.listdir(files_dir) if f.lower().endswith('.html')] if not html_files: self.log_message(f"警告: 详细文件目录中没有HTML文件 {files_dir}") return if not hasattr(self, 'copied_html_files') or not self.copied_html_files: self.copied_html_files = [] copied_count = 0 for file_name in html_files: src_path = os.path.join(files_dir, file_name) dst_path = os.path.join(report_dir, file_name) try: shutil.copy2(src_path, dst_path) copied_count += 1 self.copied_html_files.append(dst_path) except Exception as e: self.log_message(f"复制文件失败: {file_name} - {str(e)}") self.log_message(f"已复制 {copied_count}/{len(html_files)} 个详细HTML文件到报告目录") def start_processing(self): if self.processing: self.log_message("警告: 处理正在进行中") return old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") winmerge_path = self.winmerge_entry.get() if not winmerge_path or not os.path.exists(winmerge_path): validation_errors.append("WinMerge路径无效或未设置") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.DISABLED) self.processing = True self.copied_html_files = [] thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, winmerge_path, path1, path2): if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False os.makedirs(os.path.dirname(self.folder_report_path), exist_ok=True) cmd = [ winmerge_path, '/u', '/nosplash', '/dl', 'Base', '/dr', 'Modified', '/noninteractive', '/minimize' ] if self.recursive_var.get(): cmd.extend(['/r', '/s']) else: cmd.extend(['/r-', '/s-']) file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": cmd.extend(['-f', file_filter]) cmd.extend(['/or', self.folder_report_path]) cmd.extend([path1, path2]) self.update_status("正在生成比较报告...") return self.execute_winmerge_command(cmd, "比较报告") def execute_winmerge_command(self, cmd, report_type): try: self.log_message(f"开始生成{report_type}...") self.log_message(f"执行命令: {' '.join(cmd)}") start_time = time.time() creation_flags = 0 if os.name == 'nt': creation_flags = subprocess.CREATE_NO_WINDOW process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace', creationflags=creation_flags ) timeout = 900 try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log_message(f"{report_type}生成超时({timeout}秒),已终止进程") return False elapsed = time.time() - start_time self.log_message(f"{report_type}生成完成,耗时: {elapsed:.2f}秒") if stdout.strip(): self.log_message(f"WinMerge输出:\n{stdout[:2000]}") if stderr.strip(): self.log_message(f"WinMerge错误:\n{stderr[:1000]}") if process.returncode == 0: self.log_message(f"{report_type}命令执行成功") return True elif process.returncode == 1: self.log_message(f"{report_type}命令执行完成(发现差异)") return True elif process.returncode == 2: self.log_message(f"{report_type}命令执行完成(发现错误)") return False else: error_msg = f"{report_type}生成失败(退出码{process.returncode})" self.log_message(error_msg) return False except Exception as e: self.log_message(f"{report_type}生成错误: {str(e)}\n{traceback.format_exc()}") return False def open_excel_file(self, excel_path): self.log_message("正在打开Excel文件...") try: if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False os.startfile(excel_path) self.log_message(f"Excel文件已打开: {excel_path}") self.log_message("Excel已打开,请手动执行操作") messagebox.showinfo("Excel已打开", "Excel文件已打开,请手动执行所需操作") return True except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}\n{traceback.format_exc()}") return False def stop_processing(self): self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.root.after(100, self.update_view_button_state) self.update_status("操作已停止") def load_paths(self): config = configparser.ConfigParser() if os.path.exists(self.config_file): try: config.read(self.config_file) if config.has_option('Paths', 'old_folder'): old_path = config.get('Paths', 'old_folder') self.old_folder_entry.delete(0, tk.END) self.old_folder_entry.insert(0, old_path) if os.path.isdir(old_path): self.populate_folder_tree(old_path) if config.has_option('Paths', 'new_folder'): new_path = config.get('Paths', 'new_folder') self.new_folder_entry.delete(0, tk.END) self.new_folder_entry.insert(0, new_path) if config.has_option('Paths', 'excel_file'): excel_path = config.get('Paths', 'excel_file') self.excel_file_entry.delete(0, tk.END) self.excel_file_entry.insert(0, excel_path) if config.has_option('Paths', 'winmerge_path'): winmerge_path = config.get('Paths', 'winmerge_path') self.winmerge_entry.delete(0, tk.END) self.winmerge_entry.insert(0, winmerge_path) self.log_message("已加载上次保存的路径") except Exception as e: self.log_message(f"加载配置文件失败: {str(e)}") else: self.log_message("未找到配置文件,将使用默认路径") def save_path(self, key, path): config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if not config.has_section('Paths'): config.add_section('Paths') config.set('Paths', key, path) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message(f"已保存路径: {key} = {path}") except Exception as e: self.log_message(f"保存路径失败: {str(e)}") def save_all_paths(self): config = configparser.ConfigParser() config.add_section('Paths') config.set('Paths', 'old_folder', self.old_folder_entry.get()) config.set('Paths', 'new_folder', self.new_folder_entry.get()) config.set('Paths', 'excel_file', self.excel_file_entry.get()) config.set('Paths', 'winmerge_path', self.winmerge_entry.get()) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message("所有路径已保存") except Exception as e: self.log_message(f"保存所有路径失败: {str(e)}") def on_closing(self): self.save_all_paths() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop()
09-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值