乐于分享
好东西不私藏

fixWord再升级!全新v5.0.0版本来了

fixWord再升级!全新v5.0.0版本来了

fixWord
又!升!级!了!
V5.0.0
此版本新增了一些功能
进行了若干个问题的修复
进一步优化体验
新增:在线更新功能
修复:个别文档处理时程序卡住的问题
欢迎使用
!!!
此外
v4.3.5与D版本已合并
加入了自定义配置的功能,操作更方便

支持用户自定义输出格式:

页边距、字体、字号、行距、各级标题格式等

支持批量文档处理,输入文件夹路径,自动判断;

配置保存/导入;

访问主页(Gitee)
https://gitee.com/cxmStudio/fixWord
访问主页(Github)
https://github.com/Sam-CXM/fixWord
手动选择/点击按钮选择文件或文件夹路径
点击【开始处理】按钮即可快速处理
详细内容见下文

一、前期准备

  • 开发工具
VisualStudio Code
  • 编程语言
Python
  • 第三方库
python-docx
    pyinstaller
    pillow
二、功能
  • 标题
  • 一级标题
  • 二级标题
  • 三级标题
  • 段落间距
  • 页边距
  • 页码
  • 数字、英文
  • 提取图片
  • 其他
    字体、字号、替换文字(符号)
三、部分代码
  • 导入库
from docx import Documentfrom docx.shared import Pt, Cm  # 用来设置字体的大小from docx.oxml.ns import qn  # 控件名称from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_PARAGRAPH_ALIGNMENT  # 设置对其方式from docx.oxml import OxmlElement, parse_xmlfrom os import listdir, path, makedirs, getcwd, startfilefrom tkinter import Tk, Entry, Button, Label, filedialog, messagebox, SUNKEN, Radiobutton, Frame, ttk, Listbox, StringVar, END, Toplevel, Canvas, Menu, LabelFrame, Spinboxfrom tkinter import font as tkFontfrom time import localtime, strftimefrom PIL import Image, ImageTkfrom webbrowser import open as webopenfrom configparser import ConfigParserfrom upGrade import upGrade as updatefrom requests import getfrom zipfile import ZipFile
  • 标题
run = p.add_run(i)if is_digit == "num_or_let":    run.font.name = data["num_font"]["font_name"]else:    run.font.name = data["title_font"]["font_name"]    run._element.rPr.rFonts.set(qn('w:eastAsia'), data["title_font"]["font_name"])run.font.size = Pt(checkFontSize(data["title_font"]["font_size"]))
  • 一级标题
index1_list = ["一、""二、""三、""四、""五、""六、""七、""八、""九、""十、""十一、""十二、""十三、""十四、""十五、""十六、""十七、""十八、""十九、""二十、"]for i in index1_list:    if i in p.text[:3]:        if '。' in p.text:            p.text = p.text.replace('。''')        if '?' in p.text:            p.text = p.text.replace('?''')        if ':' in p.text:            p.text = p.text.replace(':''')        if ';' in p.text:            p.text = p.text.replace(';''')        return "level1"     else:        continue
  • 二级标题
index2_list = ["(一)""(二)""(三)""(四)""(五)""(六)""(七)""(八)""(九)""(十)""(十一)""(十二)""(十三)""(十四)""(十五)""(十六)""(十七)""(十八)""(十九)""(二十)"]for i in index2_list:    if i in p.text[:4]:        if '。' in p.text:            p.text = p.text.replace('。''')        if '?' in p.text:            p.text = p.text.replace('?''')        if ':' in p.text:            p.text = p.text.replace(':''')        if ';' in p.text:            p.text = p.text.replace(';''')        return "level2"    else:        continue
  • 替换文字(符号)
def replace(p):    """ 替换函数 """    # 替换符号    if '(' in p.text:        p.text = p.text.replace('(''(')    if ')' in p.text:        p.text = p.text.replace(')'')')    if ',' in p.text:        p.text = p.text.replace(','',')    if ':' in p.text:        p.text = p.text.replace(':'':')    if ';' in p.text:        p.text = p.text.replace(';'';')    if '?' in p.text:        p.text = p.text.replace('?''?')    if '》、' in p.text:        p.text = p.text.replace('》、''》')    if '.' in p.text:  # U+ff0e        p.text = p.text.replace('.''.')    if ' ' in p.text:  # 空格        p.text = p.text.replace(' ''')    if ' ' in p.text:  # U+3000        p.text = p.text.replace(' ''')    if ' ' in p.text:  # U+2003        p.text = p.text.replace(' ''')    return p
  • 判断是否是数字(字母)

