python-docx格式化输出文档的简单尝试

什么是python-docx?

python-docx 是用于创建和更新Microsoft Word(.docx)文件的Python库。

python-docx简介

使用docx.Document(docx=None)构造函数可以创建一个Document 对象;关于Document和相关对象的用法如下:
add_heading(text=‘’, level=1)
返回新添加到文档末尾的标题段落。
add_paragraph(text=‘’, style=None)
返回新添加到文档末尾的段落,填充 text 有段落风格 风格 . text 可以包含制表符 (\t )字符,这些字符将转换为选项卡的适当XML格式。 text 也可以包括换行符 (\n )或回车 (\r )字符,每个字符都转换为换行符。
property paragraphs
列表 Paragraph 与文档中的段落相对应的实例,按文档顺序排列。
更多详细用法可参阅帮助文档 python-docx 0.8.11文档

安装python-docx

python-docx 托管在PyPI上,所以安装相对简单。
python-docx 可以使用 pip 安装,如果你的网络链接正常的话:
pip install python-docx
python-docx 也可以使用 easy_install ,尽管不鼓励这样做:
easy_install python-docx
如果既不 pip 也不 easy_install 它可以通过从PyPI下载发行版、解包tarball并运行来手动安装 setup.py :pypi&python-docx下载
tar xvzf python-docx-{version}.tar.gz
cd python-docx-{version}
python setup.py install
在这里插入图片描述

如果您使用最后一种方法,则需要自己安装这些依赖项。
依赖关系
Python2.6、2.7、3.3或更高版本
lxml>=2.3.2

软件环境

win10家庭中文版
python 3.9.0
python-docx 0.8.11

确定需求

由其他程序产生的输出,已经保存为.txt文件;需要按照给定的格式输出一个新的文档。

具体实现及思路

从无到有的生成新的文档听起来令人振奋,实际应用过程中却并不可取;因为不确定因素导致的结果可能与你的最初的目的大相径庭。一个更可靠的做法是事先确定一个标准,将后续也许会出现在计划外的部分以一个固定的模板加以约束。于是就有了以下想法:
在这里插入图片描述

一、读取模板文档
首先构造一个Document对象;

doc_test = Document(r'.\test.docx')

接下来添加paragraph样式的索引,通过样式及索引确定paragraph的内容;

        for i in doc_test.paragraphs:  # <docx.text.paragraph.Paragraph object at 0x000002533CF7AA00>
            a += 1
            self.par_list.append(i)  # 通过正则匹配返回 {paragraph索引:style}的列表
            style_m_1 = re.search('Normal', str(i.style))
            style_m_2 = re.search('Heading 1', str(i.style))
            if style_m_1:
                self.style_list.append({
    
    a - 1: style_m_1.group(0)})
            elif style_m_2:
                self.style_list.append({
    
    a - 1: style_m_2.group(0)})

最后返回两个列表:

        self.par_list = []  # test.docx文档paragraphs对象的paragraph类列表
        self.style_list = []  # paragraph类的style索引列表

二、格式化源文件

打开源文件,保存line的内容列表;

        with open('Prin.txt') as f_obj:
            line_list = f_obj.readlines()
            # print(line_list)
        for line in line_list:
            a += 1
            if line == '---------------\n':
                self.prn_list.append(a) 

列出 ‘---------------\n’ 在 Prin.txt内容列表 line_list 中的索引;

        hd_1 = int(self.prn_list[0])  # Prin.txt的第一个'---------------\n'索引
        hd_2 = int(self.prn_list[1])  # Prin.txt的第二个'---------------\n'索引
        hd_3 = int(self.prn_list[2])  # Prin.txt的第三个'---------------\n'索引
        for ii in range(hd_1 + 1, hd_2):
            self.prn_text1 += line_list[ii]  # 第一个'---------------\n'索引到第二个'---------------\n'索引内容添加至字符串
        for ii in range(hd_2 + 1, hd_3):
            self.prn_text2 += line_list[ii]  # 第二个'---------------\n'索引到第三个'---------------\n'索引内容添加至字符串
        for ii in range(hd_3 + 1, len(line_list)):
            self.prn_text3 += line_list[ii]  # 第三个'---------------\n'索引到最后的内容添加至字符串

最终将源文件按照分割符’---------------'分为三部分内容分别保存至self.prn_text1、self.prn_text2、self.prn_text3中。

三、文档对象内容更新

确定标题及文档内容更新方法;

        # 文档标题
        self.TiTle_txt = 'XXX' + TiTle_txt + '数据检查'
        # paragraph 方法 'add_run', 'alignment', 'clear', 'insert_paragraph_before'
        TiTle = self.par_list[4].add_run(text=self.TiTle_txt)
        TiTle.bold = True
        TiTle.font.size = Pt(18)

