From b0a6e3e3bf52192dd80ce075f2a4b4de94949cb9 Mon Sep 17 00:00:00 2001 From: taiyi Date: Mon, 11 Aug 2025 15:43:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20ArticleReplaceBatch/txt2md?= =?UTF-8?q?2docx.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新打包软件 --- ArticleReplaceBatch/txt2md2docx.py | 177 ++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 4 deletions(-) diff --git a/ArticleReplaceBatch/txt2md2docx.py b/ArticleReplaceBatch/txt2md2docx.py index 3bdaf70..1f4b716 100644 --- a/ArticleReplaceBatch/txt2md2docx.py +++ b/ArticleReplaceBatch/txt2md2docx.py @@ -7,6 +7,9 @@ from docx.shared import Inches, Pt from docx.enum.text import WD_ALIGN_PARAGRAPH import PySimpleGUI as sg from replacestr import replace_text +import configparser # 新增:导入配置文件处理模块 + +CONFIG_FILE_PATH = os.path.join(os.path.expanduser("~"), ".txt2md2docx.ini") # 配置设置 class Config: @@ -15,9 +18,12 @@ class Config: self.txt_encoding = "utf-8" self.match_pattern = "exact" # exact: 完全匹配, prefix: 前缀匹配, contains: 包含 self.output_location = "txt_folder" # txt_folder or custom + # 最近使用的文件夹路径 - 新增 + self.last_txt_folder = "" + self.last_images_root = "" + self.last_output_root = "" # 文字处理 - # self.is_replace_str = 'true' - self.reverse_text_order = False # 新增:转换文字顺序开关 + self.reverse_text_order = False # 转换文字顺序开关 # 图片处理配置 self.image_sort_by = "name" # name or time self.image_resize = "none" # none or width @@ -27,14 +33,137 @@ class Config: # 文档格式配置 self.line_spacing = 1.5 self.title_levels = 6 # 支持的最大标题层级 + self.replace_punctuation = False # 是否替换标点符号 + self.add_disclaimer = False # 是否添加免责声明 + + # 新增:从配置文件加载配置 + def load_from_file(self, file_path): + if not os.path.exists(file_path): + return False + + config_parser = configparser.ConfigParser() + config_parser.read(file_path, encoding='utf-8') + + # 加载文件处理配置 + if 'FileHandling' in config_parser: + self.txt_encoding = config_parser.get('FileHandling', 'txt_encoding', fallback=self.txt_encoding) + self.match_pattern = config_parser.get('FileHandling', 'match_pattern', fallback=self.match_pattern) + self.output_location = config_parser.get('FileHandling', 'output_location', + fallback=self.output_location) + self.last_txt_folder = config_parser.get('FileHandling', 'last_txt_folder', + fallback=self.last_txt_folder) + self.last_images_root = config_parser.get('FileHandling', 'last_images_root', + fallback=self.last_images_root) + self.last_output_root = config_parser.get('FileHandling', 'last_output_root', + fallback=self.last_output_root) + + # 加载文字处理配置 + if 'TextProcessing' in config_parser: + self.reverse_text_order = config_parser.getboolean('TextProcessing', 'reverse_text_order', + fallback=self.reverse_text_order) + self.replace_punctuation = config_parser.getboolean('TextProcessing', 'replace_punctuation', + fallback=self.replace_punctuation) + self.add_disclaimer = config_parser.getboolean('TextProcessing', 'add_disclaimer', + fallback=self.add_disclaimer) + + # 加载图片处理配置 + if 'ImageProcessing' in config_parser: + self.image_sort_by = config_parser.get('ImageProcessing', 'image_sort_by', fallback=self.image_sort_by) + self.image_resize = config_parser.get('ImageProcessing', 'image_resize', fallback=self.image_resize) + self.image_width = config_parser.getfloat('ImageProcessing', 'image_width', fallback=self.image_width) + self.image_alignment = config_parser.get('ImageProcessing', 'image_alignment', + fallback=self.image_alignment) + self.image_strategy = config_parser.get('ImageProcessing', 'image_strategy', + fallback=self.image_strategy) + + # 加载文档格式配置 + if 'DocumentFormat' in config_parser: + self.line_spacing = config_parser.getfloat('DocumentFormat', 'line_spacing', fallback=self.line_spacing) + self.title_levels = config_parser.getint('DocumentFormat', 'title_levels', fallback=self.title_levels) + + return True + + # 新增:保存配置到文件 + def save_to_file(self, file_path): + config_parser = configparser.ConfigParser() + + # 保存文件处理配置 + config_parser['FileHandling'] = { + 'txt_encoding': self.txt_encoding, + 'match_pattern': self.match_pattern, + 'output_location': self.output_location, + 'last_txt_folder': self.last_txt_folder, + 'last_images_root': self.last_images_root, + 'last_output_root': self.last_output_root + } + + # 保存文字处理配置 + config_parser['TextProcessing'] = { + 'reverse_text_order': str(self.reverse_text_order), + 'replace_punctuation': str(self.replace_punctuation), + 'add_disclaimer': str(self.add_disclaimer) + } + + # 保存图片处理配置 + config_parser['ImageProcessing'] = { + 'image_sort_by': self.image_sort_by, + 'image_resize': self.image_resize, + 'image_width': str(self.image_width), + 'image_alignment': self.image_alignment, + 'image_strategy': self.image_strategy + } + + # 保存文档格式配置 + config_parser['DocumentFormat'] = { + 'line_spacing': str(self.line_spacing), + 'title_levels': str(self.title_levels) + } + + with open(file_path, 'w', encoding='utf-8') as f: + config_parser.write(f) + + return True # 全局配置实例 config = Config() +# 新增:尝试加载配置文件 +config.load_from_file(CONFIG_FILE_PATH) + # 添加文字处理工具类(可放在FileHandler类之后) class TextProcessor: + + @staticmethod + def replace_periods(text): + # 去除文本首尾的空白字符 + text = text.strip() + + if not text: + return "" + + # 检查最后一个字符是否为句号 + last_char = text[-1] + has_ending_period = (last_char == '。') + + # 如果最后一个字符是句号,先去掉它再处理 + if has_ending_period: + content = text[:-1] + else: + content = text + + # 将所有句号替换为逗号 + content = content.replace('。', ',') + + # 如果原文本最后有句号,在处理后的内容末尾加上句号 + if has_ending_period: + result = content + '。' + else: + result = content + + return result + @staticmethod def reverse_text_order(content): """反转文本顺序(按字符级反转)""" @@ -389,6 +518,7 @@ class ImageProcessor: else: return WD_ALIGN_PARAGRAPH.CENTER +DISCLAIMER_TEXT = """[免责声明]文章的时间、过程、图片均来自于网络,文章旨在传播正能量,均无低俗等不良引导,请观众勿对号入座,并上升到人身攻击等方面。观众理性看待本事件,切勿留下主观臆断的恶意评论,互联网不是法外之地。本文如若真实性存在争议、事件版权或图片侵权问题,请及时联系作者,我们将予以删除。""" # DOCX生成模块 class DocxGenerator: @@ -460,6 +590,16 @@ class DocxGenerator: for para in paragraphs[1:]: DocxGenerator.add_formatted_paragraph(doc, para) + # 新增:在文档末尾添加免责声明 + if config.add_disclaimer: + # 添加分隔线 + doc.add_paragraph("---") + # 添加免责声明段落 + para = doc.add_paragraph() + run = para.add_run(DISCLAIMER_TEXT) + run.font.size = Pt(10) # 可设置较小字体 + para.paragraph_format.line_spacing = 1.0 # 紧凑行距 + try: doc.save(output_path) if progress_callback: @@ -475,6 +615,10 @@ class DocxGenerator: para_type = paragraph_data['type'] formatting = paragraph_data['formatting'] + # 新增:处理标点符号替换 + if config.replace_punctuation: + content = TextProcessor.replace_periods(content) + if para_type == 'unordered_list': para = doc.add_paragraph(style='List Bullet') text = content[2:].strip() @@ -625,7 +769,13 @@ def show_config_window(): sg.Radio('包含匹配', 'match', default=config.match_pattern == "contains", key='match_contains')], [sg.HSeparator()], - [sg.Checkbox('转换文字顺序', key='-REVERSE_TEXT-', default=False)], + [sg.Checkbox('转换文字顺序', key='-REVERSE_TEXT-', default=config.reverse_text_order)], + [sg.HSeparator()], + [sg.Checkbox('替换标点符号(句号转逗号,保留结尾句号)', + key='-REPLACE_PUNCTUATION-', + default=config.replace_punctuation)], + [sg.HSeparator()], + [sg.Checkbox('添加免责声明', key='-ADD_DISCLAIMER-', default=config.add_disclaimer)], [sg.HSeparator()], [sg.Radio('输出到TXT文件所在文件夹', 'output_loc', default=config.output_location == "txt_folder", key='output_txt_folder'), @@ -673,10 +823,11 @@ def show_config_window(): # 保存输出位置设置 config.output_location = "txt_folder" if values['output_txt_folder'] else "custom" - # config.is_replace_str = "true" if values['is_replace_str'] else "false" config.image_sort_by = "name" if values['sort_name'] else "time" config.image_resize = "none" if values['resize_none'] else "width" config.reverse_text_order = values['-REVERSE_TEXT-'] + config.replace_punctuation = values['-REPLACE_PUNCTUATION-'] + config.add_disclaimer = values['-ADD_DISCLAIMER-'] try: config.image_width = float(values['image_width']) @@ -697,6 +848,9 @@ def show_config_window(): else: config.image_strategy = "repeat_last" + # 新增:保存配置到文件 + config.save_to_file(CONFIG_FILE_PATH) + break window.close() @@ -909,6 +1063,12 @@ def main_window(): event, values = window.read() if event in (sg.WIN_CLOSED, '退出'): + # 只有在窗口未关闭时,才尝试读取 values + if values is not None: + 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) break if event == '转换设置': @@ -936,6 +1096,13 @@ def main_window(): sg.popup_error('请选择图片根文件夹') continue + # 保存当前选择的文件夹路径 - 新增 + 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() @@ -959,6 +1126,8 @@ def main_window(): status_text.update(f'扫描完成: 找到 {len(matched_pairs)} 个TXT文件') # 启用相关按钮 + window['-PREVIEW_TABLE-'].update(values=table_data) + window['编辑匹配'].update(disabled=False) window['开始批量转换'].update(disabled=False)