def isNumberOrLetter(char):    """ 判断是否为数字或字母 """    number_and_letter_strs = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'    if char in number_and_letter_strs:        return "num_or_let"    else:        return False
  • 段落函数

def paragraphFun(is_title, p, is_level1="", is_level2="", is_level3=""):    """ 段落函数 """    def checkLineSpacing(data_font):        if data_font["font_ls_lbl_txt"] == "倍":            font_ls_vlu = float(data_font["font_ls_vlu"])            p.paragraph_format.element.pPr.spacing.set(qn("w:line"), f'{int(font_ls_vlu * 240)}')            p.paragraph_format.element.pPr.spacing.set(qn("w:lineRule"), 'auto')        else:            p.paragraph_format.line_spacing = Pt(float(data_font["font_ls_vlu"]))  # 行距    global data    if p.paragraph_format.element.pPr is None:        p.paragraph_format.element.append(parse_xml(r'<w:pPr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>'))    # 判断 ind 是否存在,方便后边设置首行缩进    if p.paragraph_format.element.pPr.ind is None:        p.paragraph_format.element.pPr.append(parse_xml(r'<w:ind xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>'))    # 判断 spacing 是否存在,方便后边设置行距    if p.paragraph_format.element.pPr.spacing is None:        p.paragraph_format.element.pPr.append(parse_xml(r'<w:spacing xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>'))    pgp_almt = data["main"]["pgp_almt"]    if pgp_almt == "居中":        p.alignment = WD_ALIGN_PARAGRAPH.CENTER    elif pgp_almt == "左对齐":        p.alignment = WD_ALIGN_PARAGRAPH.LEFT    elif pgp_almt == "右对齐":        p.alignment = WD_ALIGN_PARAGRAPH.RIGHT    else:        p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY    if is_title == "title":        p.alignment = WD_ALIGN_PARAGRAPH.CENTER        checkLineSpacing(data["title_font"])    elif is_title == "odd_footer":        p.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT        p.paragraph_format.right_indent = Pt(14)        p.paragraph_format.line_spacing = Pt(28)    elif is_title == "even_footer":        p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT        p.paragraph_format.left_indent = Pt(14)        p.paragraph_format.line_spacing = Pt(28)    else:        checkLineSpacing(data["mb_font"])        if is_level1 == "level1":            checkLineSpacing(data["1title_font"])        ……
  • 图片输出
def picFix(docx, file, output_path):    """ 图片处理 """    img_path = output_path + "\image"    file_name = path.splitext(file)[0]    parts = docx.part.related_parts    parts_values = parts.values()    parts_keys = parts.keys()    list_val = list(parts_values)    list_key = list(parts_keys)    parts_length = len(parts_values)    if parts_length > 5:        # print(type(list(parts_values)[-1]))        k = 0        for i in range(parts_length):            # print(type(list_val[i]))            if 'image' in str(type(list_val[i])):                if not path.isdir(img_path):                    makedirs(img_path)                # print('找到图片数据')                k += 1                try:                    img_data = parts[list_key[i]].image.blob                    img_type = parts[list_key[i]].image.ext                    full_path = f'{img_path}\{file_name}_image{k}.{img_type}'                    writeHistory(f"··>提示<·· 正在输出:{full_path}")                    with open(full_path, 'wb'as f:                        f.write(img_data)                except:                    writeHistory(f"··>错误<·· 图片{k}输出失败!")        if k == 0:            writeHistory(f"··>提示<·· 未找到图片!")
  • 页边距
def margin(docx):    """ 设置页边距 """    global data    for s in docx.sections:        s.top_margin = Cm(float(data["margin"]["t_value"]))        s.bottom_margin = Cm(float(data["margin"]["b_value"]))        s.left_margin = Cm(float(data["margin"]["l_value"]))        s.right_margin = Cm(float(data["margin"]["r_value"]))
  • 设置奇偶数页不同