对于字体的设定方法如下,这里注意设置了paragraph 对象的style字体后,你对应的样式字体会被全部修改;例如标题字体与正文不一致但是同样使用正文的样式"Normal",不能直接使用此类方法修改字体,应创建新的样式标题使用新样式的字体;

        """
        中文字体的设置  paragraph对象
        self.list[4].style.font.name = '楷体'
        self.list[4].style._element.rPr.rFonts.set(qn('w:eastAsia'), u'楷体')
        self.list[4].style.font.bold = True
        self.list[4].style.font.size = Pt(18)
        西文字体的设置  run对象
        TiTle.bold = True
        TiTle.font.size = Pt(18)
        TiTle.font.name = '楷体'
        """

确定文档内容的输入位置;

        for i in self.style_list:
            for k, v in i.items():
                if v == 'Heading 1':  # 各分段标题的统一样式
                    self.Heading_list.append(k)

清理标题(‘Heading 1’)之间的内容;

        hd_1 = int(self.Heading_list[0])  # 标题一的paragraph索引
        hd_2 = int(self.Heading_list[1])  # 标题二的paragraph索引
        hd_3 = int(self.Heading_list[2])  # 标题三的paragraph索引
        hd_4 = int(self.Heading_list[3])  # 标题四的paragraph索引
        for ii in range(hd_2 + 1, hd_3):
            # print(ii)
            self.par_list[ii].clear()
        for ii in range(hd_3 + 1, hd_4):
            # print(ii)
            self.par_list[ii].clear()

在标题(‘Heading 1’)之间写入内容;

        self.par_list[hd_2 + 1].add_run(text=self.prn_text1)  # 标题二 之后的内容
        self.par_list[hd_2 + 1].add_run(text=self.prn_text2)  # 标题二 之后的内容
        self.par_list[hd_3 + 1].add_run(text=self.prn_text3)  # 标题三 之后的内容
        self.par_list[hd_4 + 1].add_run(text='')  # 标题四 之后的内容

四、保存输出新文档

消除上一步,清除内容后留下的空行;

        hd_2 = int(self.Heading_list[1])  # 标题二的paragraph索引
        hd_3 = int(self.Heading_list[2])  # 标题三的paragraph索引
        hd_4 = int(self.Heading_list[3])  # 标题四的paragraph索引
        for ii in range(hd_2, hd_3):  # 消除空行 ? 存在问题未处理 标题二、标题三、标题四之间多一个空行
            if len(self.par_list[ii].text) == 0:
                p = self.par_list[ii]._element
                p.getparent().remove(p)
                p._p = p._element = None
        for ii in range(hd_3, hd_4):
            if len(self.par_list[ii].text) == 0:
                p = self.par_list[ii]._element
                p.getparent().remove(p)
                p._p = p._element = None

输出新文档到指定路径;

        a = list(os.path.split(path))[0]
        self.doc.save(os.path.join(str(a), self.TiTle_txt + '报告.docx'))

完整代码

# encoding: utf-8
# design by bill_love_3
from docx import Document
from docx.shared import Pt
import re
import os

"""
需求:
1.读取test.docx模板文档内容及样式;
2.用模板的样式格式化Prin.txt文档
3.将格式化后的内容添加到test.docx文档中去
4.添加内容后输出新的文档
"""


