活动介绍

import os import sys import logging import time from pathlib import Path from concurrent.futures import ThreadPoolExecutor from tqdm import tqdm import tkinter as tk from tkinter import filedialog, messagebox, ttk from tkinterdnd2 import DND_FILES, TkinterDnD import json from pdf2docx import Converter import pytesseract from pdf2image import convert_from_path from docx import Document from docx.shared import Pt, Cm, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH import numexpr as ne import tempfile import traceback import io import threading import queue # 获取程序运行路径 def get_application_path(): """获取应用程序路径""" if getattr(sys, 'frozen', False): # 如果是打包后的exe return os.path.dirname(sys.executable) else: # 如果是直接运行的python脚本 return os.path.dirname(os.path.abspath(__file__)) # 设置NumExpr线程数 ne.set_num_threads(8) # 配置文件路径 CONFIG_FILE = os.path.join(get_application_path(), "config.json") # 日志文件路径 LOG_FILE = os.path.join(get_application_path(), "conversion.log") # 自定义StreamHandler来捕获所有输出 class StreamToLogger(io.StringIO): def __init__(self, logger, level): super().__init__() self.logger = logger self.level = level self.buf = '' def write(self, buf): self.buf = buf.strip('\r\n\t ') if self.buf: self.logger.log(self.level, self.buf) def flush(self): pass def setup_logging(): """配置日志系统""" try: app_path = get_application_path() log_dir = os.path.join(app_path, 'logs') os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, 'conversion.log') # 创建logger logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 创建文件处理器 file_handler = logging.FileHandler(log_file, mode='w', encoding='utf-8') file_handler.setLevel(logging.DEBUG) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建格式化器 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s') file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加处理器 logger.addHandler(file_handler) logger.addHandler(console_handler) logging.debug(f"日志系统初始化成功,日志文件路径:{log_file}") return logger except Exception as e: print(f"设置日志系统失败: {str(e)}") return None # 初始化日志记录器 logger = setup_logging() if not logger: print("警告:日志系统初始化失败,程序将继续运行但不会记录日志") # 创建一个基本的日志记录器 logger = logging.getLogger() logger.setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) logger.addHandler(handler) def load_config(): try: if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: config = json.load(f) # 自动修正Poppler路径 poppler_path = config.get('POPPLER_PATH', '') if poppler_path: # 检查是否存在bin子目录 bin_path = os.path.join(poppler_path, 'bin') if os.path.exists(bin_path): config['POPPLER_PATH'] = bin_path return config return {} except: return {} def save_config(config): """保存配置文件""" with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=4) def setup_config(): """首次运行配置界面""" root = TkinterDnD.Tk() root.title("PDF转Word工具 - 首次配置") root.geometry("600x400") # 设置窗口样式 root.configure(bg='#f0f0f0') style = {'bg': '#f0f0f0', 'font': ('微软雅黑', 10)} button_style = {'bg': '#4CAF50', 'fg': 'white', 'font': ('微软雅黑', 10), 'padx': 10, 'pady': 5} # 创建说明标签 tk.Label(root, text="首次使用需要配置以下路径:", **style).pack(pady=10) # Poppler路径配置 poppler_frame = tk.Frame(root, bg='#f0f0f0') poppler_frame.pack(fill='x', padx=20, pady=5) tk.Label(poppler_frame, text="Poppler路径:", **style).pack(side='left') poppler_path = tk.StringVar() poppler_entry = tk.Entry(poppler_frame, textvariable=poppler_path, width=50) poppler_entry.pack(side='left', padx=5) def select_poppler(): path = filedialog.askdirectory(title="选择Poppler安装目录") if path: poppler_path.set(path) tk.Button(poppler_frame, text="浏览", command=select_poppler, **button_style).pack(side='left') # Tesseract路径配置 tesseract_frame = tk.Frame(root, bg='#f0f0f0') tesseract_frame.pack(fill='x', padx=20, pady=5) tk.Label(tesseract_frame, text="Tesseract路径:", **style).pack(side='left') tesseract_path = tk.StringVar() tesseract_entry = tk.Entry(tesseract_frame, textvariable=tesseract_path, width=50) tesseract_entry.pack(side='left', padx=5) def select_tesseract(): path = filedialog.askdirectory(title="选择Tesseract安装目录") if path: tesseract_path.set(path) tk.Button(tesseract_frame, text="浏览", command=select_tesseract, **button_style).pack(side='left') # 说明文本 help_text = """ 使用说明: 1. Poppler路径:选择poppler的安装目录(包含bin文件夹的目录) 2. Tesseract路径:选择Tesseract-OCR的安装目录 3. 配置完成后点击"保存配置"即可开始使用 4. Poppler和Tesseract需放在在系统C盘下(C:\Program Files\) """ tk.Label(root, text=help_text, justify='left', **style).pack(pady=10) def save_and_exit(): if not poppler_path.get() or not tesseract_path.get(): messagebox.showerror("错误", "请填写所有配置项!") return tesseract_dir = tesseract_path.get() tesseract_exe = os.path.join(tesseract_dir, 'tesseract.exe') # 新增:验证Tesseract路径有效性 if not os.path.isfile(tesseract_exe): messagebox.showerror("错误", "选择的Tesseract路径无效,未找到tesseract.exe!") return config = { 'POPPLER_PATH': poppler_path.get(), 'TESSERACT_CMD': tesseract_exe # 直接使用正确路径 } save_config(config) root.destroy() tk.Button(root, text="保存配置", command=save_and_exit, **button_style).pack(pady=20) root.mainloop() class PDFConverterGUI: def __init__(self): self.root = TkinterDnD.Tk() self.root.title("PDF转Word工具") self.root.geometry("800x600") # 设置样式 self.style = {'bg': '#f0f0f0', 'font': ('微软雅黑', 10)} self.button_style = {'bg': '#4CAF50', 'fg': 'white', 'font': ('微软雅黑', 10), 'padx': 10, 'pady': 5} # 绑定窗口关闭事件 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.setup_ui() def on_closing(self): """处理窗口关闭事件""" try: if messagebox.askokcancel("退出", "确定要退出程序吗?"): self.root.destroy() sys.exit(0) except Exception as e: logger.error(f"关闭窗口时出错: {str(e)}") self.root.destroy() sys.exit(1) def setup_ui(self): # 创建主框架 main_frame = tk.Frame(self.root, bg='#f0f0f0') main_frame.pack(fill='both', expand=True, padx=20, pady=20) # 路径选择区域 path_frame = tk.Frame(main_frame, bg='#f0f0f0') path_frame.pack(fill='x', pady=10) tk.Label(path_frame, text="选择PDF文件或目录:", **self.style).pack(side='left') self.path_var = tk.StringVar() path_entry = tk.Entry(path_frame, textvariable=self.path_var, width=50) path_entry.pack(side='left', padx=5) tk.Button(path_frame, text="选择文件", command=self.select_file, **self.button_style).pack(side='left', padx=5) tk.Button(path_frame, text="选择目录", command=self.select_directory, **self.button_style).pack(side='left') # 文件列表区域 list_frame = tk.Frame(main_frame, bg='#f0f0f0') list_frame.pack(fill='both', expand=True, pady=10) tk.Label(list_frame, text="待处理文件列表(支持拖放文件):", **self.style).pack(anchor='w') # 创建带滚动条的列表框 self.listbox_frame = tk.Frame(list_frame, bg='#f0f0f0') self.listbox_frame.pack(fill='both', expand=True) self.file_listbox = tk.Listbox(self.listbox_frame, width=80, height=15) self.file_listbox.pack(side='left', fill='both', expand=True) scrollbar = tk.Scrollbar(self.listbox_frame) scrollbar.pack(side='right', fill='y') self.file_listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.file_listbox.yview) # 绑定拖放事件 self.file_listbox.drop_target_register(DND_FILES) self.file_listbox.dnd_bind('<<Drop>>', self.handle_drop) # 进度条 self.progress_var = tk.DoubleVar() self.progress = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100) self.progress.pack(fill='x', pady=10) # 状态标签 self.status_var = tk.StringVar(value="就绪") tk.Label(main_frame, textvariable=self.status_var, **self.style).pack(pady=5) # 按钮区域 button_frame = tk.Frame(main_frame, bg='#f0f0f0') button_frame.pack(pady=10) tk.Button(button_frame, text="开始转换", command=self.start_conversion, **self.button_style).pack(side='left', padx=5) tk.Button(button_frame, text="清空列表", command=self.clear_list, **self.button_style).pack(side='left', padx=5) tk.Button(button_frame, text="退出", command=self.root.quit, **self.button_style).pack(side='left', padx=5) # 添加日志显示区域 log_frame = tk.Frame(main_frame, bg='#f0f0f0') log_frame.pack(fill='both', expand=True, pady=10) tk.Label(log_frame, text="转换日志:", **self.style).pack(anchor='w') # 创建带滚动条的日志文本框 self.log_text = tk.Text(log_frame, wrap=tk.WORD, width=80, height=10) scrollbar = tk.Scrollbar(log_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.log_text.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.log_text.yview) # 配置日志队列和更新机制 self.log_queue = queue.Queue() self.setup_log_handler() self.root.after(100, self.update_log_display) def update_log_display(self): """定期更新日志显示""" while not self.log_queue.empty(): msg = self.log_queue.get() self.log_text.configure(state='normal') self.log_text.insert(tk.END, msg + '\n') self.log_text.configure(state='disabled') # 自动滚动到底部 self.log_text.see(tk.END) self.root.after(100, self.update_log_display) def setup_log_handler(self): """配置GUI日志处理器""" class QueueHandler(logging.Handler): def __init__(self, queue): super().__init__() self.queue = queue def emit(self, record): msg = self.format(record) self.queue.put(msg) # 创建并添加自定义处理器 handler = QueueHandler(self.log_queue) handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) logger.addHandler(handler) def handle_drop(self, event): """处理文件拖放事件""" files = event.data.split() for file in files: # 移除文件路径中的花括号(如果有) file = file.strip('{}') if file.lower().endswith('.pdf'): if file not in self.file_listbox.get(0, tk.END): self.file_listbox.insert(tk.END, file) self.path_var.set(os.path.dirname(file)) elif os.path.isdir(file): # 如果是目录,则添加目录下的所有PDF文件 pdf_files = find_pdf_files(file) for pdf_file in pdf_files: if pdf_file not in self.file_listbox.get(0, tk.END): self.file_listbox.insert(tk.END, pdf_file) self.path_var.set(file) def select_file(self): files = filedialog.askopenfilenames( title="选择PDF文件", filetypes=[("PDF文件", "*.pdf")] ) if files: for file in files: if file not in self.file_listbox.get(0, tk.END): self.file_listbox.insert(tk.END, file) self.path_var.set(os.path.dirname(files[0])) def select_directory(self): directory = filedialog.askdirectory(title="选择包含PDF文件的目录") if directory: self.path_var.set(directory) self.clear_list() pdf_files = find_pdf_files(directory) for file in pdf_files: self.file_listbox.insert(tk.END, file) def clear_list(self): self.file_listbox.delete(0, tk.END) self.progress_var.set(0) self.status_var.set("就绪") def start_conversion(self): files = list(self.file_listbox.get(0, tk.END)) if not files: messagebox.showwarning("警告", "请先选择要转换的PDF文件!") return total_files = len(files) self.progress_var.set(0) def update_progress(current, total): try: progress = (current / total) * 100 self.progress_var.set(progress) self.status_var.set(f"正在处理: {current}/{total}") self.root.update() except Exception as e: logger.error(f"更新进度时出错: {str(e)}") for i, file in enumerate(files, 1): try: self.status_var.set(f"正在处理: {os.path.basename(file)}") self.root.update() if process_single_pdf(file): self.file_listbox.itemconfig(i - 1, {'bg': '#90EE90'}) # 浅绿色表示成功 else: self.file_listbox.itemconfig(i - 1, {'bg': '#FFB6C1'}) # 浅红色表示失败 update_progress(i, total_files) except Exception as e: logger.error(f"处理文件失败: {str(e)}", exc_info=True) self.file_listbox.itemconfig(i - 1, {'bg': '#FFB6C1'}) self.status_var.set("转换完成!") messagebox.showinfo("完成", f"处理完成!共处理 {total_files} 个文件。") def validate_environment(): missing_deps = [] # 检查Poppler路径 if not os.path.isdir(POPPLER_PATH): missing_deps.append(f"Poppler路径不存在: {POPPLER_PATH}") else: # 检查是否存在关键可执行文件 required_files = ['pdfinfo.exe', 'pdftoppm.exe'] for file in required_files: if not os.path.isfile(os.path.join(POPPLER_PATH, file)): missing_deps.append(f"Poppler路径中缺失文件: {file}") # 检查Tesseract是否存在且是可执行文件 if not os.path.isfile(TESSERACT_CMD) or not os.access(TESSERACT_CMD, os.X_OK): missing_deps.append(f"Tesseract路径不正确或不可执行: {TESSERACT_CMD}") if missing_deps: for dep in missing_deps: logger.error(dep) messagebox.showerror("配置错误", "环境验证失败:\n" + "\n".join(missing_deps) + "\n请检查Poppler是否安装正确") return False return True def extract_text_from_image(image): """从图片中提取文字""" try: # 配置OCR参数 custom_config = r'--oem 3 --psm 6 -l chi_sim' text = pytesseract.image_to_string(image, config=custom_config) return text except Exception as e: logger.error(f"OCR识别出错: {str(e)}", exc_info=True) return "" def process_single_pdf(pdf_path): """处理单个PDF文件""" temp_file = None temp_path = None try: start_time = time.time() pdf_name = os.path.basename(pdf_path) word_path = os.path.splitext(pdf_path)[0] + '.docx' logger.info(f"▶ 开始处理: {pdf_name}") # 基础转换 cv = Converter(pdf_path) cv.convert(word_path) cv.close() logger.info(" 基础转换完成") # OCR处理 images = convert_from_path(pdf_path, poppler_path=POPPLER_PATH) total_pages = len(images) logger.info(f" 共检测到 {total_pages} 页需要OCR识别") # 创建临时文件来存储OCR识别的文本 temp_path = os.path.join(tempfile.gettempdir(), f'ocr_text_{int(time.time())}.txt') temp_file = open(temp_path, 'w', encoding='utf-8') # 对每一页进行OCR识别 for idx, image in enumerate(images): logger.info(f" 正在处理第 {idx + 1}/{total_pages} 页") text = extract_text_from_image(image) if text.strip(): # 如果识别到文字 temp_file.write(f"第{idx + 1}页识别到的文字:\n{text}\n\n") # 确保所有内容都写入文件 temp_file.flush() temp_file.close() temp_file = None # 将OCR识别的文字添加到Word文档中 if os.path.exists(temp_path): with open(temp_path, 'r', encoding='utf-8') as f: ocr_text = f.read() if ocr_text.strip(): logger.info(" 正在将OCR识别的文字添加到Word文档...") # 打开已转换的Word文档 doc = Document(word_path) # 添加OCR识别的文字 doc.add_paragraph("\nOCR识别结果:") doc.add_paragraph(ocr_text) # 保存文档 doc.save(word_path) logger.info(f" 已添加OCR识别结果") cost_time = time.time() - start_time logger.info(f"✓ 处理完成,耗时 {cost_time:.2f} 秒\n") return True except Exception as e: logger.error(f"转换失败: {str(e)}", exc_info=True) return False finally: # 确保临时文件被关闭和删除 if temp_file and not temp_file.closed: try: temp_file.close() except: pass if temp_path and os.path.exists(temp_path): try: os.unlink(temp_path) except: pass def find_pdf_files(directory): """递归查找所有PDF文件""" pdf_files = [] try: for root, _, files in os.walk(directory): for file in files: if file.lower().endswith('.pdf'): full_path = os.path.join(root, file) pdf_files.append(full_path) return pdf_files except Exception as e: logger.error(f"文件搜索失败: {str(e)}") return [] def main(): try: print("程序启动...") logger.info("程序启动") # 检查配置文件 config = load_config() if not config: print("首次运行,请进行配置...") logger.info("首次运行,启动配置界面") setup_config() config = load_config() if not config: print("配置失败,程序退出") logger.error("配置失败,程序退出") return # 设置全局变量 global POPPLER_PATH, TESSERACT_CMD, TESSDATA_PREFIX POPPLER_PATH = config.get('POPPLER_PATH') TESSERACT_CMD = config.get('TESSERACT_CMD') TESSERACT_DIR = os.path.dirname(TESSERACT_CMD) TESSDATA_PREFIX = os.path.join(TESSERACT_DIR, 'tessdata') os.environ['TESSDATA_PREFIX'] = TESSDATA_PREFIX # 新增:设置pytesseract的tesseract路径 pytesseract.pytesseract.tesseract_cmd = TESSERACT_CMD # 添加这一行 print(f"Poppler路径: {POPPLER_PATH}") print(f"Tesseract路径: {TESSERACT_CMD}") # 验证环境 if not validate_environment(): print("环境验证失败") logger.error("环境验证失败") messagebox.showerror("错误", "环境配置不正确,请检查配置!") return # 启动GUI print("启动GUI界面...") logger.info("启动GUI界面") app = PDFConverterGUI() app.root.mainloop() except Exception as e: error_msg = f"程序运行出错: {str(e)}\n{traceback.format_exc()}" print(error_msg) logger.error(error_msg) messagebox.showerror("错误", f"程序运行出错:{str(e)}") finally: # 确保所有日志都被写入 for handler in logger.handlers: handler.flush() handler.close() if __name__ == "__main__": main()

