python 使用 spire.doc 和 docx 对word文档进行错别字批注和修正

一、所用库

from spire.doc import *
import docx

二、按照格式获取错别字内容

需要自行获取错别字内容,可以使用大模型按照格式输出,例:

    typos_info = [
        {"文本错误位置": "着是一格测试百度错屋文字的说明文档", "文本错误内容": "着是一格", "文本正确内容": "这是一份", "错误原因": "'着'应为'这','一格'应为'一份',此处为形似字及语义理解错误"},
        {"文本错误位置": "着是一格测试百度错屋文字的说明文档", "文本错误内容": "错屋", "文本正确内容": "错误", "错误原因": "'错屋'应为'错误',此处为形似字错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "蠢天", "文本正确内容": "春天", "错误原因": "'蠢天'应为'春天',此处为形似字及语义理解错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "煖了", "文本正确内容": "暖了", "错误原因": "'煖'应为'暖',此处为形似字错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "事件", "文本正确内容": "一切", "错误原因": "'事件'应为'一切',此处为形似字及语义理解错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "梦雅", "文本正确内容": "萌芽", "错误原因": "'梦雅'应为'萌芽',此处为形似字及语义理解错误"},
        {"文本错误位置": "具体的时间详情", "文本错误内容": "时间", "文本正确内容": "事件", "错误原因": "'时间'应为'事件',此处为形似字及语义理解错误"}
        ]

下一步需要对这个内容进行再一步处理,进行合并。

为什么不让大模型直接输出最后需要的格式,因为代码的限制,最后需要的格式,有些许复杂,大模型理解不了,但如果你提示词用的好,当我没说,你可以忽略下面的第三步,直接让大模型输出最终需要的格式,例:

merged_typos_info = [
{'文本错误位置': '着是一格测试百度错屋文字的说明文档', '文本错误内容': ['着是一格', '错屋'], '文本正确内容': ['这是一份', '错误'], '错误原因': ["'着'应为'这','一格'应为'一份',此处为形似字及语义理解错误", "'错屋'应为'错误',此处为形似字错误"]}, 
{'文本错误位置': '蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅', '文本错误内容': ['蠢天', '煖了', '事件', '梦雅'], '文本正确内容': ['春天', '暖了', '一切', '萌芽'], '错误原因': ["'蠢天'应为'春天',此处为形似字及语义理解错误", "'煖'应为'暖',此处为形似字错误", "'事件'应为'一切',此处为形似字及语义理解错误", "'梦雅'应为'萌芽',此处为形似字及语义理解错误"]}, 
{'文本错误位置': '具体的时间详情', '文本错误内容': ['时间'], '文本正确内容': ['事件'], '错误原因': ["'时间'应为'事件',此处为形似字及语义理解错误"]}]

三、对上面的错别字内容合并成需要的格式

def mark_typos(doc_path,typos_info,save_path):
    # 创建一个新的列表用于存储去重合并后的结果
    merged_typos_info = []

    # 使用字典来记录每个"文本错误位置"及其对应的"文本错误内容"、"文本正确内容"和"错误原因"
    location_to_data = {}

    # 遍历typos_info列表
    for typo in typos_info:
        location = typo["文本错误位置"]
        error_content = typo["文本错误内容"]
        correct_content = typo["文本正确内容"]
        error_reason = typo["错误原因"]

        # 如果该位置已经存在于字典中,则添加错误内容、正确内容和错误原因到列表
        if location in location_to_data:
            location_to_data[location]["文本错误内容"].append(error_content)
            location_to_data[location]["文本正确内容"].append(correct_content)
            location_to_data[location]["错误原因"].append(error_reason)
        else:
            location_to_data[location] = {
                "文本错误位置": location,
                "文本错误内容": [error_content],
                "文本正确内容": [correct_content],
                "错误原因": [error_reason]
            }

    # 将字典转换为列表
    for data in location_to_data.values():
        merged_typos_info.append({
            "文本错误位置": data["文本错误位置"],
            "文本错误内容": data["文本错误内容"],
            "文本正确内容": data["文本正确内容"],
            "错误原因": data["错误原因"]
        })


四、使用spire.doc加载文档,将错别字内容替换成占位符