class FormatDocument():
    def __init__(self):
        self.par_list = []  # test.docx文档paragraphs对象的paragraph类列表
        self.style_list = []  # paragraph类的style索引列表
        self.doc = ''          # test.docx文档对象
        self.Heading_list = []  # 标题所在位置的paragraph索引列表 整数
        self.prn_text1 = ''  # 源txt的内容部分一
        self.prn_text2 = ''  # 源txt的内容部分二
        self.prn_text3 = ''  # 源txt的内容部分三
        self.prn_list = []  # 源txt的分隔标识符索引位置列表 整数  “---------------”
        self.TiTle_txt = ''  # 输出文档的标题内容 样式固定

    def ReadTestDocx(self, a=0):  # 读取模板文档方法
        doc_test = Document(r'.\test.docx')  # <class 'docx.document.Document'>
        self.doc = doc_test
        # 添加paragraph样式的索引 通过样式及索引确定paragraph内容
        for i in doc_test.paragraphs:  # <docx.text.paragraph.Paragraph object at 0x000002533CF7AA00>
            a += 1
            self.par_list.append(i)  # 通过正则匹配返回 {paragraph索引:style}的列表
            style_m_1 = re.search('Normal', str(i.style))
            style_m_2 = re.search('Heading 1', str(i.style))
            if style_m_1:
                self.style_list.append({
    
    a - 1: style_m_1.group(0)})
            elif style_m_2:
                self.style_list.append({
    
    a - 1: style_m_2.group(0)})

        # print(self.style)

    def FormatPrin(self, a=-1):  # 格式化源文件方法
        with open('Prin.txt') as f_obj:
            line_list = f_obj.readlines()
            # print(line_list)
        for line in line_list:
            a += 1
            if line == '---------------\n':
                self.prn_list.append(a)  # 列出 '---------------\n' 在 Prin.txt内容列表 line_list 中的索引
        print(self.prn_list)
        hd_1 = int(self.prn_list[0])  # Prin.txt的第一个'---------------\n'索引
        hd_2 = int(self.prn_list[1])  # Prin.txt的第二个'---------------\n'索引
        hd_3 = int(self.prn_list[2])  # Prin.txt的第三个'---------------\n'索引
        for ii in range(hd_1 + 1, hd_2):
            self.prn_text1 += line_list[ii]  # 第一个'---------------\n'索引到第二个'---------------\n'索引内容添加至字符串
        for ii in range(hd_2 + 1, hd_3):
            self.prn_text2 += line_list[ii]  # 第二个'---------------\n'索引到第三个'---------------\n'索引内容添加至字符串
        for ii in range(hd_3 + 1, len(line_list)):
            self.prn_text3 += line_list[ii]  # 第三个'---------------\n'索引到最后的内容添加至字符串
        # print(self.prn_text1, self.prn_text2, self.prn_text3)

    def AddToTestDocx(self, TiTle_txt):  # 文档对象内容更新方法
        # 文档标题
        self.TiTle_txt = 'XXX' + TiTle_txt + '数据检查'
        # paragraph 方法 'add_run', 'alignment', 'clear', 'insert_paragraph_before'
        TiTle = self.par_list[4].add_run(text=self.TiTle_txt)
        TiTle.bold = True
        TiTle.font.size = Pt(18)
        # 文档内容 确定输入位置
        for i in self.style_list:
            for k, v in i.items():
                if v == 'Heading 1':  # 各分段标题的统一样式
                    self.Heading_list.append(k)
        # print(self.Heading_list)
        # 清理标题之间的内容
        hd_1 = int(self.Heading_list[0])  # 标题一的paragraph索引
        hd_2 = int(self.Heading_list[1])  # 标题二的paragraph索引
        hd_3 = int(self.Heading_list[2])  # 标题三的paragraph索引
        hd_4 = int(self.Heading_list[3])  # 标题四的paragraph索引
        for ii in range(hd_2 + 1, hd_3):
            # print(ii)
            self.par_list[ii].clear()
        for ii in range(hd_3 + 1, hd_4):
            # print(ii)
            self.par_list[ii].clear()
        # 在标题之间写入内容
        self.par_list[hd_2 + 1].add_run(text=self.prn_text1)  # 标题二 之后的内容
        self.par_list[hd_2 + 1].add_run(text=self.prn_text2)  # 标题二 之后的内容
        self.par_list[hd_3 + 1].add_run(text=self.prn_text3)  # 标题三 之后的内容
        self.par_list[hd_4 + 1].add_run(text='')  # 标题四 之后的内容

    def SaveNewDocx(self, path):  # 保存输出新文档方法
        hd_2 = int(self.Heading_list[1])  # 标题二的paragraph索引
        hd_3 = int(self.Heading_list[2])  # 标题三的paragraph索引
        hd_4 = int(self.Heading_list[3])  # 标题四的paragraph索引
        for ii in range(hd_2, hd_3):  # 消除空行 ? 存在问题未处理 标题二、标题三、标题四之间多一个空行
            if len(self.par_list[ii].text) == 0:
                p = self.par_list[ii]._element
                p.getparent().remove(p)
                p._p = p._element = None
        for ii in range(hd_3, hd_4):
            if len(self.par_list[ii].text) == 0:
                p = self.par_list[ii]._element
                p.getparent().remove(p)
                p._p = p._element = None
        a = list(os.path.split(path))[0]
        self.docs.save(os.path.join(str(a), self.TiTle_txt + '报告.docx'))


if __name__ == "__main__":
    path = 'C:\\drea\\python3\\Qu_Ins_04_04_2023\\数据库.gdb'
    myDocument = FormatDocument()
    myDocument.ReadTestDocx()
    myDocument.FormatPrin()
    myDocument.AddToTestDocx('xxxx')
    myDocument.SaveNewDocx(path)

总结及反思

目前只是完成了简单的格式化输出,对样式的定义及缩进等控制还没有更深入的涉及;接下来的想法是构造一个包含多种样式的定义及缩进等格式的程序,后续会陆续更新的。

猜你喜欢

转载自blog.csdn.net/bill_love_c/article/details/130023947