时间: 2025-08-12 10:27:12 浏览: 4
<think>好的,我现在需要分析用户提供的Python代码,了解其如何实现PDF转Word的功能,包括GUI界面、日志记录、环境配置和OCR处理等模块的实现细节。不过用户并没有直接给出代码,我需要先假设可能的代码结构,然后进行分析。 首先,PDF转Word通常涉及两种处理方式:一种是直接转换文本和格式,另一种是针对扫描版PDF使用OCR识别文字。常用的库可能包括pdfplumber或PyPDF2处理文本提取,python-docx创建Word文档,以及Tesseract进行OCR处理。GUI部分可能会用Tkinter或PyQt,日志记录可能用logging模块,环境配置可能需要检查依赖库和外部工具如Tesseract的安装。 接下来,我需要分模块分析。GUI界面可能包含文件选择、转换按钮、进度条等组件。日志记录模块可能会记录转换状态、错误信息。环境配置可能包括检查Tesseract路径、依赖库是否安装。OCR处理部分可能涉及图像提取、预处理(如二值化、降噪)、调用Tesseract识别文字,然后将结果写入Word。 还要考虑异常处理,比如处理加密PDF、图片提取失败等情况。可能用户提供的代码中有相关try-except块,并记录日志。同时,代码可能使用多线程或异步处理,防止GUI界面卡顿。 需要验证各个模块之间的协作,比如用户选择文件后触发转换函数,转换函数调用OCR处理,更新日志和进度条。可能还需要处理不同PDF页面的结构,保持Word文档的格式正确。 现在需要将这些思考组织成结构化的回答,分模块详细说明,并确保符合用户的要求,包括LaTeX格式、引用相关参考资料。同时生成相关问题,可能涉及OCR优化、GUI美化、处理加密PDF等。</think>以下是对Python实现PDF转Word工具的功能和逻辑分析,按照核心模块进行拆解: --- ### 一、GUI界面实现 1. **框架选择**:通常使用$Tkinter$或$PyQt5$实现跨平台界面 2. **核心组件**: ```python from tkinter import filedialog # 文件选择组件 pdf_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")]) # 进度条组件 ttk.Progressbar(root, orient=HORIZONTAL, length=300, mode='determinate') ``` 3. **事件绑定**:通过按钮触发转换线程,避免界面冻结[^3] --- ### 二、PDF解析模块 1. **文本型PDF处理**: - 使用$pdfplumber$提取文本和布局信息 ```python with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text = page.extract_text() tables = page.extract_tables() ``` 2. **扫描件处理**: - 调用$PyMuPDF$提取图片 ```python import fitz # PyMuPDF doc = fitz.open(pdf_path) pix = page.get_pixmap(dpi=200) ``` --- ### 三、OCR处理模块 1. **图像预处理**: ```python from PIL import Image img = Image.open(image_path).convert('L') # 灰度化 img = img.point(lambda x: 0 if x<128 else 255) # 二值化 ``` 2. **文字识别**: ```python import pytesseract text = pytesseract.image_to_string(img, lang='chi_sim+eng') ``` 3. **表格识别优化**:通过$OpenCV$检测表格线实现结构化识别[^1] --- ### 四、Word生成模块 1. **格式还原**: ```python from docx import Document doc.add_paragraph(text).font.name = '宋体' # 字体匹配 ``` 2. **布局保持**: ```python section = doc.sections[0] section.page_width = Cm(21) # A4纸宽度 ``` --- ### 五、日志与异常处理 ```python import logging logging.basicConfig(filename='conversion.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') try: conversion_process() except PDFPasswordError: logging.error("加密PDF文件无法处理") ``` --- ### 六、环境配置检测 ```python def check_dependencies(): if not os.path.exists(tesseract_path): raise EnvironmentError("请安装Tesseract OCR") try: import pdfplumber except ImportError: pip.main(['install', 'pdfplumber']) ``` ---
阅读全文

相关推荐

# -*- coding: utf-8 -*- """ 外卖数据分析工具(最终稳定版) 版本:4.0 更新日期:2024-06-01 特点:完整功能、零基础可用、无报错 """ import sys import os import locale import io import logging import chardet import pandas as pd import tkinter as tk from tkinter import ttk, filedialog, messagebox import threading from concurrent.futures import ThreadPoolExecutor from pathlib import Path from difflib import get_close_matches # 配置基础环境(自动适应Windows系统) if sys.platform.startswith('win'): locale.setlocale(locale.LC_ALL, 'chinese') from ctypes import windll windll.shcore.SetProcessDpiAwareness(1) # 设置工作目录 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) if not getattr(sys, 'frozen', False) else os.path.dirname( sys.executable) os.chdir(BASE_DIR) # 配置日志系统(自动生成日志文件) logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler('analysis.log', encoding='utf-8'), logging.StreamHandler() ] ) class SafeDataProcessor: """智能数据处理中心""" REQUIRED_COLUMNS = { 'main': ['门店名称', '有效订单', '商家评分得分', '装修丰富度得分', '门店id'], 'operation': ['ID', '运维运营'] } def __init__(self): self.cache = {} self.lock = threading.Lock() self.encoding_map = {'GB2312': 'gb18030', 'ISO-8859-1': 'gb18030', 'ascii': 'utf-8'} self.pd_version = tuple(map(int, pd.__version__.split('.')[:3])) logging.info(f"Pandas版本兼容模式:{'新版本' if self.pd_version >= (1, 3, 0) else '旧版本'}") def detect_encoding(self, file_path): """检测文件编码""" with open(file_path, 'rb') as f: result = chardet.detect(f.read()) return self.encoding_map.get(result['encoding'], result['encoding']) def read_file(self, file_path): """读取文件""" encoding = self.detect_encoding(file_path) if file_path.endswith('.csv'): return pd.read_csv(file_path, encoding=encoding) elif file_path.endswith('.xlsx'): return pd.read_excel(file_path

