[Python] 纯文本查看 复制代码
import os # 文件路径操作
import pyperclip # 剪贴板操作
import re # 正则表达式
import tkinter as tk # GUI
from tkinter import filedialog, scrolledtext, messagebox # GUI组件
from docx import Document # 读取Word文档
import openpyxl # 读取Excel文件
import xlrd # 读取旧版Excel文件
import threading # 线程操作
from concurrent.futures import ThreadPoolExecutor # 线程池
import time # 时间操作
# 全局变量
file_content = [] # 存储文件内容
monitoring = False # 剪贴板监控状态
initial_clipboard_content = '' # 初始剪贴板内容
root = tk.Tk() # 创建Tkinter主窗口
text_display = None # 显示结果的文本框
toggle_button = None # 切换监控的按钮
keyword_var = tk.StringVar(root) # 关键词的Tkinter变量
result = tk.StringVar(root) # 状态信息的Tkinter变量
failed_files = [] # 存储加载失败的文件
def create_gui():
"""创建GUI界面"""
global text_display, toggle_button
root.title("划词搜索2.0-本地题库版") # 窗口标题
root.geometry("600x400") # 窗口大小
root.minsize(600, 400) # 窗口最小大小
root.attributes('-topmost', True) # 窗口置顶
tk.Label(root, text="当前剪贴板内容:").pack(pady=5) # 剪贴板内容标签
tk.Entry(root, textvariable=keyword_var, width=50).pack(pady=5) # 输入框绑定关键词变量
tk.Label(root, text="搜索结果:").pack(pady=5) # 搜索结果标签
text_display = scrolledtext.ScrolledText(root, wrap=tk.WORD, height=10, width=70, state=tk.DISABLED,
font=("微软雅黑", 12)) # 创建滚动文本框
text_display.pack(pady=5) # 显示滚动文本框
button_frame = tk.Frame(root) # 创建按钮框架
button_frame.pack(pady=5) # 显示按钮框架
tk.Button(button_frame, text="加载题库", command=select_files).pack(side=tk.LEFT, padx=5) # 加载题库按钮
toggle_button = tk.Button(button_frame, text="开始监控", command=toggle_monitoring) # 切换监控按钮
toggle_button.pack(side=tk.LEFT, padx=5) # 显示切换监控按钮
status_frame = tk.Frame(root)
status_frame.pack(fill=tk.X, pady=2, padx=10)
help_button = tk.Button(status_frame, text="帮助", command=show_help) # 帮助按钮
help_button.pack(side=tk.LEFT, padx=5) # 定位在左侧
tk.Label(status_frame, textvariable=result).pack(side=tk.LEFT, expand=True) # 状态信息标签并居中
label = tk.Label(status_frame, text="by Corvus", font=("Arial", 10)) # 创建标签
label.pack(side=tk.RIGHT) # 定位在右边
result.set("请先加载题库") # 默认提示信息
def read_file(filepath):
"""根据文件扩展名选择合适的读取方式"""
ext = os.path.splitext(filepath)[1].lower() # 获取文件扩展名并转为小写
if ext == '.docx':
return read_docx(filepath) # 读取docx文件
elif ext in ('.xlsx', '.xls'):
return read_excel(filepath) # 统一调用读取Excel文件函数
return [] # 不支持的文件类型返回空列表
def read_docx(filepath):
"""读取docx文件内容并按段落和表格存储"""
paragraphs = [] # 存储结果
try:
doc = Document(filepath) # 打开docx文件
# 读取段落
for para in doc.paragraphs: # 遍历所有段落
if para.text: # 只取非空段落
paragraphs.append(para.text) # 添加段落内容
# 读取表格
for table in doc.tables: # 遍历所有表格
for row in table.rows: # 遍历表格的每一行
row_data = [cell.text.strip() for cell in row.cells if cell.text.strip()] # 取出非空单元格内容
if row_data: # 如果行非空
paragraphs.append(' | '.join(row_data)) # 用 | 连接每行的内容并添加到结果中
# 添加分隔符
if paragraphs:
paragraphs.append("-" * 40) # 添加分隔符
except Exception as e:
print(f"Error reading docx file {filepath}: {e}")
failed_files.append(filepath) # 记录加载失败的文件
return [] # 返回空列表表示失败
return paragraphs # 返回所有段落和表格内容
def read_excel(filepath):
"""读取Excel文件内容并按段落存储"""
paragraphs = []
try:
if filepath.endswith('.xlsx'):
wb = openpyxl.load_workbook(filepath, read_only=True) # 以只读模式打开xlsx文件
else: # 处理xls文件
wb = xlrd.open_workbook(filepath) # 打开xls文件
for sheet in wb.worksheets if hasattr(wb, 'worksheets') else wb.sheets(): # 遍历所有工作表
for row in sheet.iter_rows(values_only=True) if hasattr(sheet, 'iter_rows') else range(sheet.nrows):
row_contents = [str(cell) for cell in row if cell is not None] # 取出非空单元格内容
if row_contents: # 如果行非空
paragraphs.append(' | '.join(row_contents)) # 将行内容拼接成字符串
# 添加分隔符
if paragraphs:
paragraphs.append("-" * 40) # 添加分隔符
except Exception as e:
print(f"Error reading file {filepath}: {e}") # 打印错误信息
failed_files.append(filepath) # 记录加载失败的文件
return paragraphs # 返回所有段落和表格内容
def load_files_async(filepaths):
"""异步加载文件内容"""
local_content = [] # 局部文件内容列表
total_files = len(filepaths)
batch_size = 10 # 每批处理10个文件
for i in range(0, total_files, batch_size):
batch_files = filepaths[i:i + batch_size] # 获取当前批次文件
with ThreadPoolExecutor(max_workers=4) as executor: # 创建线程池
futures = [executor.submit(read_file, filepath) for filepath in batch_files] # 提交当前批次的文件读取任务
for future in futures:
local_content.extend(future.result()) # 合并任务结果
# 更新状态信息,只有在每批处理完成后更新一次
result.set(f"已加载 {min(i + batch_size, total_files)} / {total_files} 个文件")
global file_content
file_content = local_content # 更新全局文件内容
result.set("已加载所有题库") # 更新状态信息
# 显示加载失败的文件信息
if failed_files: # 如果有加载失败的文件
result.set(f"已加载所有题库,但以下文件加载失败:")
text_display.config(state=tk.NORMAL) # 启用文本框编辑
for file in failed_files:
text_display.insert(tk.END, f"加载失败: {file}\n", "fail") # 插入失败文件信息
text_display.config(state=tk.DISABLED) # 禁用文本框编辑
failed_files.clear() # 清空失败文件列表
def select_files():
"""打开文件选择对话框"""
filepaths = filedialog.askopenfilenames(
filetypes=[("All supported files", "*.docx;*.xlsx;*.xls"),
("Word files", "*.docx"),
("Excel files", "*.xlsx;*.xls")]
)
if filepaths: # 如果选中文件
result.set("正在加载文件...") # 更新状态信息
threading.Thread(target=load_files_async, args=(filepaths,), daemon=True).start() # 异步加载文件
def toggle_monitoring():
"""切换剪贴板监控状态"""
global monitoring, initial_clipboard_content
monitoring = not monitoring # 切换监控状态
if monitoring:
initial_clipboard_content = pyperclip.paste().strip() # 获取并记录当前剪贴板内容
toggle_button.config(text="停止监控") # 更新按钮文本
result.set("正在监控剪贴板...") # 更新状态信息
threading.Thread(target=monitor_clipboard, daemon=True).start() # 启动监控线程
else:
toggle_button.config(text="开始监控") # 更新按钮文本
result.set("监控已停止") # 更新状态信息
def monitor_clipboard():
"""监控剪贴板内容变化"""
global monitoring, initial_clipboard_content
while monitoring: # 监控状态为True时
try:
tmp_value = pyperclip.paste().strip() # 获取当前剪贴板内容
if tmp_value and tmp_value != initial_clipboard_content: # 只处理新复制的内容
initial_clipboard_content = tmp_value # 更新初始剪贴板内容
keyword_var.set(tmp_value) # 更新关键词变量
threading.Thread(target=update_text_area, args=(tmp_value,), daemon=True).start() # 更新文本区域
time.sleep(0.5) # 每0.5秒检查一次
except Exception as e:
print(f"Error accessing clipboard: {e}") # 打印错误信息
time.sleep(1) # 出现错误等待1秒再试
def update_text_area(keyword_value):
"""更新文本显示区域"""
result_text = search_context(file_content, keyword_value, batch_size=10) # 在搜索时使用批处理
text_display.config(state=tk.NORMAL) # 启用文本框编辑
text_display.delete(1.0, tk.END) # 清空文本框
text_display.insert(tk.END, result_text) # 显示搜索结果
text_display.config(state=tk.DISABLED) # 禁用文本框编辑
def search_context(content_lines, keyword, batch_size=10):
"""在文件内容中搜索关键词,支持分批处理"""
if not keyword: # 如果关键词为空
return "请输入关键词" # 返回提示信息
pattern = re.escape(keyword) # 转义关键词
contexts = [] # 初始化上下文列表
total_lines = len(content_lines)
for i in range(0, total_lines, batch_size): # 按批次遍历内容
batch_lines = content_lines[i:i + batch_size] # 获取当前批次内容
for idx, paragraph in enumerate(batch_lines): # 遍历当前批次中的段落
if re.search(pattern, paragraph): # 如果段落匹配
# 获取当前段落及其后面的六个段落
start_idx = i + idx
result_paragraphs = content_lines[start_idx:start_idx + 7] # 取出当前段落和后面的六个段落
# 添加每段首行缩进两个空格
formatted_paragraphs = [" " + p for p in result_paragraphs]
# 在每行之间添加分隔符
contexts.append('\n'.join(formatted_paragraphs))
contexts.append("-" * 40) # 添加分隔符
if not contexts: # 如果未找到匹配
return f'未找到 "{keyword}"' # 返回未找到信息
return '\n\n'.join(contexts) # 返回所有匹配段落之间添加空行
def show_help():
"""显示帮助信息"""
help_text = (
"帮助文档\n\n"
"1. 使用指南:\n"
"- 加载题库:点击'加载题库'按钮可以选择多个题库加载\n"
"- 开始监控:点击'开始监控'按钮以监控剪贴板内容。\n\n"
"2. 功能说明:\n"
"- 本程序用于文件内容搜索,题库支持docx,xls,xlsx。\n"
"- 题库内容没有固定格式要求,设定为自题库内关键词所在段落,往下共显示7个段落内容。\n"
"- 剪贴板监控功能可自动检测并搜索新的剪贴板内容。\n"
"- 该程序自动置顶,可用于测试进行中不允许切换页面的程序。\n"
"- 关闭程序后,自动清空题库缓存,再次运行后,需要重新加载题库。\n"
)
messagebox.showinfo("帮助文档", help_text) # 显示帮助信息的弹出窗口
if __name__ == "__main__":
create_gui() # 创建并显示GUI
root.mainloop() # 启动Tkinter事件循环