因为后面需要把错别字位置替换,但是spire.doc 不能识别\n,所以要以\n进行分割,然后对每个分割出来的字符串判断有无错别字,有就替换成占位符

    # 创建一个 Document 类的对象并加载一个 Word 文档
    doc = Document()
    doc.LoadFromFile(doc_path)
    for typos in merged_typos_info:

        # 因为\n匹配不了,所以进行分割,找到错误内容在第几行
        if '\n' in typos['文本错误位置']:
            a = typos['文本错误位置'].split('\n')

            for i in a:
                short_str = i
                for j in range(len(typos['文本错误内容'])):
                    if typos['文本错误内容'][j] in i:
                        short_str = short_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

                doc.Replace(i, short_str, False, True)

        else:
            sign_str = typos['文本错误位置']
            for j in range(len(typos['文本错误内容'])):
                sign_str = sign_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

            doc.Replace(typos['文本错误位置'], sign_str, False, True)

五、批注和修改

批注

对所有的占位符进行批注,然后保存

        for i in range(len(typos['文本错误内容'])):
            text = doc.FindString(f'<错别字占位符{i}>', True, True)

            # 创建一个评论并设置评论的内容和作者
            comment = Comment(doc)
            comment.Body.AddParagraph().Text = typos['错误原因'][i]
            comment.Format.Author = "法伴"


            # 将找到的文本作为文本范围,并获取其所属的段落
            text_range = text.GetAsOneRange()
            paragraph = text_range.OwnerParagraph

            # 将评论添加到段落中
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range) + 1, comment)

            # 创建评论起始标记和结束标记,并将它们设置为创建的评论的起始标记和结束标记
            commentStart = CommentMark(doc, CommentMarkType.CommentStart)
            commentEnd = CommentMark(doc, CommentMarkType.CommentEnd)
            commentStart.CommentId = comment.Format.CommentId
            commentEnd.CommentId = comment.Format.CommentId

            # 在找到的文本之前和之后插入创建的评论起始和结束标记
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range), commentStart)
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range) + 1, commentEnd)

            doc.Replace(f'<错别字占位符{i}>', typos['文本错误内容'][i], False, True)


    # 保存文档
    doc.SaveToFile(save_path)
    doc.Close()
    delete_watermark(save_path)
    print('批注版已完成')

修正

还是二、三部的代码,加上这个就是修正错别字的代码

        for i in range(len(typos['文本错误内容'])):
            doc.Replace(f"<错别字占位符{i}>", typos['文本正确内容'][i], False, True)

    # 保存文档
    doc.SaveToFile(save_path)
    doc.Close()
    delete_watermark(save_path)
    print('正确版已完成')

六、解释

第一个函数是使用spire.doc读取文档,但是本程序并没有使用,可以删除

第二个函数是用来去掉spire.doc水印的(使用spire.doc保存会在第一行留下红色的一行字),使用docx读取文档,删除水印,在保存

# 使用spire.doc读取文档
def read_doc(doc_path):
    # 创建一个 Document 类的对象并加载一个 Word 文档
    doc = Document()
    doc.LoadFromFile(doc_path)
    text = doc.GetText()
    text = text.replace('Evaluation Warning: The document was created with Spire.Doc for Python.\r\n','')
    doc.Close()
    return text


# 使用docx读取文档在保存,目的是去掉spire.doc的水印
def delete_watermark(path):
    pydoc = docx.Document(path)
    for para in pydoc.paragraphs:
        if para.text == 'Evaluation Warning: The document was created with Spire.Doc for Python.':
            p = para._element
            p.getparent().remove(p)

    pydoc.save(path)


七、完整代码

from spire.doc import *
import docx



def read_doc(doc_path):
    # 创建一个 Document 类的对象并加载一个 Word 文档
    doc = Document()
    doc.LoadFromFile(doc_path)
    text = doc.GetText()
    text = text.replace('Evaluation Warning: The document was created with Spire.Doc for Python.\r\n','')
    return text



def delete_watermark(path):
    pydoc = docx.Document(path)
    for para in pydoc.paragraphs:
        if para.text == 'Evaluation Warning: The document was created with Spire.Doc for Python.':
            p = para._element
            p.getparent().remove(p)

    pydoc.save(path)