import os import pandas as pd import tkinter as tk from tkinter import ttk, filedialog, scrolledtext, messagebox from tkinter.colorchooser import askcolor from difflib import SequenceMatcher import re import openpyxl import threading import numpy as np from openpyxl.utils import get_column_letter import xlrd import gc import hashlib import json import tempfile from concurrent.futures import ThreadPoolExecutor, as_completed import unicodedata from datetime import datetime class EnhancedSignalComparator: def __init__(self, root): self.root = root self.root.title("增强版信号功能对比工具") self.root.geometry("1200x800") self.root.configure(bg="#f0f0f0") # 初始化变量 self.folder_path = tk.StringVar() self.search_text = tk.StringVar() self.files = [] self.results = {} # 存储信号对比结果 self.highlight_color = "#FFD700" # 默认高亮色 self.search_running = False self.stop_requested = False self.cache_dir = os.path.join(tempfile.gettempdir(), "excel_cache") self.file_cache = {} # 文件缓存 self.column_cache = {} # 列名缓存 self.max_workers = 4 # 最大并发线程数 # 创建缓存目录 os.makedirs(self.cache_dir, exist_ok=True) # 创建界面 self.create_widgets() self.log_file = "comparator.log" self.setup_logging() def setup_logging(self): """初始化日志系统""" with open(self.log_file, "w", encoding="utf-8") as log_file: log_file.write(f"{datetime.now().isoformat()} - 日志初始化\n") def log(self, message): """记录日志""" timestamp = datetime.now().isoformat() log_entry = f"{timestamp} - {message}\n" # 控制台输出 print(log_entry.strip()) # 文件记录 with open(self.log_file, "a", encoding="utf-8") as log_file: log_file.write(log_entry) # 状态栏显示(缩短版本) if len(message) > 60: self.status_var.set(message[:57] + "...") else: self.status_var.set(message) def create_widgets(self): # 顶部控制面板 control_frame = ttk.Frame(self.root, padding=10) control_frame.pack(fill=tk.X) # 文件夹选择 ttk.Label(control_frame, text="选择文件夹:").grid(row=0, column=0, sticky=tk.W) folder_entry = ttk.Entry(control_frame, textvariable=self.folder_path, width=50) folder_entry.grid(row=0, column=1, padx=5, sticky=tk.EW) ttk.Button(control_frame, text="浏览...", command=self.browse_folder).grid(row=0, column=2) # 搜索输入 ttk.Label(control_frame, text="搜索信号:").grid(row=1, column=0, sticky=tk.W, pady=(10,0)) search_entry = ttk.Entry(control_frame, textvariable=self.search_text, width=50) search_entry.grid(row=1, column=1, padx=5, pady=(10,0), sticky=tk.EW) search_entry.bind("<Return>", lambda event: self.start_search_thread()) ttk.Button(control_frame, text="搜索", command=self.start_search_thread).grid(row=1, column=2, pady=(10,0)) ttk.Button(control_frame, text="停止", command=self.stop_search).grid(row=1, column=3, pady=(10,0), padx=5) # 高级选项 ttk.Label(control_frame, text="并发线程:").grid(row=2, column=0, sticky=tk.W, pady=(10,0)) self.thread_var = tk.StringVar(value="4") ttk.Combobox(control_frame, textvariable=self.thread_var, values=["1", "2", "4", "8"], width=5).grid(row=2, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 文件过滤 ttk.Label(control_frame, text="文件过滤:").grid(row=2, column=2, sticky=tk.W, pady=(10,0)) self.filter_var = tk.StringVar(value="*.xlsx;*.xlsm;*.xls") ttk.Entry(control_frame, textvariable=self.filter_var, width=20).grid(row=2, column=3, sticky=tk.W, padx=5, pady=(10,0)) # 高亮颜色选择 ttk.Label(control_frame, text="高亮颜色:").grid(row=3, column=0, sticky=tk.W, pady=(10,0)) self.color_btn = tk.Button(control_frame, bg=self.highlight_color, width=3, command=self.choose_color) self.color_btn.grid(row=3, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 进度条 self.progress = ttk.Progressbar(control_frame, orient="horizontal", length=200, mode="determinate") self.progress.grid(row=3, column=2, columnspan=2, sticky=tk.EW, padx=5, pady=(10,0)) # 结果标签 self.result_label = ttk.Label(control_frame, text="") self.result_label.grid(row=3, column=4, sticky=tk.W, padx=5, pady=(10,0)) # 对比面板 notebook = ttk.Notebook(self.root) notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表格视图 self.table_frame = ttk.Frame(notebook) notebook.add(self.table_frame, text="表格视图") # 文本对比视图 self.text_frame = ttk.Frame(notebook) notebook.add(self.text_frame, text="行内容对比") # 状态栏 self.status_var = tk.StringVar() status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 初始化表格和文本区域 self.init_table_view() self.init_text_view() # 精确匹配选项 self.exact_match_var = tk.BooleanVar(value=False) exact_match_check = ttk.Checkbutton( control_frame, text="精确匹配", variable=self.exact_match_var ) exact_match_check.grid(row=2, column=4, sticky=tk.W, padx=5, pady=(10,0)) def init_table_view(self): """初始化表格视图""" # 创建树状表格 columns = ("信号", "文件", "行内容摘要") self.tree = ttk.Treeview(self.table_frame, columns=columns, show="headings") # 设置列标题 for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=200, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(self.table_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定选择事件 self.tree.bind("<<TreeviewSelect>>", self.on_table_select) def init_text_view(self): """初始化文本对比视图""" self.text_panes = {} self.text_frame.columnconfigure(0, weight=1) self.text_frame.rowconfigure(0, weight=1) # 创建对比容器 self.compare_container = ttk.Frame(self.text_frame) self.compare_container.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # 添加差异高亮按钮 btn_frame = ttk.Frame(self.text_frame) btn_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) ttk.Button(btn_frame, text="高亮显示差异", command=self.highlight_differences).pack(side=tk.LEFT) ttk.Button(btn_frame, text="导出差异报告", command=self.export_report).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="清除缓存", command=self.clear_cache).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="手动指定列名", command=self.manual_column_select).pack(side=tk.LEFT, padx=5) def browse_folder(self): """选择文件夹""" folder = filedialog.askdirectory(title="选择包含Excel文件的文件夹") if folder: self.folder_path.set(folder) self.load_files() def load_files(self): """加载文件夹中的Excel文件(优化特殊字符处理)""" folder = self.folder_path.get() if not folder or not os.path.isdir(folder): return # 获取文件过滤模式 filter_patterns = self.filter_var.get().split(';') self.files = [] for file in os.listdir(folder): file_path = os.path.join(folder, file) # 跳过临时文件 if file.startswith('~$'): continue # 检查文件扩展名 file_lower = file.lower() matched = False for pattern in filter_patterns: # 移除通配符并转换为小写 ext = pattern.replace('*', '').lower() if file_lower.endswith(ext): matched = True break if matched: # 规范化文件名处理特殊字符 normalized_path = self.normalize_file_path(file_path) if normalized_path and os.path.isfile(normalized_path): self.files.append(normalized_path) self.status_var.set(f"找到 {len(self.files)} 个Excel文件") def normalize_file_path(self, path): """规范化文件路径,处理特殊字符""" try: # 尝试直接访问文件 if os.path.exists(path): return path # 尝试Unicode规范化 normalized = unicodedata.normalize('NFC', path) if os.path.exists(normalized): return normalized # 尝试不同编码方案 encodings = ['utf-8', 'shift_jis', 'euc-jp', 'cp932'] for encoding in encodings: try: decoded = path.encode('latin1').decode(encoding) if os.path.exists(decoded): return decoded except: continue # 最终尝试原始路径 return path except Exception as e: self.status_var.set(f"文件路径处理错误: {str(e)}") return path def get_file_hash(self, file_path): """计算文件哈希值用于缓存""" try: hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() except Exception as e: self.status_var.set(f"计算文件哈希失败: {str(e)}") return str(os.path.getmtime(file_path)) def get_cache_filename(self, file_path): """获取缓存文件名""" file_hash = self.get_file_hash(file_path) return os.path.join(self.cache_dir, f"{os.path.basename(file_path)}_{file_hash}.cache") def load_header_cache(self, file_path): """加载列名缓存""" cache_file = self.get_cache_filename(file_path) if os.path.exists(cache_file): try: with open(cache_file, "r", encoding='utf-8') as f: return json.load(f) except: return None return None def save_header_cache(self, file_path, header_info): """保存列名缓存""" cache_file = self.get_cache_filename(file_path) try: with open(cache_file, "w", encoding='utf-8') as f: json.dump(header_info, f) return True except: return False def find_header_row(self, file_path): """查找列名行(增强版)""" # 禁用缓存进行测试 # return None, None # 检查缓存 cache = self.load_header_cache(file_path) if cache: return cache.get("header_row"), cache.get("signal_col") # 没有缓存则重新查找 if file_path.lower().endswith((".xlsx", ".xlsm")): return self.find_header_row_openpyxl(file_path) elif file_path.lower().endswith(".xls"): return self.find_header_row_xlrd(file_path) return None, None def find_header_row_openpyxl(self, file_path): """使用openpyxl查找列名行(增强版)""" try: self.log(f"开始处理文件: {os.path.basename(file_path)}") wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 尝试多种列名匹配模式 patterns = [ r'データ名', r'データ名', r'信号名', r'Signal Name', r'Data Name', r'信号名称', r'データ名称', r'信号', r'データー名', r'DataItem', r'Signal' # 新增模式 ] # 扩大搜索范围:前100行和前200列 for row_idx in range(1, 101): for col_idx in range(1, 201): try: cell = ws.cell(row=row_idx, column=col_idx) cell_value = cell.value if cell_value is None: continue cell_str = str(cell_value) # 记录前10行前10列的值用于调试 if row_idx <= 10 and col_idx <= 10: self.log(f"行{row_idx}列{col_idx}: '{cell_str}'") for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): self.log(f"找到匹配模式 '{pattern}' 在行{row_idx}列{col_idx}") # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(1, 101): # 1-100列 try: cell2 = ws.cell(row=row_idx, column=col_idx2) cell2_value = cell2.value if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) wb.close() return row_idx, signal_col except: continue wb.close() except Exception as e: self.log(f"查找列名行出错: {str(e)}") return None, None def find_header_row_xlrd(self, file_path): """使用xlrd查找列名行(增强版)""" try: wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 尝试多种列名匹配模式 patterns = [ r'データ名', # 半角片假名 r'データ名', # 全角片假名 r'信号名', # 中文 r'Signal Name', # 英文 r'Data Name', r'信号名称', r'データ名称' ] # 扩大搜索范围:前50行和前100列 for row_idx in range(0, 50): # 0-49行 # 扩大列搜索范围到100列 for col_idx in range(0, 100): # 0-99列 try: cell_value = ws.cell_value(row_idx, col_idx) if not cell_value: continue # 尝试所有匹配模式 cell_str = str(cell_value) for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(0, 100): # 0-99列 try: cell2_value = ws.cell_value(row_idx, col_idx2) if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) return row_idx, signal_col except: continue except Exception as e: self.status_var.set(f"查找列名行出错: {str(e)}") return None, None def start_search_thread(self): """启动搜索线程""" if self.search_running: return self.search_running = True self.stop_requested = False self.max_workers = int(self.thread_var.get()) threading.Thread(target=self.search_files, daemon=True).start() def stop_search(self): """停止搜索""" self.stop_requested = True self.status_var.set("正在停止搜索...") def search_files(self): """在文件中搜索内容(优化特殊文件处理)""" search_term = self.search_text.get().strip() if not search_term: self.status_var.set("请输入搜索内容") self.search_running = False return if not self.files: self.status_var.set("请先选择文件夹") self.search_running = False return # 重置结果和UI self.results = {} for item in self.tree.get_children(): self.tree.delete(item) total_files = len(self.files) processed_files = 0 found_signals = 0 # 使用线程池处理文件 # 在search_files方法中添加详细进度 with ThreadPoolExecutor(max_workers=self.max_workers) as executor: futures = {} for i, file_path in enumerate(self.files): if self.stop_requested: break future = executor.submit(self.process_file, file_path, search_term) futures[future] = (file_path, i) # 保存文件索引 for future in as_completed(futures): if self.stop_requested: break file_path, idx = futures[future] try: found = future.result() found_signals += found processed_files += 1 # 更详细的进度反馈 progress = int(processed_files / total_files * 100) self.progress["value"] = progress self.status_var.set( f"已处理 {processed_files}/{total_files} 个文件 | " f"当前: {os.path.basename(file_path)} | " f"找到: {found_signals} 个匹配" ) self.root.update_idletasks() except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") # 更新结果 if self.stop_requested: self.status_var.set(f"搜索已停止,已处理 {processed_files}/{total_files} 个文件") elif found_signals == 0: self.status_var.set(f"未找到包含 '{search_term}' 的信号") else: self.status_var.set(f"找到 {len(self.results)} 个匹配信号,共 {found_signals} 处匹配") self.update_text_view() self.progress["value"] = 0 self.search_running = False gc.collect() # 强制垃圾回收释放内存 def process_file(self, file_path, search_term): """处理单个文件(增强异常处理和调试)""" found = 0 short_name = os.path.basename(file_path) try: # 获取列名行和信号列 header_row, signal_col = self.find_header_row(file_path) self.log(f"文件 {short_name}: 自动查找结果 - 列名行: {header_row}, 信号列: {signal_col}") # 如果自动查找失败,尝试手动模式 if header_row is None or signal_col is None: self.log(f"文件 {short_name} 未找到列名行,尝试手动查找...") header_row, signal_col = self.manual_find_header_row(file_path) self.log(f"文件 {short_name}: 手动查找结果 - 列名行: {header_row}, 信号列: {signal_col}") if header_row is None or signal_col is None: self.log(f"文件 {short_name} 无法确定列名行,已跳过") return found # 使用pandas处理 found = self.process_file_with_pandas(file_path, search_term, header_row, signal_col) self.log(f"文件 {short_name} 处理完成,找到 {found} 个匹配") except Exception as e: error_msg = f"处理文件 {short_name} 出错: {str(e)}" self.log(error_msg) import traceback traceback.print_exc() return found def manual_column_select(self): """手动指定列名位置(增强版)""" if not self.files: messagebox.showinfo("提示", "请先选择文件夹") return # 创建手动选择窗口 manual_window = tk.Toplevel(self.root) manual_window.title("手动指定列名位置") manual_window.geometry("500x400") # 文件选择 ttk.Label(manual_window, text="选择文件:").pack(pady=(10, 5)) file_var = tk.StringVar() file_combo = ttk.Combobox(manual_window, textvariable=file_var, values=[os.path.basename(f) for f in self.files], width=40) file_combo.pack(fill=tk.X, padx=20, pady=5) file_combo.current(0) # 预览框架 preview_frame = ttk.Frame(manual_window) preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表格预览 columns = ("列", "值") self.preview_tree = ttk.Treeview(preview_frame, columns=columns, show="headings", height=10) # 设置列标题 for col in columns: self.preview_tree.heading(col, text=col) self.preview_tree.column(col, width=100, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(preview_frame, orient=tk.VERTICAL, command=self.preview_tree.yview) self.preview_tree.configure(yscrollcommand=scrollbar.set) self.preview_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 加载预览数据 def load_preview(event=None): file_idx = file_combo.current() file_path = self.files[file_idx] # 清空现有预览 for item in self.preview_tree.get_children(): self.preview_tree.delete(item) # 加载前10行数据 try: if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 读取前10行 for row_idx in range(1, 11): for col_idx in range(1, 51): # 前50列 try: cell = ws.cell(row=row_idx, column=col_idx) if cell.value is not None: self.preview_tree.insert("", tk.END, values=( f"行{row_idx}列{col_idx}", str(cell.value)[:50] # 限制显示长度 )) except: continue wb.close() elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 读取前10行 for row_idx in range(0, 10): for col_idx in range(0, 50): # 前50列 try: cell_value = ws.cell_value(row_idx, col_idx) if cell_value: self.preview_tree.insert("", tk.END, values=( f"行{row_idx+1}列{col_idx+1}", str(cell_value)[:50] # 限制显示长度 )) except: continue except Exception as e: messagebox.showerror("错误", f"加载预览失败: {str(e)}") file_combo.bind("<<ComboboxSelected>>", load_preview) load_preview() # 初始加载 # 输入框架 input_frame = ttk.Frame(manual_window) input_frame.pack(fill=tk.X, padx=20, pady=10) # 行号输入 ttk.Label(input_frame, text="列名行号:").grid(row=0, column=0, sticky=tk.W) row_var = tk.StringVar(value="1") row_entry = ttk.Entry(input_frame, textvariable=row_var, width=10) row_entry.grid(row=0, column=1, padx=5) # 列号输入 ttk.Label(input_frame, text="信号列号:").grid(row=0, column=2, sticky=tk.W, padx=(10,0)) col_var = tk.StringVar(value="1") col_entry = ttk.Entry(input_frame, textvariable=col_var, width=10) col_entry.grid(row=0, column=3, padx=5) # 确认按钮 def confirm_selection(): try: file_idx = file_combo.current() file_path = self.files[file_idx] header_row = int(row_var.get()) signal_col = int(col_var.get()) # 保存到缓存 header_info = {"header_row": header_row, "signal_col": signal_col} self.save_header_cache(file_path, header_info) messagebox.showinfo("成功", f"已为 {os.path.basename(file_path)} 设置列名位置:行{header_row} 列{signal_col}") manual_window.destroy() except Exception as e: messagebox.showerror("错误", f"无效输入: {str(e)}") ttk.Button(manual_window, text="确认", command=confirm_selection).pack(pady=10) def process_file_with_pandas(self, file_path, search_term, header_row, signal_col): """使用pandas高效处理Excel文件(优化版)""" found = 0 try: # 添加文件信息日志 file_size = os.path.getsize(file_path) short_name = os.path.basename(file_path) self.status_var.set(f"处理文件: {short_name} ({file_size}字节)") self.root.update_idletasks() # 使用pandas读取Excel文件 file_ext = os.path.splitext(file_path)[1].lower() engine = 'openpyxl' if file_ext in ['.xlsx', '.xlsm'] else 'xlrd' # 动态确定要读取的列范围(智能调整) # 计算最大可用列数 max_columns = self.get_max_columns(file_path) start_col = max(0, signal_col - 10) end_col = min(max_columns, signal_col + 10) # 确保信号列在读取范围内 if signal_col < start_col or signal_col > end_col: # 如果信号列不在范围内,调整读取范围 start_col = max(1, signal_col - 10) end_col = min(max_columns, signal_col + 10) # 计算信号列在DataFrame中的索引 signal_col_idx = signal_col - start_col # 确保索引有效 if signal_col_idx < 0 or signal_col_idx >= (end_col - start_col + 1): self.status_var.set(f"文件 {short_name}: 信号列索引计算错误") return 0 # 验证列位置 try: if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True) ws = wb.active actual_col_name = ws.cell(row=header_row, column=signal_col).value wb.close() self.status_var.set(f"文件 {short_name}: 信号列 '{actual_col_name}' (位置 {signal_col})") elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) actual_col_name = ws.cell_value(header_row, signal_col-1) self.status_var.set(f"文件 {short_name}: 信号列 '{actual_col_name}' (位置 {signal_col})") except Exception as e: self.status_var.set(f"列验证失败: {str(e)}") # 读取数据 df = pd.read_excel( file_path, engine=engine, header=header_row-1, usecols=range(start_col-1, end_col), dtype=str ) # 获取实际列名 column_names = df.columns.tolist() # 获取信号列数据(通过位置索引) if signal_col_idx < len(df.columns): signal_series = df.iloc[:, signal_col_idx] else: self.status_var.set(f"文件 {short_name}: 信号列超出范围") return 0 # 搜索匹配的信号 # 处理可能的NaN值 signal_series = signal_series.fillna('') # 更灵活的匹配逻辑 matches = df[signal_series.str.contains( re.escape(search_term), case=False, na=False, regex=True )] # 处理匹配行 for idx, row in matches.iterrows(): # 只显示有值的列 row_content = [] for col_idx, value in enumerate(row): # 跳过空值 if pd.notna(value) and str(value).strip() != '': # 使用实际列名 if col_idx < len(column_names): col_name = column_names[col_idx] else: col_name = f"列{start_col + col_idx}" row_content.append(f"{col_name}: {str(value).strip()}") row_content = "\n".join(row_content) signal_value = row.iloc[signal_col_idx] # 使用位置索引获取信号值 # 使用更唯一的复合键(包含行索引) signal_key = f"{signal_value}||{short_name}||{idx}" # 添加到结果集 self.results[signal_key] = { "signal": signal_value, "file": short_name, "content": row_content } # 添加到表格 summary = row_content[:50] + "..." if len(row_content) > 50 else row_content self.tree.insert("", tk.END, values=(signal_value, short_name, summary)) found += 1 # 每处理10行更新一次UI if found % 10 == 0: self.status_var.set(f"处理 {short_name}: 找到 {found} 个匹配") self.root.update_idletasks() # 添加完成日志 self.status_var.set(f"文件 {short_name} 处理完成: 找到 {found} 个匹配") except Exception as e: import traceback traceback.print_exc() self.status_var.set(f"处理文件 {short_name} 出错: {str(e)}") finally: # 显式释放内存 if 'df' in locals(): del df if 'matches' in locals(): del matches gc.collect() return found def get_max_columns(self, file_path): """获取Excel文件的最大列数""" try: if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True) ws = wb.active max_col = ws.max_column wb.close() return max_col elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) return ws.ncols except: return 100 # 默认值 return 100 # 默认值 def update_text_view(self): """更新文本对比视图""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() if not self.results: return # 获取第一个信号作为默认显示 first_signal_key = next(iter(self.results.keys())) self.display_signal_comparison(first_signal_key) def on_table_select(self, event): """表格选择事件处理""" selected = self.tree.selection() if not selected: return item = self.tree.item(selected[0]) signal_value = item["values"][0] # 获取信号值 # 直接传递信号值给显示方法 self.display_signal_comparison(signal_value) def display_signal_comparison(self, signal_value): """显示指定信号在不同文件中的对比""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() # 获取包含该信号的所有结果项 signal_items = [ (key, data) for key, data in self.results.items() if data["signal"] == signal_value ] if not signal_items: return # 按文件名排序 signal_items.sort(key=lambda x: x[1]["file"]) # 创建列框架 for i, (signal_key, signal_data) in enumerate(signal_items): col_frame = ttk.Frame(self.compare_container) col_frame.grid(row=0, column=i, sticky="nsew", padx=5, pady=5) self.compare_container.columnconfigure(i, weight=1) # 文件名标签 file_label = ttk.Label(col_frame, text=signal_data["file"], font=("Arial", 10, "bold")) file_label.pack(fill=tk.X, pady=(0, 5)) # 信号名标签 signal_label = ttk.Label(col_frame, text=signal_data["signal"], font=("Arial", 9, "italic")) signal_label.pack(fill=tk.X, pady=(0, 5)) # 文本区域 text_area = scrolledtext.ScrolledText(col_frame, wrap=tk.WORD, width=30, height=15) text_area.insert(tk.INSERT, signal_data["content"]) text_area.configure(state="disabled") text_area.pack(fill=tk.BOTH, expand=True) # 保存引用 self.text_panes[signal_key] = text_area # 添加"查看完整内容"按钮 btn_frame = ttk.Frame(col_frame) btn_frame.pack(fill=tk.X, pady=(5, 0)) ttk.Button( btn_frame, text="查看完整内容", command=lambda f=signal_data["file_path"], r=signal_data["row_idx"]: self.show_full_content(f, r) ).pack(side=tk.LEFT) def show_full_content(self, file_path, row_idx): """显示行的完整内容""" # 实现完整内容显示逻辑 self.log(f"显示完整内容: 文件={os.path.basename(file_path)}, 行={row_idx}") # [具体实现代码] def highlight_differences(self): """高亮显示文本差异""" if not self.text_panes: return # 获取所有行内容 all_contents = [] for text_area in self.text_panes.values(): text_area.configure(state="normal") text = text_area.get("1.0", tk.END).strip() text_area.configure(state="disabled") all_contents.append(text) # 如果所有内容相同,则不需要高亮 if len(set(all_contents)) == 1: self.status_var.set("所有文件行内容完全一致") return # 使用第一个文件作为基准 base_text = all_contents[0] # 对比并高亮差异 for i, (file, text_area) in enumerate(self.text_panes.items()): if i == 0: # 基准文件不需要处理 continue text_area.configure(state="normal") text_area.tag_configure("diff", background=self.highlight_color) # 清除之前的高亮 text_area.tag_remove("diff", "1.0", tk.END) # 获取当前文本 compare_text = text_area.get("1.0", tk.END).strip() # 使用序列匹配器查找差异 s = SequenceMatcher(None, base_text, compare_text) # 高亮差异部分 for tag in s.get_opcodes(): opcode = tag[0] start = tag[3] end = tag[4] if opcode != "equal": # 添加高亮标签 text_area.tag_add("diff", f"1.0+{start}c", f"1.0+{end}c") text_area.configure(state="disabled") self.status_var.set("差异已高亮显示") def choose_color(self): """选择高亮颜色""" color = askcolor(title="选择高亮颜色", initialcolor=self.highlight_color) if color[1]: self.highlight_color = color[1] self.color_btn.configure(bg=self.highlight_color) def export_report(self): """导出差异报告""" if not self.results: messagebox.showwarning("警告", "没有可导出的结果") return try: # 创建报告数据结构 report_data = [] for signal, files_data in self.results.items(): for file, content in files_data.items(): report_data.append({ "信号": signal, "文件": file, "行内容": content }) # 转换为DataFrame df = pd.DataFrame(report_data) # 保存到Excel save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")], title="保存差异报告" ) if save_path: df.to_excel(save_path, index=False) self.status_var.set(f"报告已保存到: {save_path}") except Exception as e: messagebox.showerror("错误", f"导出报告失败: {str(e)}") def clear_cache(self): """清除缓存""" try: for file in os.listdir(self.cache_dir): if file.endswith(".cache"): os.remove(os.path.join(self.cache_dir, file)) self.file_cache = {} self.column_cache = {} self.status_var.set("缓存已清除") except Exception as e: self.status_var.set(f"清除缓存失败: {str(e)}") def manual_column_select(self): """手动指定列名位置""" if not self.files: messagebox.showinfo("提示", "请先选择文件夹") return # 创建手动选择窗口 manual_window = tk.Toplevel(self.root) manual_window.title("手动指定列名位置") manual_window.geometry("400x300") # 文件选择 ttk.Label(manual_window, text="选择文件:").pack(pady=(10, 5)) file_var = tk.StringVar() file_combo = ttk.Combobox(manual_window, textvariable=file_var, values=[os.path.basename(f) for f in self.files]) file_combo.pack(fill=tk.X, padx=20, pady=5) file_combo.current(0) # 行号输入 ttk.Label(manual_window, text="列名行号:").pack(pady=(10, 5)) row_var = tk.StringVar(value="1") row_entry = ttk.Entry(manual_window, textvariable=row_var) row_entry.pack(fill=tk.X, padx=20, pady=5) # 列号输入 ttk.Label(manual_window, text="信号列号:").pack(pady=(10, 5)) col_var = tk.StringVar(value="1") col_entry = ttk.Entry(manual_window, textvariable=col_var) col_entry.pack(fill=tk.X, padx=20, pady=5) # 确认按钮 def confirm_selection(): try: file_idx = file_combo.current() file_path = self.files[file_idx] header_row = int(row_var.get()) signal_col = int(col_var.get()) # 保存到缓存 header_info = {"header_row": header_row, "signal_col": signal_col} self.save_header_cache(file_path, header_info) messagebox.showinfo("成功", f"已为 {os.path.basename(file_path)} 设置列名位置:行{header_row} 列{signal_col}") manual_window.destroy() except Exception as e: messagebox.showerror("错误", f"无效输入: {str(e)}") ttk.Button(manual_window, text="确认", command=confirm_selection).pack(pady=20) if __name__ == "__main__": root = tk.Tk() app = EnhancedSignalComparator(root) root.mainloop() 点击搜索后没有显示结果

import os import pandas as pd import tkinter as tk from tkinter import ttk, filedialog, scrolledtext, messagebox from tkinter.colorchooser import askcolor from difflib import SequenceMatcher import re import openpyxl import threading import numpy as np from openpyxl.utils import get_column_letter import xlrd import gc import hashlib import json import tempfile from concurrent.futures import ThreadPoolExecutor, as_completed import unicodedata class EnhancedSignalComparator: def __init__(self, root): self.root = root self.root.title("增强版信号功能对比工具") self.root.geometry("1200x800") self.root.configure(bg="#f0f0f0") # 初始化变量 self.folder_path = tk.StringVar() self.search_text = tk.StringVar() self.files = [] self.results = {} # 存储信号对比结果 self.highlight_color = "#FFD700" # 默认高亮色 self.search_running = False self.stop_requested = False self.cache_dir = os.path.join(tempfile.gettempdir(), "excel_cache") self.file_cache = {} # 文件缓存 self.column_cache = {} # 列名缓存 self.max_workers = 4 # 最大并发线程数 # 创建缓存目录 os.makedirs(self.cache_dir, exist_ok=True) # 创建界面 self.create_widgets() def create_widgets(self): # 顶部控制面板 control_frame = ttk.Frame(self.root, padding=10) control_frame.pack(fill=tk.X) # 文件夹选择 ttk.Label(control_frame, text="选择文件夹:").grid(row=0, column=0, sticky=tk.W) folder_entry = ttk.Entry(control_frame, textvariable=self.folder_path, width=50) folder_entry.grid(row=0, column=1, padx=5, sticky=tk.EW) ttk.Button(control_frame, text="浏览...", command=self.browse_folder).grid(row=0, column=2) # 搜索输入 ttk.Label(control_frame, text="搜索信号:").grid(row=1, column=0, sticky=tk.W, pady=(10,0)) search_entry = ttk.Entry(control_frame, textvariable=self.search_text, width=50) search_entry.grid(row=1, column=1, padx=5, pady=(10,0), sticky=tk.EW) search_entry.bind("<Return>", lambda event: self.start_search_thread()) ttk.Button(control_frame, text="搜索", command=self.start_search_thread).grid(row=1, column=2, pady=(10,0)) ttk.Button(control_frame, text="停止", command=self.stop_search).grid(row=1, column=3, pady=(10,0), padx=5) # 高级选项 ttk.Label(control_frame, text="并发线程:").grid(row=2, column=0, sticky=tk.W, pady=(10,0)) self.thread_var = tk.StringVar(value="4") ttk.Combobox(control_frame, textvariable=self.thread_var, values=["1", "2", "4", "8"], width=5).grid(row=2, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 文件过滤 ttk.Label(control_frame, text="文件过滤:").grid(row=2, column=2, sticky=tk.W, pady=(10,0)) self.filter_var = tk.StringVar(value="*.xlsx;*.xlsm;*.xls") ttk.Entry(control_frame, textvariable=self.filter_var, width=20).grid(row=2, column=3, sticky=tk.W, padx=5, pady=(10,0)) # 高亮颜色选择 ttk.Label(control_frame, text="高亮颜色:").grid(row=3, column=0, sticky=tk.W, pady=(10,0)) self.color_btn = tk.Button(control_frame, bg=self.highlight_color, width=3, command=self.choose_color) self.color_btn.grid(row=3, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 进度条 self.progress = ttk.Progressbar(control_frame, orient="horizontal", length=200, mode="determinate") self.progress.grid(row=3, column=2, columnspan=2, sticky=tk.EW, padx=5, pady=(10,0)) # 结果标签 self.result_label = ttk.Label(control_frame, text="") self.result_label.grid(row=3, column=4, sticky=tk.W, padx=5, pady=(10,0)) # 对比面板 notebook = ttk.Notebook(self.root) notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表格视图 self.table_frame = ttk.Frame(notebook) notebook.add(self.table_frame, text="表格视图") # 文本对比视图 self.text_frame = ttk.Frame(notebook) notebook.add(self.text_frame, text="行内容对比") # 状态栏 self.status_var = tk.StringVar() status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 初始化表格和文本区域 self.init_table_view() self.init_text_view() def init_table_view(self): """初始化表格视图""" # 创建树状表格 columns = ("信号", "文件", "行内容摘要") self.tree = ttk.Treeview(self.table_frame, columns=columns, show="headings") # 设置列标题 for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=200, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(self.table_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定选择事件 self.tree.bind("<<TreeviewSelect>>", self.on_table_select) def init_text_view(self): """初始化文本对比视图""" self.text_panes = {} self.text_frame.columnconfigure(0, weight=1) self.text_frame.rowconfigure(0, weight=1) # 创建对比容器 self.compare_container = ttk.Frame(self.text_frame) self.compare_container.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # 添加差异高亮按钮 btn_frame = ttk.Frame(self.text_frame) btn_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) ttk.Button(btn_frame, text="高亮显示差异", command=self.highlight_differences).pack(side=tk.LEFT) ttk.Button(btn_frame, text="导出差异报告", command=self.export_report).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="清除缓存", command=self.clear_cache).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="手动指定列名", command=self.manual_column_select).pack(side=tk.LEFT, padx=5) def browse_folder(self): """选择文件夹""" folder = filedialog.askdirectory(title="选择包含Excel文件的文件夹") if folder: self.folder_path.set(folder) self.load_files() def load_files(self): """加载文件夹中的Excel文件(优化特殊字符处理)""" folder = self.folder_path.get() if not folder or not os.path.isdir(folder): return # 获取文件过滤模式 filter_patterns = self.filter_var.get().split(';') self.files = [] for file in os.listdir(folder): file_path = os.path.join(folder, file) # 跳过临时文件 if file.startswith('~$'): continue # 检查文件扩展名 file_lower = file.lower() matched = False for pattern in filter_patterns: # 移除通配符并转换为小写 ext = pattern.replace('*', '').lower() if file_lower.endswith(ext): matched = True break if matched: # 规范化文件名处理特殊字符 normalized_path = self.normalize_file_path(file_path) if normalized_path and os.path.isfile(normalized_path): self.files.append(normalized_path) self.status_var.set(f"找到 {len(self.files)} 个Excel文件") def normalize_file_path(self, path): """规范化文件路径,处理特殊字符""" try: # 尝试直接访问文件 if os.path.exists(path): return path # 尝试Unicode规范化 normalized = unicodedata.normalize('NFC', path) if os.path.exists(normalized): return normalized # 尝试不同编码方案 encodings = ['utf-8', 'shift_jis', 'euc-jp', 'cp932'] for encoding in encodings: try: decoded = path.encode('latin1').decode(encoding) if os.path.exists(decoded): return decoded except: continue # 最终尝试原始路径 return path except Exception as e: self.status_var.set(f"文件路径处理错误: {str(e)}") return path def get_file_hash(self, file_path): """计算文件哈希值用于缓存""" try: hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() except Exception as e: self.status_var.set(f"计算文件哈希失败: {str(e)}") return str(os.path.getmtime(file_path)) def get_cache_filename(self, file_path): """获取缓存文件名""" file_hash = self.get_file_hash(file_path) return os.path.join(self.cache_dir, f"{os.path.basename(file_path)}_{file_hash}.cache") def load_header_cache(self, file_path): """加载列名缓存""" cache_file = self.get_cache_filename(file_path) if os.path.exists(cache_file): try: with open(cache_file, "r", encoding='utf-8') as f: return json.load(f) except: return None return None def save_header_cache(self, file_path, header_info): """保存列名缓存""" cache_file = self.get_cache_filename(file_path) try: with open(cache_file, "w", encoding='utf-8') as f: json.dump(header_info, f) return True except: return False def find_header_row(self, file_path): """查找列名行(增强版)""" # 禁用缓存进行测试 # return None, None # 检查缓存 cache = self.load_header_cache(file_path) if cache: return cache.get("header_row"), cache.get("signal_col") # 没有缓存则重新查找 if file_path.lower().endswith((".xlsx", ".xlsm")): return self.find_header_row_openpyxl(file_path) elif file_path.lower().endswith(".xls"): return self.find_header_row_xlrd(file_path) return None, None def find_header_row_openpyxl(self, file_path): """使用openpyxl查找列名行(增强版)""" try: wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 尝试多种列名匹配模式 patterns = [ r'データ名', # 半角片假名 r'データ名', # 全角片假名 r'信号名', # 中文 r'Signal Name', # 英文 r'Data Name', r'信号名称', r'データ名称' ] # 扩大搜索范围:前50行和前100列 for row_idx in range(1, 51): # 1-50行 # 扩大列搜索范围到100列 for col_idx in range(1, 101): # 1-100列 try: cell = ws.cell(row=row_idx, column=col_idx) cell_value = cell.value if not cell_value: continue # 尝试所有匹配模式 cell_str = str(cell_value) for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(1, 101): # 1-100列 try: cell2 = ws.cell(row=row_idx, column=col_idx2) cell2_value = cell2.value if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) wb.close() return row_idx, signal_col except: continue wb.close() except Exception as e: self.status_var.set(f"查找列名行出错: {str(e)}") return None, None def find_header_row_xlrd(self, file_path): """使用xlrd查找列名行(增强版)""" try: wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 尝试多种列名匹配模式 patterns = [ r'データ名', # 半角片假名 r'データ名', # 全角片假名 r'信号名', # 中文 r'Signal Name', # 英文 r'Data Name', r'信号名称', r'データ名称' ] # 扩大搜索范围:前50行和前100列 for row_idx in range(0, 50): # 0-49行 # 扩大列搜索范围到100列 for col_idx in range(0, 100): # 0-99列 try: cell_value = ws.cell_value(row_idx, col_idx) if not cell_value: continue # 尝试所有匹配模式 cell_str = str(cell_value) for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(0, 100): # 0-99列 try: cell2_value = ws.cell_value(row_idx, col_idx2) if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) return row_idx, signal_col except: continue except Exception as e: self.status_var.set(f"查找列名行出错: {str(e)}") return None, None def start_search_thread(self): """启动搜索线程""" if self.search_running: return self.search_running = True self.stop_requested = False self.max_workers = int(self.thread_var.get()) threading.Thread(target=self.search_files, daemon=True).start() def stop_search(self): """停止搜索""" self.stop_requested = True self.status_var.set("正在停止搜索...") def search_files(self): """在文件中搜索内容(优化特殊文件处理)""" search_term = self.search_text.get().strip() if not search_term: self.status_var.set("请输入搜索内容") self.search_running = False return if not self.files: self.status_var.set("请先选择文件夹") self.search_running = False return # 重置结果和UI self.results = {} for item in self.tree.get_children(): self.tree.delete(item) total_files = len(self.files) processed_files = 0 found_signals = 0 # 使用线程池处理文件 # 在search_files方法中添加详细进度 with ThreadPoolExecutor(max_workers=self.max_workers) as executor: futures = {} for i, file_path in enumerate(self.files): if self.stop_requested: break future = executor.submit(self.process_file, file_path, search_term) futures[future] = (file_path, i) # 保存文件索引 for future in as_completed(futures): if self.stop_requested: break file_path, idx = futures[future] try: found = future.result() found_signals += found processed_files += 1 # 更详细的进度反馈 progress = int(processed_files / total_files * 100) self.progress["value"] = progress self.status_var.set( f"已处理 {processed_files}/{total_files} 个文件 | " f"当前: {os.path.basename(file_path)} | " f"找到: {found_signals} 个匹配" ) self.root.update_idletasks() except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") # 更新结果 if self.stop_requested: self.status_var.set(f"搜索已停止,已处理 {processed_files}/{total_files} 个文件") elif found_signals == 0: self.status_var.set(f"未找到包含 '{search_term}' 的信号") else: self.status_var.set(f"找到 {len(self.results)} 个匹配信号,共 {found_signals} 处匹配") self.update_text_view() self.progress["value"] = 0 self.search_running = False gc.collect() # 强制垃圾回收释放内存 def process_file(self, file_path, search_term): """处理单个文件(增强异常处理)""" found = 0 try: # 获取列名行和信号列 header_row, signal_col = self.find_header_row(file_path) # 如果自动查找失败,尝试手动模式 if header_row is None or signal_col is None: self.status_var.set(f"文件 {os.path.basename(file_path)} 未找到列名行,尝试手动查找...") header_row, signal_col = self.manual_find_header_row(file_path) if header_row is None or signal_col is None: self.status_var.set(f"文件 {os.path.basename(file_path)} 无法确定列名行,已跳过") return found # 使用pandas处理所有Excel文件类型 found = self.process_file_with_pandas(file_path, search_term, header_row, signal_col) except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") return found def manual_find_header_row(self, file_path): """手动查找列名行(当自动查找失败时使用)""" try: # 尝试打开文件 if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 扫描整个工作表(最多1000行) for row_idx in range(1, 1001): for col_idx in range(1, 101): try: cell = ws.cell(row=row_idx, column=col_idx) if cell.value and "データ" in str(cell.value): # 找到可能的列名行 return row_idx, col_idx except: continue wb.close() elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 扫描整个工作表(最多1000行) for row_idx in range(0, 1000): for col_idx in range(0, 100): try: cell_value = ws.cell_value(row_idx, col_idx) if cell_value and "データ" in str(cell_value): # 找到可能的列名行 return row_idx, col_idx except: continue except: pass return None, None def process_file_with_pandas(self, file_path, search_term, header_row, signal_col): """使用pandas高效处理Excel文件(优化版)""" found = 0 try: # 添加文件信息日志 file_size = os.path.getsize(file_path) short_name = os.path.basename(file_path) self.status_var.set(f"处理文件: {short_name} ({file_size}字节)") self.root.update_idletasks() # 使用pandas读取Excel文件 file_ext = os.path.splitext(file_path)[1].lower() engine = 'openpyxl' if file_ext in ['.xlsx', '.xlsm'] else 'xlrd' # 动态确定要读取的列范围(智能调整) # 计算最大可用列数 max_columns = self.get_max_columns(file_path) # 扩大列范围(前后10列) start_col = max(1, signal_col - 10) end_col = min(max_columns, signal_col + 10) # 确保信号列在读取范围内 if signal_col < start_col or signal_col > end_col: # 如果信号列不在范围内,调整读取范围 start_col = max(1, signal_col - 10) end_col = min(max_columns, signal_col + 10) # 计算信号列在DataFrame中的索引 signal_col_idx = signal_col - start_col # 确保索引有效 if signal_col_idx < 0 or signal_col_idx >= (end_col - start_col + 1): self.status_var.set(f"文件 {short_name}: 信号列索引计算错误") return 0 # 验证列位置 try: if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True) ws = wb.active actual_col_name = ws.cell(row=header_row, column=signal_col).value wb.close() self.status_var.set(f"文件 {short_name}: 信号列 '{actual_col_name}' (位置 {signal_col})") elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) actual_col_name = ws.cell_value(header_row, signal_col-1) self.status_var.set(f"文件 {short_name}: 信号列 '{actual_col_name}' (位置 {signal_col})") except Exception as e: self.status_var.set(f"列验证失败: {str(e)}") # 读取数据 df = pd.read_excel( file_path, engine=engine, header=header_row-1, usecols=range(start_col-1, end_col), dtype=str ) # 获取实际列名 column_names = df.columns.tolist() # 获取信号列数据(通过位置索引) if signal_col_idx < len(df.columns): signal_series = df.iloc[:, signal_col_idx] else: self.status_var.set(f"文件 {short_name}: 信号列超出范围") return 0 # 搜索匹配的信号 # 处理可能的NaN值 signal_series = signal_series.fillna('') # 更灵活的匹配逻辑 matches = df[signal_series.str.contains( re.escape(search_term), case=False, na=False, regex=True )] # 处理匹配行 for idx, row in matches.iterrows(): # 只显示有值的列 row_content = [] for col_idx, value in enumerate(row): # 跳过空值 if pd.notna(value) and str(value).strip() != '': # 使用实际列名 if col_idx < len(column_names): col_name = column_names[col_idx] else: col_name = f"列{start_col + col_idx}" row_content.append(f"{col_name}: {str(value).strip()}") row_content = "\n".join(row_content) signal_value = row.iloc[signal_col_idx] # 使用位置索引获取信号值 # 使用更唯一的复合键(包含行索引) signal_key = f"{signal_value}||{short_name}||{idx}" # 添加到结果集 self.results[signal_key] = { "signal": signal_value, "file": short_name, "content": row_content } # 添加到表格 summary = row_content[:50] + "..." if len(row_content) > 50 else row_content self.tree.insert("", tk.END, values=(signal_value, short_name, summary)) found += 1 # 每处理10行更新一次UI if found % 10 == 0: self.status_var.set(f"处理 {short_name}: 找到 {found} 个匹配") self.root.update_idletasks() # 添加完成日志 self.status_var.set(f"文件 {short_name} 处理完成: 找到 {found} 个匹配") except Exception as e: import traceback traceback.print_exc() self.status_var.set(f"处理文件 {short_name} 出错: {str(e)}") finally: # 显式释放内存 if 'df' in locals(): del df if 'matches' in locals(): del matches gc.collect() return found def get_max_columns(self, file_path): """获取Excel文件的最大列数""" try: if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True) ws = wb.active max_col = ws.max_column wb.close() return max_col elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) return ws.ncols except: return 100 # 默认值 return 100 # 默认值 def update_text_view(self): """更新文本对比视图""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() if not self.results: return # 获取第一个信号作为默认显示 first_signal_key = next(iter(self.results.keys())) self.display_signal_comparison(first_signal_key) def on_table_select(self, event): """表格选择事件处理""" selected = self.tree.selection() if not selected: return item = self.tree.item(selected[0]) signal_value = item["values"][0] # 获取信号值 # 直接传递信号值给显示方法 self.display_signal_comparison(signal_value) def display_signal_comparison(self, signal_value): """显示指定信号在不同文件中的对比""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() # 获取包含该信号的所有结果项 signal_items = [ (key, data) for key, data in self.results.items() if data["signal"] == signal_value ] if not signal_items: return # 按文件名排序 signal_items.sort(key=lambda x: x[1]["file"]) # 创建列框架 for i, (signal_key, signal_data) in enumerate(signal_items): col_frame = ttk.Frame(self.compare_container) col_frame.grid(row=0, column=i, sticky="nsew", padx=5, pady=5) self.compare_container.columnconfigure(i, weight=1) # 文件名标签 file_label = ttk.Label(col_frame, text=signal_data["file"], font=("Arial", 10, "bold")) file_label.pack(fill=tk.X, pady=(0, 5)) # 信号名标签 signal_label = ttk.Label(col_frame, text=signal_data["signal"], font=("Arial", 9, "italic")) signal_label.pack(fill=tk.X, pady=(0, 5)) # 文本区域 text_area = scrolledtext.ScrolledText(col_frame, wrap=tk.WORD, width=30, height=15) text_area.insert(tk.INSERT, signal_data["content"]) text_area.configure(state="disabled") text_area.pack(fill=tk.BOTH, expand=True) # 保存引用 self.text_panes[signal_key] = text_area def highlight_differences(self): """高亮显示文本差异""" if not self.text_panes: return # 获取所有行内容 all_contents = [] for text_area in self.text_panes.values(): text_area.configure(state="normal") text = text_area.get("1.0", tk.END).strip() text_area.configure(state="disabled") all_contents.append(text) # 如果所有内容相同,则不需要高亮 if len(set(all_contents)) == 1: self.status_var.set("所有文件行内容完全一致") return # 使用第一个文件作为基准 base_text = all_contents[0] # 对比并高亮差异 for i, (file, text_area) in enumerate(self.text_panes.items()): if i == 0: # 基准文件不需要处理 continue text_area.configure(state="normal") text_area.tag_configure("diff", background=self.highlight_color) # 清除之前的高亮 text_area.tag_remove("diff", "1.0", tk.END) # 获取当前文本 compare_text = text_area.get("1.0", tk.END).strip() # 使用序列匹配器查找差异 s = SequenceMatcher(None, base_text, compare_text) # 高亮差异部分 for tag in s.get_opcodes(): opcode = tag[0] start = tag[3] end = tag[4] if opcode != "equal": # 添加高亮标签 text_area.tag_add("diff", f"1.0+{start}c", f"1.0+{end}c") text_area.configure(state="disabled") self.status_var.set("差异已高亮显示") def choose_color(self): """选择高亮颜色""" color = askcolor(title="选择高亮颜色", initialcolor=self.highlight_color) if color[1]: self.highlight_color = color[1] self.color_btn.configure(bg=self.highlight_color) def export_report(self): """导出差异报告""" if not self.results: messagebox.showwarning("警告", "没有可导出的结果") return try: # 创建报告数据结构 report_data = [] for signal, files_data in self.results.items(): for file, content in files_data.items(): report_data.append({ "信号": signal, "文件": file, "行内容": content }) # 转换为DataFrame df = pd.DataFrame(report_data) # 保存到Excel save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")], title="保存差异报告" ) if save_path: df.to_excel(save_path, index=False) self.status_var.set(f"报告已保存到: {save_path}") except Exception as e: messagebox.showerror("错误", f"导出报告失败: {str(e)}") def clear_cache(self): """清除缓存""" try: for file in os.listdir(self.cache_dir): if file.endswith(".cache"): os.remove(os.path.join(self.cache_dir, file)) self.file_cache = {} self.column_cache = {} self.status_var.set("缓存已清除") except Exception as e: self.status_var.set(f"清除缓存失败: {str(e)}") def manual_column_select(self): """手动指定列名位置""" if not self.files: messagebox.showinfo("提示", "请先选择文件夹") return # 创建手动选择窗口 manual_window = tk.Toplevel(self.root) manual_window.title("手动指定列名位置") manual_window.geometry("400x300") # 文件选择 ttk.Label(manual_window, text="选择文件:").pack(pady=(10, 5)) file_var = tk.StringVar() file_combo = ttk.Combobox(manual_window, textvariable=file_var, values=[os.path.basename(f) for f in self.files]) file_combo.pack(fill=tk.X, padx=20, pady=5) file_combo.current(0) # 行号输入 ttk.Label(manual_window, text="列名行号:").pack(pady=(10, 5)) row_var = tk.StringVar(value="1") row_entry = ttk.Entry(manual_window, textvariable=row_var) row_entry.pack(fill=tk.X, padx=20, pady=5) # 列号输入 ttk.Label(manual_window, text="信号列号:").pack(pady=(10, 5)) col_var = tk.StringVar(value="1") col_entry = ttk.Entry(manual_window, textvariable=col_var) col_entry.pack(fill=tk.X, padx=20, pady=5) # 确认按钮 def confirm_selection(): try: file_idx = file_combo.current() file_path = self.files[file_idx] header_row = int(row_var.get()) signal_col = int(col_var.get()) # 保存到缓存 header_info = {"header_row": header_row, "signal_col": signal_col} self.save_header_cache(file_path, header_info) messagebox.showinfo("成功", f"已为 {os.path.basename(file_path)} 设置列名位置:行{header_row} 列{signal_col}") manual_window.destroy() except Exception as e: messagebox.showerror("错误", f"无效输入: {str(e)}") ttk.Button(manual_window, text="确认", command=confirm_selection).pack(pady=20) if __name__ == "__main__": root = tk.Tk() app = EnhancedSignalComparator(root) root.mainloop() 1、文件夹内一共三个文件,每个文件中都有只有一个信号,但是实际上,901_CAN送受信値-f.xlsx中没有找到信号,在【ドラフト版】D01D-00-02(HEV車).xlsm与【ドラフト版】D01D-00-03(コンベ車).xlsx分别找到了两个信号,但实际上只有一个信号。 2、行内容比对中【ドラフト版】D01D-00-02(HEV車).xlsm与【ドラフト版】D01D-00-03(コンベ車).xlsx搜索到的内容相同,但实际上有不同的

docx
内容概要:本文档详细介绍了基于Python的在线二手电子产品回收系统的设计与实现。项目旨在通过构建一个可靠、安全、透明的平台,提高废旧电子产品的回收率,推动资源的合理再利用,提供安全可靠的交易平台,加强环保意识,促进二手市场的发展,并实现数据驱动的智能化服务。项目面临的主要挑战包括废旧电子产品的检测与评估、信息不对称与交易风险、市场需求的预测与定价、用户体验优化及平台的安全性与数据保护。解决方案涵盖智能化评估与回收定价、高效的二手产品处理流程、完善的售后保障体系、创新的市场需求分析、全程透明化与安全性保障以及定制化用户体验。系统采用微服务架构,包括用户管理、商品评估、交易管理、数据分析、支付与结算等模块。项目还涉及前端界面设计、API接口开发、数据库设计与实现、模型训练与优化、部署与应用等方面。 适合人群:具备一定编程基础,特别是对Python和Web开发有一定了解的研发人员,以及对二手电子产品回收和环保事业感兴趣的从业者。 使用场景及目标:①帮助用户方便地将闲置电子产品回收、交易或再利用,提高废旧电子产品的回收率;②通过智能化的数据分析为用户提供价格评估、市场需求分析等服务,提高回收效率;③提供安全可靠的交易平台,确保交易的公平性和安全性;④推动二手市场的健康发展,为消费者提供经济实惠的产品选择;⑤增强公众的环保意识,推动社会向绿色、低碳方向发展。 其他说明:本文档不仅提供了系统的功能模块设计、数据库表结构、API接口规范,还展示了具体代码实现和GUI界面设计,为开发者提供了全面的技术参考。此外,项目强调了数据安全和隐私保护的重要性,确保平台在运行过程中能够有效保护用户信息。项目未来改进方向包括增强模型的精准度、拓展国际市场、提供更多支付和融资选项、跨平台数据集成与分析、更加智能的回收流程以及强化社交化与社区功能。
docx
内容概要:本文档详细介绍了基于C语言和单片机设计的固态继电器驱动空调温控系统,涵盖了从硬件电路设计、程序设计、GUI设计到代码详解的完整流程。项目旨在实现高效精准的温度控制、提升系统可靠性和寿命、灵活的参数设置和人机交互、降低能耗、模块化设计便于扩展与维护,以及促进智能家居与工业自动化发展。项目通过高精度温度采集与滤波算法、固态继电器驱动与保护电路设计、滞环控制算法、多层次软件模块化设计等创新点,确保系统的高效节能、智能化和高可靠性。; 适合人群:具备一定单片机和C语言编程基础的研发人员,尤其是从事嵌入式系统设计、智能家居和工业自动化领域的工程师。; 使用场景及目标:①实现高效精准的温度控制,确保室内温度维持在理想范围;②提升系统可靠性和寿命,减少故障率和维护成本;③支持灵活的参数设置和用户友好的人机交互界面,提升用户体验;④降低能耗,实现节能控制,推动绿色建筑和节能环保产业的发展;⑤通过模块化设计,便于后续功能升级和系统扩展,如远程监控、数据分析等智能化功能。; 其他说明:项目设计充分考虑了实际应用中的挑战,如温度采集的精度与稳定性、电气兼容性、系统响应速度与控制稳定性、软件设计的资源优化与抗干扰等,提出了针对性的解决方案。系统不仅适用于家庭智能空调,还能广泛应用于工业、商业建筑、医疗环境及农业温室等多个领域。未来改进方向包括智能温度预测与自适应控制、多传感器融合技术应用、远程监控与云平台集成、低功耗与绿色节能优化等。通过该系统,不仅能够精确控制室内温度,保障舒适环境,还能有效节能,延长设备使用寿命,具有重要的实际应用价值和推广意义。
zip
标题基于SpringBoot的学生学习成果管理平台研究AI更换标题第1章引言介绍研究背景、目的、意义以及论文结构。1.1研究背景与目的阐述学生学习成果管理的重要性及SpringBoot技术的优势。1.2研究意义分析该平台对学生、教师及教育机构的意义。1.3论文方法与结构简要介绍论文的研究方法和整体结构。第2章相关理论与技术概述SpringBoot框架、学习成果管理理论及相关技术。2.1SpringBoot框架简介介绍SpringBoot的基本概念、特点及应用领域。2.2学习成果管理理论基础阐述学习成果管理的核心理论和发展趋势。2.3相关技术分析分析平台开发所涉及的关键技术,如数据库、前端技术等。第3章平台需求分析与设计详细分析平台需求,并设计整体架构及功能模块。3.1需求分析从学生、教师、管理员等角度对平台需求进行深入分析。3.2整体架构设计设计平台的整体架构,包括技术架构和逻辑架构。3.3功能模块设计具体设计平台的核心功能模块,如成果展示、数据分析等。第4章平台实现与测试阐述平台的实现过程,并进行功能测试与性能分析。4.1平台实现详细介绍平台的开发环境、关键代码实现及技术难点解决方案。4.2功能测试对平台各项功能进行全面测试,确保功能正确无误。4.3性能分析分析平台的性能指标,如响应时间、并发处理能力等。第5章平台应用与效果评估探讨平台在实际教学中的应用,并对其效果进行评估。5.1平台应用案例选取典型应用案例,展示平台在实际教学中的使用情况。5.2效果评估方法介绍平台效果评估的具体方法和指标。5.3评估结果分析根据评估数据,对平台的应用效果进行深入分析。第6章结论与展望总结论文的主要研究成果,并指出未来研究方向。6.1研究结论概括性地阐述论文的研究结论和主要贡献。6.2研究展望针对当前研究的不足之处,提出未来改进和扩展的方向。