# 奇偶页不同docx.settings.odd_and_even_pages_header_footer = True
  • 添加页码
def footer(docx):    """ 设置页脚,添加页码 """    # print(len(docx.sections))    def AddFooterNumber(p):        t1 = p.add_run("— ")        font = t1.font        font.name = PAGENUMBERFONT        font.size = Pt(PAGENUMBERFONTSIZE)  # 14号字体        t1._element.rPr.rFonts.set(qn("w:eastAsia"), PAGENUMBERFONT)        run1 = p.add_run('')        fldChar1 = OxmlElement('w:fldChar')  # creates a new element        fldChar1.set(qn('w:fldCharType'), 'begin')  # sets attribute on element        run1._element.append(fldChar1)        run2 = p.add_run('')        instrText = OxmlElement('w:instrText')        instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element        instrText.text = 'PAGE'        font = run2.font        font.name = PAGENUMBERFONT        font.size = Pt(PAGENUMBERFONTSIZE)  # 14号字体        run2._element.rPr.rFonts.set(qn("w:eastAsia"), PAGENUMBERFONT)        run2._element.append(instrText)        run3 = p.add_run('')        fldChar2 = OxmlElement('w:fldChar')        fldChar2.set(qn('w:fldCharType'), 'end')        run3._element.append(fldChar2)        t2 = p.add_run(" —")        font = t2.font        font.name = PAGENUMBERFONT        font.size = Pt(PAGENUMBERFONTSIZE)  # 14号字体        t2._element.rPr.rFonts.set(qn("w:eastAsia"), PAGENUMBERFONT)    for s in docx.sections:        # print(s.footer)        footer = s.footer  # 获取第一个节的页脚        footer.is_linked_to_previous = True# 编号续前一节        paragraph = footer.paragraphs[0]  # 获取页脚的第一个段落        paragraphFun("odd_footer", paragraph)        AddFooterNumber(paragraph)        even_footer = s.even_page_footer  # 获取第一个节的页脚        even_footer.is_linked_to_previous = True# 编号续前一节        paragraph = even_footer.paragraphs[0]  # 获取页脚的第一个段落        paragraphFun("even_footer", paragraph)        AddFooterNumber(paragraph)
  • 输入路径

def inputPath():    """ 输入路径 """    input_path = type_radio_value.get()    if input_path == "file_path":        file_path = filedialog.askopenfile(title="请选择文件", filetypes=[("docx文件""*.docx")])        if file_path != None:            path_label["text"] = file_path.name    elif input_path == "dir_path":        dir_path = filedialog.askdirectory(title="请选择文件夹")        if dir_path != "":            path_label["text"] = dir_path
  • 入口函数
