docx操控Word文件

PythonABC的教程基本完成,编辑刘大人建议每章结束后出几道思考题。这对我不是件容易的事儿,编程的题目最好既能串起知识点又能对解决实际问题有所帮助。年前跑去图书馆翻了几本python教材,希望一如既往奉行的“拿来主义”也能“拿来”好的思考题。大多数题目都太书生气,我若出题大概率也是这种风格,只有一本书上的题目还算接地气,揪出来了几道放进教程。

基础知识应付过去,python实例部分很吃力,实战经验太少,想不出实战的题目来。出到word这一张的题目时,正好在听许子东重读鲁迅和细读张爱玲,突发奇想何不出一道题目:在python-docx模块帮助下自动汇总作家的作品,生成图文并茂的word文档。想来应该不难,无非就是加段落加图片加表格,都有现成的方法函数。

题目出完后自己要做一做,看介绍的知识是不是足够用。结果做完后甚为不满,虽然可以完成既定任务,做出来的word文档却非常丑陋。对英文灵光的设置字体方法到中文字块这里完全不起作用,设置表格列宽的函数也不好用,添加的图片怎么不能居中?

好吧,还是先通过对鲁迅作品的处理来看我究竟是想让程序干什么吧(对于程序来说,一个作家的事儿整明白,十个作家还是一百个作家那都不叫事儿):

    先建一个叫做鲁迅的文件夹,然后选鲁迅的几篇小说和小说中的人物放进一个txt文本(列表文件list.txt),内容是:

    故乡 闰土

    狂人日记 狂人

    阿Q正传 阿Q

    祝福 祥林嫂

    孔乙己 孔乙己

    伤逝 涓生、子君

    药 夏瑜

    然后按照列表找来作品的摘抄和跟作品相关的图片,放进鲁迅的文件夹里。鲁迅的文件夹长这样:

    祝福.txt 长这样:

程序把这些原始素材组织起来自动编辑排版成如下word文档:

段落和表格里的中文字设置字体、图片居中和表格列宽的设定费了点周章。给出面向过程和面向对象两种实现方式,面向过程的代码如下: 

# ---------------- 自动汇总材料生成Word文档(面向对象)---------------

from docx import Document									# 操控word
from docx.enum.table import WD_TABLE_ALIGNMENT				# 表格居中
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT			# 标题居中
from docx.shared import Cm									# 图片尺寸
from docx.oxml.ns import qn									# 设置中文字体用
from pathlib import Path									# 路径设置


def getElem(writer):			# 从索引文件里获得作品、人物、文章内容、相关图片位置

    currentFolder = Path('.').joinpath(writer)		# 作家作品资料所在目录
    listFile = currentFolder.joinpath('list.txt')	# 作品资料索引

    novelCharacter = {}								# 作品-人物
    novelQuote = {}									# 作品-文章内容
    novelPic = {}									# 作品-图片

    fileObj = listFile.open(encoding='utf-8')		# 打开索引文件

    dataLine = fileObj.readline()					# 读出一行

    while dataLine:									# 读出内容为空,说明到了文章尾

        novel, character = dataLine.split()			# 从索引中获取作品和人物

        for f in currentFolder.glob(novel + '.*'):	# 从作品名搜作品内容和作品的相关图片
            if f.suffix == '.txt':					# 作品内容放在txt文档
                quote = f
                with quote.open(encoding='utf-8') as qObj:  # 打开文章内容文件,按段落放进列表
                    paragraphs = qObj.read().split('\n')
                    novelQuote.setdefault(novel, paragraphs)
            else:									# 图片文件
                pic = f
                novelPic.setdefault(novel, str(pic))  # 作品和相关图像的位置字串

        novelCharacter.setdefault(novel, character)	# 作品和人物对应起来

        dataLine = fileObj.readline()

    return novelCharacter, novelQuote, novelPic		# 返回三个构建word文档的字典变量


def setChineseFont(run, cFont):						# 设置中文字体
    font = run.font
    font.name = cFont
    r = run._element
    r.rPr.rFonts.set(qn('w:eastAsia'), cFont)


def createWord(author, nCharacter, nQuote, nPic):		# 生成word文档
    docObj = Document()

    # 整个word文档的标题
    docObj.add_heading(author+'小说及人物', level=0).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

    # 作品人物表格
    table = docObj.add_table(rows = len(nCharacter.keys())+1, cols = 2, style = 'Light Grid Accent 1')
    table.alignment = WD_TABLE_ALIGNMENT.CENTER		# 表格居中

    table.allow_autofit = False						# 允许人工调节
    for row in table.rows:							# 设置表格每一列大小
        row.cells[0].width = Cm(4)
        row.cells[1].width = Cm(3)

    table.rows[0].cells[0].text = '作品'				# 表格头
    table.rows[0].cells[1].text = '人物'

    i = 1											# i是指向每个作品的指针
    for k,v in nCharacter.items():					# 遍历小说-人物字典元素,k获得键值(作品),v获得值(人物)

        # 加表格
        table.rows[i].cells[0].text = k				# 把作品和人物填进表格
        table.rows[i].cells[1].text = v
        i +=1										# 指向下一行

        docObj.add_heading(k).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER	# 作品名做小标题

        docObj.add_paragraph(nQuote[k][0])			# 写入文章第一段

        docObj.add_picture(nPic[k], width=Cm(6))		# 加图片,设置图片尺寸

        last_paragraph = docObj.paragraphs[-1]			# 获得图片段落
        last_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER	# 图片居中

        docObj.add_paragraph(nQuote[k][1:])				# 加上剩余的文字


    for paragraph in docObj.paragraphs:					# 设置所有段落文字字体
        for run in paragraph.runs:
            setChineseFont(run, 'KaiTi')

    for row in table.rows:								# 设置所有表格文字字体
        for cell in row.cells:
            for paragraph in cell.paragraphs:
                for run in paragraph.runs:
                    setChineseFont(run, 'KaiTi')

    return docObj