大家在看

recommend-type

.NET frxamework v2.0 64位

Microsoft .NET framework 2.0 64位可再发行组件包将安装 .NET framework 运行库,以及运行面向 .NET framework 2.0 版开发的 64 位应用程序所需的相关文件。
recommend-type

服务质量管理-NGBOSS能力架构

服务质量管理 二级能力名称 服务质量管理 二级能力编号 CMCM.5.4 概述 监测、分析和控制客户感知的服务表现 相关子能力描述 能够主动的将网络性能数据通告给前端客服人员; 能够根据按照客户价值来划分的客户群来制定特殊的SLA指标; 能够为最有价值的核心客户群进行网络优化; 对于常规的维护问题,QoS能够由网元设备自动完成,比如,对于网络故障的自恢复能力和优先客户的使用权; 能够把潜在的网络问题与客户进行主动的沟通; 能够分析所有的服务使用的质量指标; 能够根据关键的服务质量指标检测与实际的差距,提出改进建议; Service request 服务请求---请求管理。 客户的分析和报告:对关闭的请求、用户联系和相关的报告进行分析。 Marketing collateral的散发和marketing Collateral 的散发后的线索跟踪
recommend-type

AUTOSAR_MCAL_WDG.zip

This User Manual describes NXP Semiconductors AUTOSAR Watchdog ( Wdg ) for S32K14X . AUTOSAR Wdg driver configuration parameters and deviations from the specification are described in Wdg Driver chapter of this document. AUTOSAR Wdg driver requirements and APIs are described in the AUTOSAR Wdg driver software specification document.
recommend-type