def mark_typos(doc_path,typos_info,save_path):
    # 创建一个新的列表用于存储去重合并后的结果
    merged_typos_info = []

    # 使用字典来记录每个"文本错误位置"及其对应的"文本错误内容"、"文本正确内容"和"错误原因"
    location_to_data = {}

    # 遍历typos_info列表
    for typo in typos_info:
        location = typo["文本错误位置"]
        error_content = typo["文本错误内容"]
        correct_content = typo["文本正确内容"]
        error_reason = typo["错误原因"]

        # 如果该位置已经存在于字典中,则添加错误内容、正确内容和错误原因到列表
        if location in location_to_data:
            location_to_data[location]["文本错误内容"].append(error_content)
            location_to_data[location]["文本正确内容"].append(correct_content)
            location_to_data[location]["错误原因"].append(error_reason)
        else:
            location_to_data[location] = {
                "文本错误位置": location,
                "文本错误内容": [error_content],
                "文本正确内容": [correct_content],
                "错误原因": [error_reason]
            }

    # 将字典转换为列表
    for data in location_to_data.values():
        merged_typos_info.append({
            "文本错误位置": data["文本错误位置"],
            "文本错误内容": data["文本错误内容"],
            "文本正确内容": data["文本正确内容"],
            "错误原因": data["错误原因"]
        })


    # 创建一个 Document 类的对象并加载一个 Word 文档
    doc = Document()
    doc.LoadFromFile(doc_path)
    for typos in merged_typos_info:

        # 因为\n匹配不了,所以进行分割,找到错误内容在第几行
        if '\n' in typos['文本错误位置']:
            a = typos['文本错误位置'].split('\n')


            for i in a:
                short_str = i
                for j in range(len(typos['文本错误内容'])):
                    if typos['文本错误内容'][j] in i:
                        short_str = short_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

                doc.Replace(i, short_str, False, True)

        else:
            sign_str = typos['文本错误位置']
            for j in range(len(typos['文本错误内容'])):
                sign_str = sign_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

            doc.Replace(typos['文本错误位置'], sign_str, False, True)


        for i in range(len(typos['文本错误内容'])):
            text = doc.FindString(f'<错别字占位符{i}>', True, True)

            # 创建一个评论并设置评论的内容和作者
            comment = Comment(doc)
            comment.Body.AddParagraph().Text = typos['错误原因'][i]
            comment.Format.Author = "法伴"


            # 将找到的文本作为文本范围,并获取其所属的段落
            text_range = text.GetAsOneRange()
            paragraph = text_range.OwnerParagraph

            # 将评论添加到段落中
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range) + 1, comment)

            # 创建评论起始标记和结束标记,并将它们设置为创建的评论的起始标记和结束标记
            commentStart = CommentMark(doc, CommentMarkType.CommentStart)
            commentEnd = CommentMark(doc, CommentMarkType.CommentEnd)
            commentStart.CommentId = comment.Format.CommentId
            commentEnd.CommentId = comment.Format.CommentId

            # 在找到的文本之前和之后插入创建的评论起始和结束标记
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range), commentStart)
            paragraph.ChildObjects.Insert(paragraph.ChildObjects.IndexOf(text_range) + 1, commentEnd)

            doc.Replace(f'<错别字占位符{i}>', typos['文本错误内容'][i], False, True)


    # 保存文档
    doc.SaveToFile(save_path)
    doc.Close()
    delete_watermark(save_path)
    print('批注版已完成')

