import os
import re
import tkinter as tk
from tkinter import messagebox, ttk
import pandas as pd
from datetime import datetime
import threading
import subprocess
import time
import sys
class WordGradingApp:
def __init__(self, root):
self.root = root
self.root.title("Word 批量评分程序")
self.root.geometry("900x600")
# 变量初始化
self.word_folder_path = tk.StringVar()
self.excel_file_path = tk.StringVar()
self.word_files = []
self.extracted_info = []
self.grade_data = None
# Excel列名映射
self.id_column = tk.StringVar(value="学号")
self.name_column = tk.StringVar(value="姓名")
self.grade_column = tk.StringVar(value="期末成绩")
self.comment_column = tk.StringVar(value="评语")
# 创建界面
self.create_widgets()
# 是否已启动 LibreOffice
self.libreoffice_process = None
def create_widgets(self):
# 使用Notebook创建选项卡
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True)
# 主要操作页面
main_frame = ttk.Frame(self.notebook)
self.notebook.add(main_frame, text="主页")
# 设置页面
settings_frame = ttk.Frame(self.notebook)
self.notebook.add(settings_frame, text="设置")
# ===== 主页设计 =====
# 框架布局
frame_top = ttk.Frame(main_frame, padding=10)
frame_top.pack(fill=tk.X)
frame_middle = ttk.Frame(main_frame, padding=10)
frame_middle.pack(fill=tk.BOTH, expand=True)
frame_bottom = ttk.Frame(main_frame, padding=10)
frame_bottom.pack(fill=tk.X)
# 顶部选择区域
ttk.Label(frame_top, text="Word文件夹路径:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(frame_top, textvariable=self.word_folder_path, width=50).grid(row=0, column=1, padx=5, pady=5)
ttk.Button(frame_top, text="浏览", command=self.browse_word_folder).grid(row=0, column=2, padx=5, pady=5)
ttk.Label(frame_top, text="成绩表格文件:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(frame_top, textvariable=self.excel_file_path, width=50).grid(row=1, column=1, padx=5, pady=5)
ttk.Button(frame_top, text="浏览", command=self.browse_excel_file).grid(row=1, column=2, padx=5, pady=5)
# 特别添加 LibreOffice 启动按钮
ttk.Button(frame_top, text="启动 LibreOffice 服务", command=self.start_libreoffice).grid(row=1, column=3,
padx=5, pady=5)
ttk.Button(frame_top, text="读取文件", command=self.load_files).grid(row=2, column=1, padx=5, pady=10)
# 中间预览区域
# 创建表格视图
self.tree = ttk.Treeview(frame_middle, columns=("filename", "student_id", "name", "grade", "comment"),
show="headings")
self.tree.heading("filename", text="文件名")
self.tree.heading("student_id", text="学号")
self.tree.heading("name", text="姓名")
self.tree.heading("grade", text="期末成绩")
self.tree.heading("comment", text="评语")
self.tree.column("filename", width=250)
self.tree.column("student_id", width=100)
self.tree.column("name", width=80)
self.tree.column("grade", width=80)
self.tree.column("comment", width=300)
# 添加滚动条
scrollbar_y = ttk.Scrollbar(frame_middle, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar_y.set)
scrollbar_x = ttk.Scrollbar(frame_middle, orient=tk.HORIZONTAL, command=self.tree.xview)
self.tree.configure(xscrollcommand=scrollbar_x.set)
# 放置表格和滚动条
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
# 底部操作区域
ttk.Button(frame_bottom, text="批量填写评分", command=self.process_files).grid(row=0, column=0, padx=5, pady=5)
ttk.Button(frame_bottom, text="退出", command=self.on_exit).grid(row=0, column=1, padx=5, pady=5)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("准备就绪")
ttk.Label(frame_bottom, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).grid(row=1, column=0,
columnspan=2,
sticky=tk.W + tk.E,
padx=5, pady=5)
# 进度条
self.progress = ttk.Progressbar(frame_bottom, orient=tk.HORIZONTAL, length=100, mode='determinate')
self.progress.grid(row=2, column=0, columnspan=2, sticky=tk.W + tk.E, padx=5, pady=5)
# 文件类型选择
self.file_type_var = tk.StringVar(value="both")
file_type_frame = ttk.LabelFrame(frame_top, text="文件类型", padding=5)
file_type_frame.grid(row=3, column=0, columnspan=3, sticky=tk.W + tk.E, padx=5, pady=5)
ttk.Radiobutton(file_type_frame, text="仅 .docx 文件", variable=self.file_type_var, value="docx").pack(
side=tk.LEFT, padx=10)
ttk.Radiobutton(file_type_frame, text="仅 .doc 文件", variable=self.file_type_var, value="doc").pack(
side=tk.LEFT, padx=10)
ttk.Radiobutton(file_type_frame, text="两种格式都包含", variable=self.file_type_var, value="both").pack(
side=tk.LEFT, padx=10)
# ===== 设置页面设计 =====
# Excel列名设置
excel_frame = ttk.LabelFrame(settings_frame, text="Excel 表格列名设置", padding=10)
excel_frame.pack(fill=tk.X, padx=10, pady=10)
ttk.Label(excel_frame, text="学号列名:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(excel_frame, textvariable=self.id_column, width=20).grid(row=0, column=1, padx=5, pady=5)
ttk.Label(excel_frame, text="姓名列名:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(excel_frame, textvariable=self.name_column, width=20).grid(row=1, column=1, padx=5, pady=5)
ttk.Label(excel_frame, text="成绩列名:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(excel_frame, textvariable=self.grade_column, width=20).grid(row=2, column=1, padx=5, pady=5)
ttk.Label(excel_frame, text="评语列名:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(excel_frame, textvariable=self.comment_column, width=20).grid(row=3, column=1, padx=5, pady=5)
# 学号格式设置
id_frame = ttk.LabelFrame(settings_frame, text="学号格式设置", padding=10)
id_frame.pack(fill=tk.X, padx=10, pady=10)
self.id_length_var = tk.IntVar(value=11)
ttk.Label(id_frame, text="学号位数:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Spinbox(id_frame, from_=6, to=15, textvariable=self.id_length_var, width=5).grid(row=0, column=1,
sticky=tk.W, padx=5,
pady=5)
# LibreOffice 设置
libreoffice_frame = ttk.LabelFrame(settings_frame, text="LibreOffice 设置", padding=10)
libreoffice_frame.pack(fill=tk.X, padx=10, pady=10)
self.libreoffice_path = tk.StringVar(value="/Applications/LibreOffice.app/Contents/MacOS/soffice")
ttk.Label(libreoffice_frame, text="LibreOffice 路径:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(libreoffice_frame, textvariable=self.libreoffice_path, width=40).grid(row=0, column=1, padx=5, pady=5)
ttk.Button(libreoffice_frame, text="浏览", command=self.browse_libreoffice).grid(row=0, column=2, padx=5,
pady=5)
# Python 路径设置
python_frame = ttk.LabelFrame(settings_frame, text="Python 设置", padding=10)
python_frame.pack(fill=tk.X, padx=10, pady=10)
# 显示当前 Python 路径
current_python = sys.executable
ttk.Label(python_frame, text="当前 Python 路径:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Label(python_frame, text=current_python).grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 模块信息
modules_frame = ttk.LabelFrame(settings_frame, text="模块信息", padding=10)
modules_frame.pack(fill=tk.X, padx=10, pady=10)
# 检查必要模块
modules_info = "已安装模块:\n"
for module in ["docx", "pandas", "openpyxl"]:
try:
__import__(module)
modules_info += f"✓ {module}\n"
except ImportError:
modules_info += f"✗ {module} (未安装)\n"
# 检查 uno 模块
try:
__import__("uno")
modules_info += "✓ uno (LibreOffice API)\n"
except ImportError:
modules_info += "✗ uno (未安装,无法处理 .doc 文件)\n"
modules_info += " 请确保 Python 能够找到 LibreOffice 的 uno 模块\n"
ttk.Label(modules_frame, text=modules_info).pack(fill=tk.X, padx=5, pady=5)
# 保存设置按钮
ttk.Button(settings_frame, text="保存设置", command=self.save_settings).pack(pady=10)
# 帮助信息
help_frame = ttk.LabelFrame(settings_frame, text="帮助信息", padding=10)
help_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
help_text = tk.Text(help_frame, wrap=tk.WORD, height=10)
help_text.pack(fill=tk.BOTH, expand=True)
help_text.insert(tk.END, """Mac OS 版使用说明:
1. 设置 Excel 表格的列名:在"设置"选项卡中配置与您表格相匹配的列名。
2. 学号位数:根据学生学号的实际位数调整设置。
3. 处理 .doc 文件需要 LibreOffice 支持:
- 确保已安装 LibreOffice
- 在主页中点击"启动 LibreOffice 服务"按钮
- 如果无法启动服务,请检查 LibreOffice 路径设置
4. 基本操作:
- 选择 Word 文件夹和成绩表格
- 点击"读取文件"按钮预览匹配结果
- 确认无误后点击"批量填写评分"开始处理
常见问题:
* 如果出现"uno"模块错误,请确保使用的 Python 环境能够访问 LibreOffice 的 uno 模块
* 对于 .doc 文件,建议先转换为 .docx 格式,再进行处理
""")
help_text.config(state=tk.DISABLED)
def browse_libreoffice(self):
# 使用自定义对话框选择 LibreOffice 路径
file_entry = FileEntryDialog(self.root, "选择 LibreOffice 程序路径", [("All files", "*.*")])
if file_entry.result:
self.libreoffice_path.set(file_entry.result)
def start_libreoffice(self):
"""启动 LibreOffice 服务"""
try:
# 如果已经启动,先关闭
if self.libreoffice_process:
try:
self.libreoffice_process.terminate()
time.sleep(1)
except:
pass
libreoffice_path = self.libreoffice_path.get()
if not os.path.exists(libreoffice_path):
messagebox.showerror("错误", f"LibreOffice 路径不存在: {libreoffice_path}")
return
# 启动 LibreOffice 服务
self.status_var.set("正在启动 LibreOffice 服务...")
cmd = [
libreoffice_path,
"--headless",
"--accept=socket,host=localhost,port=2002;urp;"
]
# 启动进程
self.libreoffice_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 等待服务启动
time.sleep(3)
messagebox.showinfo("成功", "LibreOffice 服务已启动,现在可以处理 .doc 文件了。")
self.status_var.set("LibreOffice 服务已启动")
except Exception as e:
messagebox.showerror("错误", f"启动 LibreOffice 服务失败: {str(e)}")
self.status_var.set("LibreOffice 服务启动失败")
import traceback
traceback.print_exc()
def on_exit(self):
"""程序退出时关闭 LibreOffice 进程"""
if self.libreoffice_process:
try:
self.libreoffice_process.terminate()
except:
pass
self.root.quit()
def save_settings(self):
messagebox.showinfo("设置保存", "设置已保存!")
# 切换回主页
self.notebook.select(0)
def browse_word_folder(self):
# 使用替代方法避免 macOS 上的 filedialog 问题
folder_entry = FolderEntryDialog(self.root, "选择Word文件所在文件夹")
if folder_entry.result:
self.word_folder_path.set(folder_entry.result)
def browse_excel_file(self):
# 使用替代方法避免 macOS 上的 filedialog 问题
file_entry = FileEntryDialog(self.root, "选择成绩表格文件",
[("Excel files", "*.xlsx;*.xls"), ("All files", "*.*")])
if file_entry.result:
self.excel_file_path.set(file_entry.result)
def load_files(self):
# 检查路径是否存在
word_folder = self.word_folder_path.get()
excel_file = self.excel_file_path.get()
if not os.path.exists(word_folder):
messagebox.showerror("错误", "Word文件夹路径不存在!")
return
if not os.path.exists(excel_file):
messagebox.showerror("错误", "成绩表格文件不存在!")
return
self.status_var.set("正在读取文件...")
# 在后台线程中处理文件读取
threading.Thread(target=self._load_files_thread).start()
def _load_files_thread(self):
try:
# 获取设置值
id_column = self.id_column.get()
name_column = self.name_column.get()
grade_column = self.grade_column.get()
comment_column = self.comment_column.get()
id_length = self.id_length_var.get()
# 读取Excel文件
excel_file = self.excel_file_path.get()
self.grade_data = pd.read_excel(excel_file)
# 打印列名,帮助调试
print("Excel表格列名:", list(self.grade_data.columns))
# 检查列名是否存在
missing_columns = []
for col_name, label in [(id_column, "学号"), (name_column, "姓名"),
(grade_column, "成绩"), (comment_column, "评语")]:
if col_name not in self.grade_data.columns:
missing_columns.append(f"{label}({col_name})")
if missing_columns:
error_msg = f"Excel表格中缺少以下列: {', '.join(missing_columns)}。\n可用的列名: {', '.join(self.grade_data.columns)}"
self.root.after(0, lambda: messagebox.showerror("列名错误", error_msg))
self.root.after(0, lambda: self.status_var.set("读取Excel出错: 列名不匹配"))
return
# 读取Word文件
word_folder = self.word_folder_path.get()
file_type = self.file_type_var.get()
# 根据选择的文件类型筛选文件
self.word_files = []
all_files = os.listdir(word_folder)
if file_type == "docx" or file_type == "both":
self.word_files.extend([f for f in all_files if f.endswith('.docx') and not f.startswith('~$')])
if file_type == "doc" or file_type == "both":
self.word_files.extend([f for f in all_files if f.endswith('.doc') and not f.startswith('~$')])
# 处理没有找到文件的情况
if not self.word_files:
self.root.after(0, lambda: messagebox.showwarning("警告", f"在指定文件夹中没有找到Word文档文件!"))
self.root.after(0, lambda: self.status_var.set("未找到Word文件"))
return
# 提取文件名中的学号和姓名
self.extracted_info = []
for filename in self.word_files:
# 提取学号和姓名
info = self.extract_student_info(filename, id_length)
if info:
student_id, name = info
# 在成绩表中查找匹配项
match = self.find_match_in_grade_data(student_id, id_column)
if match is not None:
grade = self.grade_data.loc[match, grade_column]
comment = self.grade_data.loc[match, comment_column]
self.extracted_info.append({
'filename': filename,
'student_id': student_id,
'name': name,
'grade': grade,
'comment': comment
})
# 更新UI(回到主线程)
self.root.after(0, self.update_preview)
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", f"读取文件时出错: {str(e)}"))
self.root.after(0, lambda: self.status_var.set(f"读取文件出错: {str(e)}"))
import traceback
traceback.print_exc() # 打印详细错误信息到控制台
def update_preview(self):
# 清空现有表格
for item in self.tree.get_children():
self.tree.delete(item)
# 填充表格
for info in self.extracted_info:
self.tree.insert("", tk.END, values=(
info['filename'],
info['student_id'],
info['name'],
info['grade'],
info['comment']
))
total_files = len(self.word_files)
matched_files = len(self.extracted_info)
unmatched_files = total_files - matched_files
self.status_var.set(
f"读取结果: 总文件 {total_files} 个, 成功匹配 {matched_files} 个, 未匹配 {unmatched_files} 个")
def extract_student_info(self, filename, id_length):
# 输出调试信息
print(f"正在分析文件名: {filename}")
# 构建模式,匹配指定长度的学号
id_pattern = f"\\d{{{id_length}}}"
# 提取格式1: "通信数据分析实训-实验报告 20220218002 覃兆寿"
pattern1 = f".*\\s+({id_pattern})\\s+(\\S+)"
match1 = re.match(pattern1, filename)
if match1:
print(f"匹配格式1: 学号={match1.group(1)}, 姓名={match1.group(2)}")
return match1.group(1), match1.group(2)
# 提取格式2: "通信数据分析实训-实验报告(20220218001韦珍)"
pattern2 = f".*[(\\(]({id_pattern})(\\S+)[)\\)]"
match2 = re.match(pattern2, filename)
if match2:
print(f"匹配格式2: 学号={match2.group(1)}, 姓名={match2.group(2)}")
return match2.group(1), match2.group(2)
# 提取格式3: 尝试更宽松的模式,查找任何指定位数的数字作为学号
pattern3 = f".*({id_pattern})(\\D+).*"
match3 = re.search(pattern3, filename)
if match3:
print(f"匹配格式3: 学号={match3.group(1)}, 姓名={match3.group(2).strip()}")
return match3.group(1), match3.group(2).strip()
print(f"无法从文件名中提取学号和姓名: {filename}")
return None
def find_match_in_grade_data(self, student_id, id_column):
# 在成绩表中查找匹配的学号
for idx, row in self.grade_data.iterrows():
# 转换学号为字符串进行比较
if str(row[id_column]) == student_id:
return idx
return None
def process_files(self):
if not self.extracted_info:
messagebox.showerror("错误", "请先读取文件!")
return
# 确认对话框
confirm = messagebox.askyesno("确认", f"即将处理 {len(self.extracted_info)} 个文档,是否继续?")
if not confirm:
return
# 检查是否存在 .doc 文件但 LibreOffice 服务未启动
has_doc_files = any(info['filename'].endswith('.doc') for info in self.extracted_info)
if has_doc_files and not self.libreoffice_process:
# 提示用户启动 LibreOffice 服务
start_lo = messagebox.askyesno("需要 LibreOffice",
"处理列表中包含 .doc 文件,但 LibreOffice 服务尚未启动。\n是否现在启动 LibreOffice 服务?")
if start_lo:
self.start_libreoffice()
else:
return
# 在后台线程中处理
self.progress['maximum'] = len(self.extracted_info)
self.progress['value'] = 0
self.status_var.set("开始处理文件...")
threading.Thread(target=self._process_files_thread).start()
def _process_files_thread(self):
try:
word_folder = self.word_folder_path.get()
processed_count = 0
errors = []
for i, info in enumerate(self.extracted_info):
# 更新进度
self.root.after(0, lambda val=i: self.progress.configure(value=val))
self.root.after(0, lambda
msg=f"正在处理 ({i + 1}/{len(self.extracted_info)}): {info['filename']}": self.status_var.set(msg))
# 处理Word文档
doc_path = os.path.join(word_folder, info['filename'])
try:
# 根据文件扩展名决定使用哪个库
if doc_path.endswith('.docx'):
self.process_docx_file(doc_path, info)
elif doc_path.endswith('.doc'):
self.process_doc_file_with_libreoffice(doc_path, info)
processed_count += 1
except Exception as e:
error_msg = f"处理文件 {info['filename']} 时出错: {str(e)}"
errors.append(error_msg)
print(error_msg)
import traceback
traceback.print_exc()
self.root.after(0, lambda f=info['filename'], err=str(e): messagebox.showwarning(
"处理警告", f"处理文件 {f} 时出错: {err}")
)
# 完成处理
self.root.after(0, lambda: self.progress.configure(value=len(self.extracted_info)))
# 显示处理结果
if errors:
error_summary = "\n".join(errors[:5])
if len(errors) > 5:
error_summary += f"\n... 以及其他 {len(errors) - 5} 个错误"
self.root.after(0, lambda: self.status_var.set(f"处理完成,成功: {processed_count},失败: {len(errors)}"))
self.root.after(0, lambda err=error_summary: messagebox.showwarning("处理完成但有错误",
f"成功处理 {processed_count} 个文件,失败 {len(errors)} 个文件。\n\n主要错误:\n{err}"))
else:
self.root.after(0,
lambda count=processed_count: self.status_var.set(f"处理完成,成功处理 {count} 个文件"))
self.root.after(0,self.root.after(0, lambda count=processed_count: self.status_var.set(f"处理完成,成功处理 {count} 个文件")))
self.root.after(0, lambda count=processed_count: messagebox.showinfo("完成", f"成功处理 {count} 个文件!"))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", f"批量处理时出错: {str(e)}"))
self.root.after(0, lambda: self.status_var.set(f"批量处理出错: {str(e)}"))
import traceback
traceback.print_exc() # 打印详细错误信息到控制台
def process_docx_file(self, doc_path, info):
"""处理 .docx 文件,应用宋体四号字体,保持下划线"""
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
doc = Document(doc_path)
# 查找并替换相关段落
for i, paragraph in enumerate(doc.paragraphs):
if "实验评分:" in paragraph.text:
# 清除段落内容并重新添加文本
paragraph.clear()
run = paragraph.add_run("实验评分:")
run.font.name = '宋体'
run.font.size = Pt(14) # 四号字体约为14磅
# 添加成绩和保留空间
grade_run = paragraph.add_run(f"{info['grade']}")
grade_run.font.name = '宋体'
grade_run.font.size = Pt(14)
# 添加空白下划线
for _ in range(15):
space_run = paragraph.add_run(" ")
space_run.font.name = '宋体'
space_run.font.size = Pt(14)
space_run.font.underline = True
elif "指导教师签字:" in paragraph.text:
# 清除段落内容并重新添加文本
paragraph.clear()
run = paragraph.add_run("指导教师签字:")
run.font.name = '宋体'
run.font.size = Pt(14)
# 添加空白下划线
for _ in range(15):
space_run = paragraph.add_run(" ")
space_run.font.name = '宋体'
space_run.font.size = Pt(14)
space_run.font.underline = True
elif "年 月 日" in paragraph.text:
# 清除段落内容并重新添加文本
paragraph.clear()
current_date = datetime.now()
date_text = f"{current_date.year}年{current_date.month:02d}月{current_date.day:02d}日"
run = paragraph.add_run(date_text)
run.font.name = '宋体'
run.font.size = Pt(14)
# 添加评语,分两行显示
# 首先检查是否有评语
if info['comment'] and str(info['comment']) != 'nan':
# 将评语按句号或分号分割
comment_parts = re.split('[。;;.]', str(info['comment']))
comment_parts = [part.strip() for part in comment_parts if part.strip()]
# 添加评语标题段落
comment_para = doc.add_paragraph()
comment_title = comment_para.add_run("评语:")
comment_title.font.name = '宋体'
comment_title.font.size = Pt(14)
comment_title.bold = False
# 逐条添加评语内容
for part in comment_parts:
if part: # 确保部分不为空
part_para = doc.add_paragraph()
part_para.paragraph_format.left_indent = Pt(28) # 缩进
part_run = part_para.add_run(part)
part_run.font.name = '宋体'
part_run.font.size = Pt(14)
else:
# 如果没有评语,添加一个空的评语段落
comment_para = doc.add_paragraph()
comment_title = comment_para.add_run("评语:")
comment_title.font.name = '宋体'
comment_title.font.size = Pt(14)
# 保存文档
doc.save(doc_path)
def process_doc_file_with_libreoffice(self, doc_path, info):
"""使用 LibreOffice 处理 .doc 文件,专门为 macOS 优化"""
# 创建临时的 docx 文件
temp_docx = doc_path + ".docx"
try:
# 第1步:使用 LibreOffice 将 .doc 转换为 .docx
if not self.convert_doc_to_docx(doc_path, temp_docx):
raise Exception("无法将 .doc 转换为 .docx 格式")
# 第2步:使用 python-docx 处理 .docx 文件
from docx import Document
doc = Document(temp_docx)
# 查找评分行并替换内容
for paragraph in doc.paragraphs:
if "实验评分:" in paragraph.text:
# 替换评分
paragraph.text = f"实验评分:{info['grade']} "
elif "指导教师签字:" in paragraph.text:
# 保持原样
continue
elif "年 月 日" in paragraph.text:
# 替换日期并添加评语
current_date = datetime.now()
paragraph.text = f"{current_date.year}年{current_date.month:02d}月{current_date.day:02d}日"
# 在日期后添加新段落,包含评语
new_paragraph = doc.add_paragraph()
new_paragraph.text = f"评语:{info['comment']}"
# 保存 docx 文件
doc.save(temp_docx)
# 第3步:使用 LibreOffice 将修改后的 .docx 转换回 .doc
if not self.convert_docx_to_doc(temp_docx, doc_path):
raise Exception("无法将修改后的文件转换回 .doc 格式")
finally:
# 清理临时文件
if os.path.exists(temp_docx):
try:
os.remove(temp_docx)
except:
pass
def convert_doc_to_docx(self, src_path, dest_path):
"""使用 LibreOffice 将 .doc 转换为 .docx"""
try:
# 确保有完整路径
src_path = os.path.abspath(src_path)
dest_path = os.path.abspath(dest_path)
# 获取目标文件夹
dest_dir = os.path.dirname(dest_path)
# 构建 LibreOffice 命令
cmd = [
self.libreoffice_path.get(),
"--headless",
"--convert-to", "docx",
"--outdir", dest_dir,
src_path
]
# 执行命令
print(f"执行命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"转换失败,返回码: {result.returncode}")
print(f"标准输出: {result.stdout}")
print(f"标准错误: {result.stderr}")
return False
# 获取生成的文件名
converted_file = os.path.join(dest_dir, os.path.splitext(os.path.basename(src_path))[0] + ".docx")
# 如果转换后的文件名与目标不同,重命名
if converted_file != dest_path and os.path.exists(converted_file):
os.rename(converted_file, dest_path)
return os.path.exists(dest_path)
except Exception as e:
print(f"转换过程出错: {str(e)}")
import traceback
traceback.print_exc()
return False
def convert_docx_to_doc(self, src_path, dest_path):
"""使用 LibreOffice 将 .docx 转换为 .doc"""
try:
# 确保有完整路径
src_path = os.path.abspath(src_path)
dest_path = os.path.abspath(dest_path)
# 获取目标文件夹
dest_dir = os.path.dirname(dest_path)
# 构建 LibreOffice 命令
cmd = [
self.libreoffice_path.get(),
"--headless",
"--convert-to", "doc:MS Word 97",
"--outdir", dest_dir,
src_path
]
# 执行命令
print(f"执行命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"转换失败,返回码: {result.returncode}")
print(f"标准输出: {result.stdout}")
print(f"标准错误: {result.stderr}")
return False
# 获取生成的文件名
converted_file = os.path.join(dest_dir, os.path.splitext(os.path.basename(src_path))[0] + ".doc")
# 如果转换后的文件名与目标不同,重命名
if converted_file != dest_path and os.path.exists(converted_file):
# 如果目标文件已存在,需要先删除
if os.path.exists(dest_path):
os.remove(dest_path)
os.rename(converted_file, dest_path)
return os.path.exists(dest_path)
except Exception as e:
print(f"转换过程出错: {str(e)}")
import traceback
traceback.print_exc()
return False
# 自定义对话框:文件夹选择
class FolderEntryDialog:
def __init__(self, parent, title):
self.result = None
# 创建顶级窗口
self.dialog = tk.Toplevel(parent)
self.dialog.title(title)
self.dialog.geometry("500x120")
self.dialog.transient(parent)
self.dialog.grab_set()
# 创建控件
ttk.Label(self.dialog, text="请输入文件夹路径:").pack(padx=10, pady=10)
self.entry = ttk.Entry(self.dialog, width=50)
self.entry.pack(padx=10, pady=5, fill=tk.X)
button_frame = ttk.Frame(self.dialog)
button_frame.pack(padx=10, pady=10)
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=5)
# 设置居中
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
x = (parent.winfo_rootx() + parent.winfo_width() // 2) - (width // 2)
y = (parent.winfo_rooty() + parent.winfo_height() // 2) - (height // 2)
self.dialog.geometry(f"+{x}+{y}")
# 等待对话框关闭
self.dialog.wait_window()
def on_ok(self):
path = self.entry.get()
if path:
self.result = path
self.dialog.destroy()
def on_cancel(self):
self.dialog.destroy()
# 自定义对话框:文件选择
class FileEntryDialog:
def __init__(self, parent, title, filetypes):
self.result = None
self.filetypes = filetypes
# 创建顶级窗口
self.dialog = tk.Toplevel(parent)
self.dialog.title(title)
self.dialog.geometry("500x120")
self.dialog.transient(parent)
self.dialog.grab_set()
# 创建控件
ttk.Label(self.dialog, text="请输入文件路径:").pack(padx=10, pady=10)
self.entry = ttk.Entry(self.dialog, width=50)
self.entry.pack(padx=10, pady=5, fill=tk.X)
button_frame = ttk.Frame(self.dialog)
button_frame.pack(padx=10, pady=10)
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=5)
# 设置居中
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
x = (parent.winfo_rootx() + parent.winfo_width() // 2) - (width // 2)
y = (parent.winfo_rooty() + parent.winfo_height() // 2) - (height // 2)
self.dialog.geometry(f"+{x}+{y}")
# 等待对话框关闭
self.dialog.wait_window()
def on_ok(self):
path = self.entry.get()
if path:
self.result = path
self.dialog.destroy()
def on_cancel(self):
self.dialog.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = WordGradingApp(root)
root.mainloop()