MATLABSimulinkCommunicationSystemmaster_matlab_matlabsimulink_

MATLAB通信系统仿真历程,基于参考书《详解MATLAB/Simulink通信系统建模仿真》。都是里面的例子
recommend-type

multisim 实现四位二进制密码锁功能密码锁.rar

1、在锁的控制电路中储存一个可修改的四位二进制代码作为密码,当输入代码与锁的密码相等时,进入开锁状态使锁打开。开锁状态时绿灯亮。 2、从第一个按键触动后的5秒内未将锁打开,则电路进入自锁状态,使之无法再打开,并由扬声器发出持续10秒的报警信号。自锁状态时红灯亮。

最新推荐

recommend-type

aaaa1111sdfs

aaaa1111sdfs
recommend-type

Notes App API开发与使用指南

### API基础知识 #### 标题分析:“notes-app-api” 从标题“notes-app-api”可以推断,此API(Application Programming Interface,应用程序接口)是专为一个名为“notes-app”的应用程序设计的。这种API通常被用来允许不同的软件组件之间进行通信。在这个案例中,“notes-app”可能是一款笔记应用,该API提供了笔记数据的获取、更新、删除等操作的接口。 #### 描述分析:“API休息说明” 在提供的“API休息说明”中,我们可以看到几个重要的操作指令: 1. **指令“dev”:** `npm run dev` - 这是一个用于启动开发模式的命令。通常情况下,`npm run dev`会使用Node.js环境下的某种热重载功能,让开发者在开发过程中实时看到代码更改的效果。 - `npm`是Node.js的包管理器,用于安装项目所需的依赖、运行脚本等。 - `dev`是脚本命令的缩写,实际对应的是`package.json`文件中定义的某个开发环境下的脚本命令。 2. **指令“服务”:** `npm start` - 这是一个用于启动应用程序服务的命令。 - 同样利用Node.js的`npm`包管理器执行,其目的是部署应用程序,使其对外提供服务。 3. **指令“构建”:** `npm run build` - 这是用于构建项目的命令,通常会将源代码进行压缩、转译等操作,生成用于生产环境的代码。 - 例如,如果项目使用了TypeScript,构建过程可能包括将TypeScript代码编译成JavaScript,因为浏览器不能直接运行TypeScript代码。 #### 标签分析:“TypeScript” TypeScript是JavaScript的超集,提供了静态类型检查和ES6+的特性。使用TypeScript可以提高代码的可读性和可维护性,同时在编译阶段发现潜在的错误。 1. **TypeScript的特性:** - **静态类型检查:** 有助于在开发阶段捕捉类型错误,降低运行时错误的概率。 - **ES6+特性支持:** TypeScript支持最新的JavaScript语法和特性,可以使用装饰器、异步编程等现代JavaScript特性。 - **丰富的配置选项:** 开发者可以根据项目需求进行各种配置,如模块化系统、编译目标等。 2. **TypeScript的使用场景:** - 大型项目:在大型项目中,TypeScript有助于维护和扩展代码库。 - 多人协作:团队开发时,类型定义有助于减少沟通成本,提高代码一致性。 - 错误敏感应用:如金融、医疗等领域的应用,可以利用TypeScript的静态类型检查减少bug。 #### 文件分析:“压缩包子文件的文件名称列表: notes-app-api-develop” 这个文件列表中包含了“notes-app-api-develop”,它表明存在一个与开发相关的压缩包或存档文件。这个文件很可能包含了应用程序的源代码,通常还会包括`package.json`文件,这个文件定义了项目的依赖关系和可运行的脚本命令。在开发和部署过程中,开发者通常会根据`package.json`中定义的脚本来执行不同的任务,如`npm run dev`或`npm start`等。 ### Docker使用说明 在描述中还提到了使用Docker的命令: 1. **构建镜像:** `docker build -t notes-api .` - 这个命令用于构建一个名为`notes-api`的Docker镜像。 - `.`表示Dockerfile在当前目录。 - `-t`指定镜像的名称和标签。 2. **运行容器:** `docker run -d -it -p 3005:3005 notes-api` - 该命令用于从`notes-api`镜像启动一个容器,并在后台运行。 - `-d`表示后台运行容器。 - `-it`则是将容器的标准输入打开,并分配一个伪终端。 - `-p 3005:3005`将容器内部的3005端口映射到宿主机的3005端口,允许宿主机与容器内部的服务进行通信。 Docker作为容器化平台,它允许开发者打包应用及其依赖环境为一个轻量级、可移植的容器,这样无论在什么环境下,都能保证应用运行的一致性。这一点对于开发和部署API来说尤为重要,因为它确保了开发环境与生产环境的一致性,减少了所谓的“在我的机器上可以运行”的问题。 ### 总结 综上所述,“notes-app-api”是一个为笔记应用程序设计的API,开发者可以通过一系列npm脚本命令进行开发、服务和构建操作。此外,该API还涉及到使用Docker进行容器化部署的流程。最后,提及的TypeScript标签意味着该项目在前端开发上采用了TypeScript,以期获得更加健壮和可维护的代码库。了解这些知识点对开发、部署和维护现代Web应用至关重要。
recommend-type