def replace_typos(doc_path,typos_info,save_path):
    # 创建一个新的列表用于存储去重合并后的结果
    merged_typos_info = []

    # 使用字典来记录每个"文本错误位置"及其对应的"文本错误内容"、"文本正确内容"和"错误原因"
    location_to_data = {}

    # 遍历typos_info列表
    for typo in typos_info:
        location = typo["文本错误位置"]
        error_content = typo["文本错误内容"]
        correct_content = typo["文本正确内容"]
        error_reason = typo["错误原因"]

        # 如果该位置已经存在于字典中,则添加错误内容、正确内容和错误原因到列表
        if location in location_to_data:
            location_to_data[location]["文本错误内容"].append(error_content)
            location_to_data[location]["文本正确内容"].append(correct_content)
            location_to_data[location]["错误原因"].append(error_reason)
        else:
            location_to_data[location] = {
                "文本错误位置": location,
                "文本错误内容": [error_content],
                "文本正确内容": [correct_content],
                "错误原因": [error_reason]
            }

    # 将字典转换为列表
    for data in location_to_data.values():
        merged_typos_info.append({
            "文本错误位置": data["文本错误位置"],
            "文本错误内容": data["文本错误内容"],
            "文本正确内容": data["文本正确内容"],
            "错误原因": data["错误原因"]
        })


    # 创建一个 Document 类的对象并加载一个 Word 文档
    doc = Document()
    doc.LoadFromFile(doc_path)
    for typos in merged_typos_info:

        # 因为\n匹配不了,所以进行分割,找到错误内容在第几行
        if '\n' in typos['文本错误位置']:
            a = typos['文本错误位置'].split('\n')


            for i in a:
                short_str = i
                for j in range(len(typos['文本错误内容'])):
                    if typos['文本错误内容'][j] in i:
                        short_str = short_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

                doc.Replace(i, short_str, False, True)

        else:
            sign_str = typos['文本错误位置']
            for j in range(len(typos['文本错误内容'])):
                sign_str = sign_str.replace(typos['文本错误内容'][j], f'<错别字占位符{j}>')

            doc.Replace(typos['文本错误位置'], sign_str, False, True)


        for i in range(len(typos['文本错误内容'])):
            doc.Replace(f"<错别字占位符{i}>", typos['文本正确内容'][i], False, True)

    # 保存文档
    doc.SaveToFile(save_path)
    doc.Close()
    delete_watermark(save_path)
    print('正确版已完成')


if __name__ == "__main__":
    # typos_info = [
    #     {"文本错误位置": "树木从声", "文本错误内容": "从声", "文本正确内容": "丛生", "错误原因": "树木从声 的从声应该 丛生"},
    #     {"文本错误位置": "百草丰冒。\n秋封萧瑟,红波涌起。", "文本错误内容": "冒", "文本正确内容": "茂", "错误原因": "百草丰冒 的冒应该是茂盛的 茂"},
    #     {"文本错误位置": "百草丰冒。\n秋封萧瑟,红波涌起。", "文本错误内容": "封", "文本正确内容": "风", "错误原因": "秋封萧瑟 的封应该是刮风的 风"},
    #     {"文本错误位置": "百草丰冒。\n秋封萧瑟,红波涌起。", "文本错误内容": "红", "文本正确内容": "洪", "错误原因": "红波涌起 的红应该是洪水的 洪"},
    #     {"文本错误位置": "割以咏志", "文本错误内容": "割", "文本正确内容": "歌", "错误原因": "割以咏志 的割应该是歌唱的 歌"},
    # ]



    typos_info = [
        {"文本错误位置": "着是一格测试百度错屋文字的说明文档", "文本错误内容": "着是一格", "文本正确内容": "这是一份", "错误原因": "'着'应为'这','一格'应为'一份',此处为形似字及语义理解错误"},
        {"文本错误位置": "着是一格测试百度错屋文字的说明文档", "文本错误内容": "错屋", "文本正确内容": "错误", "错误原因": "'错屋'应为'错误',此处为形似字错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "蠢天", "文本正确内容": "春天", "错误原因": "'蠢天'应为'春天',此处为形似字及语义理解错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "煖了", "文本正确内容": "暖了", "错误原因": "'煖'应为'暖',此处为形似字错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "事件", "文本正确内容": "一切", "错误原因": "'事件'应为'一切',此处为形似字及语义理解错误"},
        {"文本错误位置": "蠢天来了,输液绿了,鸟儿在欢唱,天气变煖了,事件万物在梦雅", "文本错误内容": "梦雅", "文本正确内容": "萌芽", "错误原因": "'梦雅'应为'萌芽',此处为形似字及语义理解错误"},
        {"文本错误位置": "具体的时间详情", "文本错误内容": "时间", "文本正确内容": "事件", "错误原因": "'时间'应为'事件',此处为形似字及语义理解错误"}
        ]

    # 读取用户上传的docx
    doc_path = 'typos_doc/百度测试错别字.docx'

    # 导出的批注版和正确版文件
    mark_doc_path = 'result/批注.docx'
    right_doc_path = 'result/正确.docx'

    # 进行批注,导出批注版
    mark_typos(doc_path, typos_info, mark_doc_path)

    # 替换错别字,导出正确版
    replace_typos(doc_path, typos_info, right_doc_path)