def main():    """ 主函数 """    try:        global data        input_path = path_entry.get().replace("/""\\")        if input_path == "":            messagebox.showinfo("提示""请选择文件或文件夹路径!")        else:            file_type = type_radio_value.get()            if file_type == "file_path":                if not path.isfile(input_path):                    messagebox.showerror("错误""文件路径错误!")                    return            elif file_type == "dir_path":                if not path.isdir(input_path):                    messagebox.showerror("错误""文件夹路径错误!")                    return            data = getUserInput()            # print(checkSpinboxValue(data["indent"]), checkSpinboxValue(data["spacing"]))            if not all(checkSpinboxValue(data["indent"])) or not all(checkSpinboxValue(data["spacing"])):                return            output_path = data["main"]["output_path"]            time_ipt = data["main"]["time_ipt"]            page_ipt = data["main"]["page_ipt"]            img_ipt = data["main"]["img_ipt"]            merge_button.config(state="disabled", cursor="wait", text="正在处理")            reset_button.config(state="disabled")            merge_button.update_idletasks()            reset_button.update_idletasks()            if file_type == "dir_path":                have_docx = 0                done_list = []                for file in listdir(input_path):                    if '~' in file:                        continue                    elif file.endswith('.docx'):                        if not path.isdir(output_path):                            makedirs(output_path)                        have_docx += 1                        file_path = path.join(input_path, file)                        save_time, is_done = fixWord(file_path, file, output_path, time_ipt, page_ipt, img_ipt)                        if is_done:                            done_list.append(file_path)                if have_docx == 0:                    print("··>错误<·· 没有找到.docx文件")                    messagebox.showinfo("提示""没有找到.docx文件!")                else:                    if len(done_list) == have_docx:                        messagebox.showinfo("提示""全部处理完成!\n输出路径:" + output_path)                    else:                        messagebox.showinfo("提示", f"处理完成!\n共 {have_docx} 个文件,成功 {len(done_list)} 个,失败 {have_docx - len(done_list)} 个\n输出路径:" + output_path)            elif file_type == "file_path":                # 文件名                file = input_path.split("\\")[-1]                # 输出路径                dir_path = input_path.split("\\")                dir_path.pop()                result = '\\'.join(str(x) for x in dir_path)                output_path = result + "\output"                if not path.isdir(output_path):                    makedirs(output_path)                save_time, is_done = fixWord(input_path, file, output_path, time_ipt, page_ipt, img_ipt)                if is_done:                    messagebox.showinfo("提示""处理完成!\n输出路径:" + output_path + "\\" + file.split(".")[0] + save_time + ".docx")            done()    except Exception as e:        tb_next = e.__traceback__        while tb_next:            error_log = f"Function: {tb_next.tb_frame.f_code.co_name},Line: {tb_next.tb_lineno}"            writeLog(error_log)            tb_next_ = tb_next            tb_next = tb_next.tb_next        writeLog(f"Info: {e}")        messagebox.showerror("错误", f"程序出错!请截图并联系作者!\nFilename:{tb_next_.tb_frame.f_code.co_filename},Function:{tb_next_.tb_frame.f_code.co_name},Line:{tb_next_.tb_lineno},Info:{e}")        writeHistory(f"程序出错!请截图并联系作者!Filename:{tb_next_.tb_frame.f_code.co_filename},Function:{tb_next_.tb_frame.f_code.co_name},Line:{tb_next_.tb_lineno},Info:{e}")    finally:        done()
  • 功能整合
def fixDocx(docx):    """ 主要格式 """    lvl = 0    for p in docx.paragraphs:        if p.text == '':            continue        else:            lvl += 1            p = replace(p)            if lvl == 1:                paragraphFun("title", p)                for run_title in p.runs:                    # print(run_title.text)                    run_title._element.getparent().remove(run_title._element)                    for i in run_title.text:                        num_or_let = isNumberOrLetter(i)                        text("title", num_or_let, p, i)            else:                is_level1 = isLevel1(p)                is_level2 = isLevel2(p)                is_level3 = isLevel3(p)                paragraphFun("text", p, is_level1, is_level2, is_level3)                for run_content in p.runs:                    # print(run_content.text)                    run_content._element.getparent().remove(run_content._element)                    for i in run_content.text:  # 遍历字符串                        num_or_let = isNumberOrLetter(i)                        text("notitle", num_or_let, p, i, is_level1, is_level2, is_level3)
保存为 fix_word.py 文件。
四、py文件打包成可执行文件(.exe文件)
pyinstaller -D -w fix_word.py -n fixWord_v5.0.0 -i icon.ico
pyinstaller详情:https://blog.csdn.net/weixin_43804047/article/details/119704965

→访问主页1(推荐):https://gitee.com/cxmStudio/fixWord
→访问主页2:https://github.com/Sam-CXM/fixWord

## 项目简介

fixWord是一个基于python开发的Word文档修复工具,能够自动修复Word文档中的常见错误,如拼写错误、格式错误等。

## 开发环境

  • Python 3.10.7
  • python-docx 1.2.0
  • pyinstaller 6.14.2
  • pillow 11.3.0

## 项目特点

  • 支持自定义设置格式。
  • 支持多种常见错误修复,如拼写错误、格式错误等。
  • 支持单文件和批量修复多个Word文档。
  • 支持选择输出结果格式。
  • 支持添加页码。

## 运行环境

系统
内存 磁盘

Windows10及以上版本

至少2GB

至少75MB

## 使用说明

1.下载地址1(推荐):https://gitee.com/cxmStudio/fixWord/releases/download/v5.0.0/fixWord_v5.0.0.zip

下载地址2:https://github.com/Sam-CXM/fixWord/releases/download/v5.0.0/fixWord_v5.0.0.zip

2.将安装包解压到本地

3.运行fixWord_v5.0.0.exe`文件,点击【文件选项或【文件夹】选项,或输入含有文档的路径

4. 点击【开始处理按钮等待处理完成即可。

5. 处理完成后,会提示输出路径信息。

## 更新日志:

– 维护日期:2026.3.28   v5.0.0
    – 【新增】加入在线更新功能;     
    – 【新增】合并fix_word功能(自定义选择文件或文件夹);     
    – 【新增】批量处理时成功文件个数提示;     
    – 【新增】日志功能,更好地排除全局错误问题;     
    – 【修复】Spinbox自定义输入时的逻辑判断;     
    – 【修复】设置行距磅值和倍的数据关系逻辑;     
    – 【修复】下拉框输入其他内容会报错的问题;     
    – 【修复】解构fixWord函数返回值错误的问题;     
    – 【修复】导入配置行距下拉框禁用的逻辑;     
    – 【修复】页边距调整步长不规律的问题;     
    – 【修复】标题的磅值设置后不起作用的问题;         
    – 【优化】首段注释中大序号不正确的问题;     
    – 【优化】选择【固定值】项原有数据覆盖的问题;     
    – 【优化】段落函数;     
    – 【优化】配置文件内容;     
    – 【优化】提示框信息,提示更准确;     
    – 【优化】视图尺寸;     
    – 【优化】增大了操作日志的字体,处理结果更清晰;     
    – 【优化】判断各个标题的方法;     
    – 【优化】其他问题;     
    – 【删除】右键菜单的横线和退出命令。
– 维护日期:2026.2.28
    – 【新增】右键输出日志可直接打开文件或文件夹;
    – 【新增】复制路径功能;
    – 【新增】批量处理时成功文件个数提示;
    – 【修复】识别各级标题的逻辑;
    – 【修复】保存出错的问题提示;
    – 【优化】处理文档过程中的按钮状态;
    – 【优化】实时更新输出日志。
    – 【优化】正文处理函数。

 维护日期:2025.6.17  全新4.0版本

 新增】支持用户手动输入路径,输入类型多样化;

新增】底部版本信息;

新增】全角空格替换;

新增】左侧缩进为0(不是首行缩进);

新增】段前段后为0;

新增】取消孤行控制;

优化】界面排版优化,视觉效果更佳;

优化】去掉控制台显示;

优化】本地项目可直接运行;

修复】两位数字后为顿号(、)时,会丢失相邻数之前的数字;

修复】其他问题。

– 维护日期2025.5.6

    – 新增字体常量,便于统一;

    – 新增两个版本:学校留存;上交上报;

    – 新增当前格式显示;

 优化】其他内容;

    – 修复】弹窗的路径不准确的情况。

维护日期:2024.8.21

 【优化】解决了首行缩进 2 字符的问题;

 【优化】设置基础信息常量。

 维护日期:2024.3.12

 【修复】解决了批量处理时选项需要重复输入的问题。

 维护日期:2024.1.22

 【修复】解决了含有图片的文档处理后图片被删除的问题。

 维护日期:2024.1.21

 【新增】可选项判断;

 【新增】处理完成后倒计时自动关闭;

 【优化】图片输出逻辑。

## 注意事项

 本程序仅处理 `.docx` 类型的文件;

 本程序暂不支持处理含有表格内容的文件;

 含有图片的文档图片导出后可能会被压缩;

 本程序无法处理图片格式,如果图片独立成段,本程序所用API识别到图片会被默认是空段落。为了防止图片删除,只能放弃处理空段落及图片格式;

为了处理效果,处理前请将全文`清除全部格式`,操作步骤:`全选`->`开始`->`样式`->`清除格式`;将文档中所有图片环绕文字改为`嵌入型`,操作步骤:`选中图片`->`图片格式`->`排列`->`环绕文字`->`嵌入型`;

 本程序已开源,可免费使用。

项目截图

持续维护中…