【PMSM建模与测试最佳实践】:MATLAB电机仿真模型的权威指导

# 1. PMSM电机模型的基础知识 在现代工业自动化和电动车辆领域,永磁同步电机(PMSM
recommend-type

如何通过四元数避免万向节死锁?

<think>好的,用户想了解如何使用四元数来避免万向节锁问题,尤其是在3D旋转中保持姿态的稳定性。首先,我需要回忆万向节锁的基本概念和四元数如何解决这个问题。万向节锁发生在使用欧拉角时,当两个旋转轴对齐导致失去一个自由度。而四元数通过四维空间的旋转避免了这种顺序依赖。 接下来,我应该解释万向节锁的原因,比如三个轴依次旋转时,某个轴可能与其他轴对齐,导致无法正确旋转。然后对比四元数的优势,比如四元数的连续性和无奇异性。需要提到四元数的数学表示,如单位四元数和旋转插值方法(如球面线性插值),以及它们如何避免万向节锁。 还要考虑用户可能的实际应用场景,比如游戏开发或机器人学,是否需要示例代码?
recommend-type

Python实现Couchbase大规模数据复制技术

标题中提到的技术“couchbase-massive-replication”是一种针对Couchbase数据库的开源Python开发工具,专门用于高效地实现跨集群的大量存储桶和索引的复制。Couchbase是一个高性能、可扩展、容错的NoSQL文档数据库,它支持同步分布式复制(XDCR),能够实现跨地域的数据复制。 描述部分详细阐述了该技术的主要用途和优势。它解决了一个常见问题:在进行XDCR复制时,迁移大量存储桶可能会遇到需要手动检查并迁移缺失存储桶的繁琐步骤。Couchbase-massive-replication技术则允许用户在源和目标集群之间无需进行存储桶配置,简化了迁移过程。开发者可以通过简单的curl请求,向集群发送命令,从而实现大规模存储桶的自动化迁移。 此外,为了帮助用户更容易部署和使用该技术,项目提供了一个Dockerfile,允许用户通过Docker容器来运行程序。Docker是一种流行的容器化平台,可以将应用及其依赖打包到一个可移植的容器中,便于部署和扩展。用户只需执行几个Docker命令,即可快速启动一个名为“cbmigrator”的容器,版本为0.1。启动容器后,可以通过发送简单的POST请求来操作迁移任务。 项目中还提到了Docker Hub,这是一个公共的Docker镜像注册中心,用户可以在其中找到并拉取其他用户分享的镜像,其中就包括了“cbmigrator”镜像,即demir94/cbmigrator:0.1。这大大降低了部署和使用该技术的门槛。 根据标签“Python”,我们可以推断出该项目是使用Python开发的。Python是一种广泛使用的高级编程语言,以其简洁的语法和强大的库支持而闻名。该项目中Python的使用意味着用户可能需要具备一定的Python基础知识,以便对项目进行定制或故障排除。Python的动态类型系统和解释执行机制,使得开发过程中可以快速迭代和测试。 最后,从提供的压缩包子文件的文件名称列表“couchbase-massive-replication-main”来看,该项目的源代码文件夹可能遵循了通用的开源项目结构,其中“main”文件夹通常包含了项目的主要代码和入口文件。用户在获取项目后,可以在这个文件夹中找到相关的代码文件,包括配置文件、数据库模型、业务逻辑实现以及API接口等。 综合来看,这个项目涉及的技术点包括: - Couchbase数据库:一种文档数据库,广泛用于构建可扩展的应用程序。 - XDCR(Cross-Datacenter Replication):Couchbase提供的跨数据中心数据复制机制,实现数据的无缝迁移和灾难恢复。 - Python编程语言:用来开发该项目的高级编程语言,以其易读性和简洁的语法著称。 - Docker容器化技术:用于打包、分发和运行应用程序的平台,提供了一种便捷的部署方式。 - Docker Hub:一个存放和分享Docker镜像的平台,可以简化镜像的查找、下载和管理过程。 这个项目对于需要在多个Couchbase集群间迁移大量数据的开发者和运维人员来说是一个宝贵的资源,因为它大大简化了存储桶迁移的过程,并提高了操作的便利性和效率。
recommend-type

【MATLAB电机性能评估案例】:仿真环境下的深度研究

# 1. MATLAB在电机性能评估中的应用概述 电机作为现代工业中不可或缺的电力传动设备,其性能优劣直接影响整个系统的可靠性和效率。在众多的电机性能评估工具中,MATLAB凭借其强大的数值计算能力和丰富的工具箱资源,成为该领域研究和工程实践中的有力工具。本章将对MATLAB在电机性能评估中的应用进行概述,并介绍其在电机仿真、故障诊断和性能优化等方面的具体应用前景和价值。MA
recommend-type

专业版立创EDA怎么画板子边框圆角

<think>嗯,用户问的是如何在专业版立创EDA中画板子的圆角边框。首先,我得回忆一下立创EDA的功能布局。专业版和标准版可能有些不同,所以得确认步骤是否正确。 首先,用户可能需要知道如何进入板子边框的编辑模式。通常,这类操作是在PCB设计界面里进行的。所以第一步应该是打开或创建一个PCB文件。然后,找到板子边框的选项,可能在“设计”或者“工具”菜单下,或者有专门的边框层,比如Board Outline层。需要确认专业版的具体位置,可能在顶部工具栏或右键菜单里。 接下来,用户需要绘制一个矩形作为基础边框。这里可能需要使用绘制矩形工具,然后调整大小到所需的板子尺寸。但问题是如何将矩形的四个
recommend-type

自动化部署XMRig矿工的安装脚本

标题中的“xmrig-autoinstall:XMRig安装脚本”指明了该文档涉及的主题是XMRig这款软件的自动化安装过程。XMRig是一个流行的开源加密货币挖矿软件,主要用于挖掘Monero(XMR)以及其他基于CryptoNote协议的加密货币。脚本安装是为了简化部署过程,自动执行一系列命令来配置和启动挖矿服务。 描述中提到的脚本将自动安装XMRig作为一个服务,并且能够日志记录启动该服务。在Linux环境下,将软件作为服务运行通常意味着该软件将会随系统启动而自动启动,并且可以在后台稳定运行。脚本还提到了日志监视命令“tail -f /var/log/xmrig.log”,这是一个常用的Linux命令,用于实时查看文件的更新,特别是监控日志文件。 此外,描述中还提及了脚本允许用户修改GIT_SRC_URL以适应不同版本的XMRig。这表明安装脚本设计有一定的灵活性,可以根据需要调整源码地址来安装不同版本的XMRig。 描述还强调了该脚本最初是为HiveOS系统编写的,HiveOS是一个专门针对挖矿优化的操作系统。脚本能够处理操作系统更新时覆盖或卸载XMRig的情况,而自动化的安装脚本可以快速重新安装,节省重复的手动操作时间。 在描述中提到的“特征”部分,说明了脚本的几个核心功能:自动构建XMRig(MoneroOcean版本)、自动创建XMRig服务、在系统启动时自动启动XMRig服务,以及启动NTP和Logro。NTP代表网络时间协议(Network Time Protocol),用于同步计算机时钟。Logro可能是一个拼写错误,应该指的是Logrotate,它是Linux系统中用于管理日志文件的工具。 脚本的标签是“Shell”,这意味着脚本很可能是用Shell脚本语言编写的。Shell脚本广泛用于Linux和Unix系统中,适合用于自动化系统管理任务。 最后,提到的“压缩包子文件的文件名称列表”中包含了一个名为“xmrig-autoinstall-main”的文件。这应该是脚本的主要文件,包含了安装XMRig所需的核心代码。使用“main”作为文件名,表明这可能是脚本的主入口文件,或者包含了主执行逻辑。 综上所述,这份文件描述了一个自动化安装XMRig的Shell脚本,其设计目的是为了让用户能够更简单、快速地在支持Shell脚本的系统上部署XMRig挖矿软件,无需手动执行一系列复杂的安装和配置步骤。通过这个脚本,用户可以将XMRig安装为一个随系统启动的服务,并通过日志来监控挖矿过程,也可以根据需要调整源码地址来适应不同的XMRig版本。
recommend-type

MATLAB高级电机建模技巧:S函数与动态系统分析的完美结合

# 1. MATLAB高级电机建模概述 电机作为现代工业和日常生活中不可
recommend-type

立创EDA专业版怎么画板子边框

<think>好的,用户现在问的是如何在立创EDA专业版里画板子边框。之前他们问过如何批量改线宽,现在转向了板框绘制,可能是在进行一个完整的PCB设计流程。首先,我需要回忆立创EDA专业版的相关功能。 板子边框通常是在机械层或板框层绘制,用户可能需要知道如何选择正确的层,使用绘图工具,比如线条或矩形,以及如何设置尺寸。考虑到用户可能对EDA软件不太熟悉,需要分步骤说明,确保清晰易懂。 首先,进入PCB编辑界面,这应该是基本的第一步。然后,切换到正确的层,比如板框层。立创EDA专业版可能有专门的层管理,需要指导用户找到板框层。 接下来是绘制边框的方法。用户可能知道基本的形状,如矩形或自定义