authorList = ['鲁迅']					# 只要作家材料准备完善就可以加入这个列表,自动生成排好版的文章

for author in authorList:

    nCharacter, nQuote, nPic = getElem(author)	# 获得组成word文档的原始材料

    createWord(author, nCharacter, nQuote, nPic).save(author + '.docx')	# 生成排好版的word文档

面向对象实现:

# ---------------- 自动汇总材料生成Word文档(面向对象)---------------

from docx import Document						# 操控word
from docx.enum.table import WD_TABLE_ALIGNMENT				# 表格居中
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT			# 标题居中
from docx.shared import Cm						# 图片尺寸
from docx.oxml.ns import qn						# 设置中文字体用
from pathlib import Path						# 路径设置

class Writer():

    def __init__(self, name):
        self.name = name
        self.currentFolder = Path(name)
        self.novelCharacter = {}
        self.novelQuote = {}
        self.novelPic = {}
        self.getElem()

    def getElem(self):

        listFile = self.currentFolder.joinpath('list.txt')

        fileObj = listFile.open(encoding='utf-8')

        dataLine = fileObj.readline()  # 读出一行

        while dataLine:  # 读出内容为空,说明到了文章尾

            novel, character = dataLine.split()  # 从索引中获取作品和人物

            self.novelCharacter.setdefault(novel, character)  # 作品和人物对应起来

            for f in self.currentFolder.glob(novel + '.*'):  # 从作品名搜作品内容和作品的相关图片
                if f.suffix == '.txt':  # 作品内容放在txt文档
                    with f.open(encoding='utf-8') as qObj:  # 打开文章内容文件,按段落放进列表
                        # paragraphs = qObj.read().split('\n')
                        paragraphs = qObj.readlines()
                        self.novelQuote.setdefault(novel, paragraphs)
                else:  # 图片文件
                    self.novelPic.setdefault(novel, str(f))  # 作品和相关图像的位置字串

            dataLine = fileObj.readline()

class novelCollection():

    def __init__(self, authorObj):
        self.master = authorObj
        self.title = self.master.name + '小说及人物'      # word文档的标题
        self.filename = self.master.name + '.docx'   # 保存的word文档的文件名

    def launch(self):

        docObj = Document()

        # 整个word文档的标题
        docObj.add_heading(self.title, level=0).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

        # novelCollection.addTable(docObj, self.tableContent)        # 加表格
        self.addTable(docObj)        # 加表格

        for k, v in self.master.novelCharacter.items():  # 遍历小说-人物字典元素,k获得键值(作品),v获得值(人物)

            docObj.add_heading(k).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 作品名做小标题

            docObj.add_paragraph(self.master.novelQuote[k][0])  # 写入文章第一段

            self.addPicture(docObj, k)    # 加入图片
            # docObj.add_picture(self.pictures[k], width=Cm(6)).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

            docObj.add_paragraph(self.master.novelQuote[k][1:])  # 加上剩余的文字

        for paragraph in docObj.paragraphs:  # 设置所有段落文字字体
            for run in paragraph.runs:
                novelCollection.setChineseFont(run, 'KaiTi')

        docObj.save(self.filename)

    # 作品人物表格
    def addTable(self, docObj):

        # 添加表头,表格内容一行一行添加
        table = docObj.add_table(rows=1, cols=2, style='Light Grid Accent 1')
        table.alignment = WD_TABLE_ALIGNMENT.CENTER  # 表格居中

        table.rows[0].cells[0].text = '作品'  # 表格头
        table.rows[0].cells[1].text = '人物'

        for k, v in self.master.novelCharacter.items():  # 遍历小说-人物字典元素,k获得键值(作品),v获得值(人物)
            rowObj = table.add_row()    # 表格加一行
            rowObj.cells[0].text = k    # 把作品和人物填进这一行
            rowObj.cells[1].text = v

        for row in table.rows:  # 设置所有表格文字字体
            for cell in row.cells:
                for paragraph in cell.paragraphs:
                    for run in paragraph.runs:
                        novelCollection.setChineseFont(run, 'KaiTi')

        # table.allow_autofit = False  # 允许人工调节
        for row in table.rows:  # 设置表格每一列大小
            row.cells[0].width = Cm(4)
            row.cells[1].width = Cm(2)


    def addPicture(self, docObj, k):
        docObj.add_picture(self.master.novelPic[k], width=Cm(6))  # 加图片,设置图片尺寸
        last_paragraph = docObj.paragraphs[-1]  # 获得图片段落
        last_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 图片居中

    @staticmethod
    def setChineseFont(run, cFont):  # 设置中文字体
        font = run.font
        font.name = cFont
        r = run._element
        r.rPr.rFonts.set(qn('w:eastAsia'), cFont)

authorList = ['鲁迅']					# 只要作家材料准备完善就可以加入这个列表,自动生成排好版的文章

for author in authorList:
    writerObj = Writer(author)
    novelCollection(writerObj).launch()

视频解释如下: