""" 主程序文件 重构后的主程序,使用模块化的设计,提供清晰的入口点。 """ import sys import os # 添加当前目录到Python路径,确保能导入模块 current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, current_dir) try: # 导入所有必要的模块 from config import config, CONFIG_FILE_PATH from file_handler import FileHandler from text_processor import TextProcessor from markdown_parser import MarkdownParser from image_processor import ImageProcessor from error_chars import ErrorCharProcessor from docx_generator import DocxGenerator from batch_processor import BatchProcessor # GUI相关导入 import PySimpleGUI as sg except ImportError as e: print(f"导入模块失败: {e}") print("请确保所有依赖包已正确安装") sys.exit(1) class TxtToDocxApp: """TXT转DOCX应用程序主类""" def __init__(self): """初始化应用程序""" self.matched_pairs = [] self.file_handler = FileHandler() self.batch_processor = BatchProcessor() # 设置GUI主题 sg.theme('BlueMono') # 加载配置 config.load_from_file(CONFIG_FILE_PATH) def run(self): """运行应用程序""" try: self._show_main_window() except Exception as e: sg.popup_error(f"应用程序运行出错: {str(e)}") finally: # 保存配置 config.save_to_file(CONFIG_FILE_PATH) def _show_main_window(self): """显示主界面""" layout = self._create_main_layout() window = sg.Window('批量Markdown TXT转DOCX工具', layout, resizable=True) try: self._handle_main_window_events(window) finally: window.close() def _create_main_layout(self): """创建主界面布局""" return [ [sg.Text('批量Markdown TXT转DOCX工具', font=('bold', 16))], [sg.Text('(按文件名匹配TXT文件和图片文件夹,支持完整Markdown格式)', text_color='gray')], [sg.HSeparator()], [sg.Text('TXT文件文件夹:', size=(15, 1)), sg.InputText(key='txt_folder', enable_events=True, default_text=config.last_txt_folder), sg.FolderBrowse('浏览')], [sg.Text('图片根文件夹:', size=(15, 1)), sg.InputText(key='images_root', enable_events=True, default_text=config.last_images_root), sg.FolderBrowse('浏览')], [sg.Text('输出根文件夹:', size=(15, 1)), sg.InputText(key='output_root', enable_events=True, default_text=config.last_output_root), sg.FolderBrowse('浏览'), sg.Text('(当选择"输出到指定文件夹"时有效)', text_color='gray')], [sg.Button('扫描文件', size=(12, 1)), sg.Button('编辑匹配', size=(12, 1), disabled=True), sg.Button('转换设置', size=(12, 1)), sg.Button('帮助', size=(8, 1))], [sg.HSeparator()], [sg.Text('匹配结果预览:', font=('bold', 10))], [sg.Table( values=[], headings=['TXT文件名', '相对路径', '匹配的图片文件夹'], key='-PREVIEW_TABLE-', auto_size_columns=False, col_widths=[20, 30, 30], justification='left', size=(None, 10) )], [sg.ProgressBar(100, orientation='h', size=(80, 20), key='progress_bar', visible=False)], [sg.Text('状态: 就绪', key='status_text', size=(80, 1))], [sg.Button('开始批量转换', size=(15, 1), disabled=True), sg.Button('退出')] ] def _handle_main_window_events(self, window): """处理主窗口事件""" progress_bar = window['progress_bar'] status_text = window['status_text'] preview_table = window['-PREVIEW_TABLE-'] output_root_input = window['output_root'] # 初始化窗口,避免更新元素时的警告 window.read(timeout=1) # 初始化输出根文件夹输入框状态 self._update_output_root_state(output_root_input) while True: event, values = window.read() if event in (sg.WIN_CLOSED, '退出'): self._save_current_settings(values) break elif event == '转换设置': self._show_config_window() self._update_output_root_state(output_root_input) elif event == '帮助': self._show_help_window() elif event == '扫描文件': self._handle_scan_files(values, window, status_text, preview_table) elif event == '编辑匹配': self._handle_edit_matching(values, preview_table) elif event == '开始批量转换': self._handle_batch_conversion(values, window, progress_bar, status_text) elif event in ('txt_folder', 'images_root') and values[event] and not values.get('output_root', ''): # 自动设置输出路径 default_output = values['txt_folder'] if values['txt_folder'] else values['images_root'] window['output_root'].update(default_output) def _update_output_root_state(self, output_root_input): """根据配置更新输出根文件夹输入框的状态""" if config.output_location == "custom": output_root_input.update(disabled=False) try: output_root_input.Widget.configure(foreground='black') except: pass else: output_root_input.update(disabled=True) try: output_root_input.Widget.configure(foreground='gray') except: pass def _save_current_settings(self, values): """保存当前设置""" if values: config.last_txt_folder = values.get('txt_folder', '') config.last_images_root = values.get('images_root', '') config.last_output_root = values.get('output_root', '') config.save_to_file(CONFIG_FILE_PATH) def _handle_scan_files(self, values, window, status_text, preview_table): """处理扫描文件事件""" txt_folder = values['txt_folder'] images_root = values['images_root'] if not txt_folder: sg.popup_error('请选择TXT文件所在的文件夹') return if not images_root: sg.popup_error('请选择图片根文件夹') return # 保存路径 config.last_txt_folder = txt_folder config.last_images_root = images_root if values['output_root']: config.last_output_root = values['output_root'] config.save_to_file(CONFIG_FILE_PATH) try: status_text.update('正在扫描TXT文件...') window.refresh() txt_files = self.file_handler.scan_txt_files(txt_folder) status_text.update('正在匹配图片文件夹...') window.refresh() self.matched_pairs = self.file_handler.find_matching_image_folders(txt_files, images_root) # 更新预览表格 table_data = [] for pair in self.matched_pairs: img_folder = pair['image_folder']['relative_path'] if pair['image_folder'] else "无匹配" table_data.append([ pair['txt']['name'], pair['txt']['relative_path'], img_folder ]) preview_table.update(values=table_data) status_text.update(f'扫描完成: 找到 {len(self.matched_pairs)} 个TXT文件') # 启用相关按钮 window['编辑匹配'].update(disabled=False) window['开始批量转换'].update(disabled=False) except Exception as e: sg.popup_error(f'扫描失败: {str(e)}') status_text.update('状态: 扫描失败') def _handle_edit_matching(self, values, preview_table): """处理编辑匹配事件""" images_root = values['images_root'] if not images_root: sg.popup_error('请选择图片根文件夹') return if not self.matched_pairs: sg.popup_error('请先扫描文件') return # 显示匹配编辑窗口 self.matched_pairs = self._show_matching_editor(self.matched_pairs, images_root) # 更新预览表格 table_data = [] for pair in self.matched_pairs: img_folder = pair['image_folder']['relative_path'] if pair['image_folder'] else "无匹配" table_data.append([ pair['txt']['name'], pair['txt']['relative_path'], img_folder ]) preview_table.update(values=table_data) def _handle_batch_conversion(self, values, window, progress_bar, status_text): """处理批量转换事件""" if not self.matched_pairs: sg.popup_error('请先扫描文件') return if config.output_location == "custom" and not values['output_root']: sg.popup_error('请选择输出根文件夹(在"转换设置"中选择了"输出到指定文件夹")') return try: progress_bar.update(0, visible=True) status_text.update('开始批量转换...') window.refresh() def update_batch_progress(progress, text): progress_bar.update(progress) status_text.update(f'状态: {text}') window.refresh() results = self.batch_processor.process_batch( self.matched_pairs, values['output_root'], update_batch_progress ) self._show_results_window(results) status_text.update('状态: 批量转换完成') except Exception as e: sg.popup_error(f'批量处理失败: {str(e)}') status_text.update('状态: 批量转换失败') finally: progress_bar.update(0, visible=False) def _show_config_window(self): """显示配置窗口""" from gui_config import show_config_window show_config_window() def _show_help_window(self): """显示帮助窗口""" help_text = """ 批量Markdown TXT转DOCX工具使用说明: 1. 选择包含Markdown内容的TXT文件所在文件夹 2. 选择图片文件夹的根目录(程序会自动查找子文件夹) 3. 选择输出文件的保存根目录(当选择"输出到指定文件夹"时有效) 4. 点击"扫描文件"按钮,程序会自动匹配TXT文件和图片文件夹 5. 查看匹配结果,可点击"编辑匹配"调整匹配关系 6. 点击"开始批量转换"生成DOCX文件 支持的Markdown格式: - 标题:# ## ### #### ##### ###### - 粗体:**文字** 或 __文字__ - 斜体:*文字* 或 _文字_ - 行内代码:`代码` - 代码块:```语言\\n代码\\n``` - 删除线:~~文字~~ - 链接:[链接文字](URL) - 图片:![图片描述](图片路径) - 无序列表:- 或 * 或 + - 有序列表:1. 2. 3. - 引用:> 引用内容 - 表格:| 列1 | 列2 | - 水平分隔线:--- 或 *** 或 ___ 文字处理功能: - 转换文字顺序:将文字内容进行特定转换处理 - 错别字处理:可以按设定强度引入常见的错别字,用于测试或特殊用途 - 标点符号替换:将句号转换为逗号,保留文末句号 输出路径选择: - 输出到TXT文件所在文件夹: 每个DOCX文件会直接保存在对应TXT文件所在的文件夹中 - 输出到指定文件夹: 所有DOCX文件会直接保存在您指定的文件夹中 匹配规则: - 完全匹配: TXT文件名(不含扩展名)与图片文件夹名完全相同 - 前缀匹配: 图片文件夹名以前缀形式包含TXT文件名 - 包含匹配: 图片文件夹名中包含TXT文件名 转换规则: - 每个小标题的第一段后会插入一张图片 - 先将Markdown格式转换为DOCX格式,再处理文字内容 - 支持文字顺序调换、错别字处理和标点符号替换功能 错别字处理说明: - 错误强度:控制替换比例,0.0表示不替换,1.0表示替换所有可能的字 - 错别字库:可自定义JSON格式的错别字映射文件 - 常见映射:的↔地↔得、在↔再、是↔事等 """ sg.popup_scrolled('使用帮助', help_text, size=(70, 25)) def _show_matching_editor(self, matched_pairs, images_root): """显示匹配编辑窗口""" from gui_matching_editor import show_matching_editor return show_matching_editor(matched_pairs, images_root) def _show_results_window(self, results): """显示结果窗口""" from gui_results import show_results_window show_results_window(results) def main(): """主函数""" print("正在启动批量Markdown TXT转DOCX工具...") try: app = TxtToDocxApp() app.run() except KeyboardInterrupt: print("\n用户中断程序运行") except Exception as e: print(f"程序运行出错: {e}") sg.popup_error(f"程序运行出错: {e}") finally: print("程序已退出") if __name__ == '__main__': main()