python-docx生成多级编号数字列表

前言

入职了一家开发流程极其规范的公司,每个项目都要编写很多格式基本相同,但内容完全不同的文档。

在编写过程中,发现其中一份文档50%的内容来自于源代码,而且是纯复制粘贴+稍微润色的活儿。

秉承着重复性强的手工作业就要交给机器去办的个人原则,使用python开发了个工具,自动生成文档内容。

 

顺利的开发过程

开发环境使用miniconda里的python3,使用open读取源代码全部内容,正则匹配出合适代码块,提取其中的关键信息。最后用python-docx将结果导出到docx文件。

用gooey给工具加个GUI界面,增强易用性。

 

多级编号列表断档问题

在使用过程中,发现生成出来的编号列表与文档模板的编号列表不符,直接复制粘贴会导致编号列表的序号断档,但是再一个一个手动的赋予样式又会产生新的重复劳动。

公司文档模板

 

使用工具生成的部分相当于是文档模板中4.2里面,多级编号列表的标号从4.2.1开始递推。

我的py脚本里使用的是docx自带的样式“List Number 3”,从生成结果看,怎么都是单级编号列表。

 1 from docx import Document
 2 from docx.oxml.shared import qn
 3 from docx.shared import Pt
 4 
 5 
 6 doc = Document()
 7 para = doc.add_paragraph('', style='List Number 3')
 8 para.paragraph_format.space_after = Pt(0)
 9 run = para.add_run('表内容1')
10 run.font.name = '宋体'
11 run.font.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
12 run.font.bold = True
13 run.font.size = Pt(14)

将生成结果复制到文档模板中,样式和大纲标题均无法连续。

 

分析docx的xml

将docx解压可以得到类似如下的目录结构

 

文档内容在word/document.xml

文档样式在word/styles.xml

多级列表属性在word/numbering.xml

 

分析document.xml,使用样式“List Number 3”生成的列表段落节点如下:

<w:p w14:paraId="696A4A66" w14:textId="7E41BC18" w:rsidR="005A47E3" w:rsidRDefault="005A47E3" w:rsidP="005A47E3">
    <w:pPr>
        <w:pStyle w:val="a3"/>
        <w:numPr>
            <w:ilvl w:val="0"/>
            <w:numId w:val="2"/>
        </w:numPr>
        <w:ind w:firstLineChars="0"/>
    </w:pPr>
    <w:r>
        <w:rPr>
            <w:rFonts w:hint="eastAsia"/>
        </w:rPr>
        <w:t>提取内容1-1</w:t>
    </w:r>
</w:p>

 

根据查阅文档可得,<w:pPr>下的<w:pStyle>节点储存的是样式的styleId,<w:numPr>下的<w:ilvl>节点存的是缩进等级,而<w:numId>节点存的是数字等级。

但使用新建OxmlElement对pPr进行手动节点添加后,发现生成的docx中,数字编号依然只有一级。

 

解决方案

在对xml反复研究测试了2天后,发现靠手动的节点添加和修改无法实现多级数字编号。

【插入反杠声明】

而从修改<w:pStyle>节点的styleId实现多级数字列表这一解决方向出现了突破口。

分析styles.xml,在<w:latentStyles>节点的后面,是许多自定义的样式。例如:

<w:style w:type="paragraph" w:customStyle="1" w:styleId="lv2">
    <w:name w:val="lv2"/>
    <w:basedOn w:val="a5"/>
    <w:link w:val="lv20"/>
    <w:qFormat/>
    <w:rsid w:val="00762E5E"/>
    <w:pPr>
        <w:numPr>
            <w:ilvl w:val="1"/>
            <w:numId w:val="2"/>
        </w:numPr>
        <w:ind w:firstLineChars="0"/>
    </w:pPr>
    <w:rPr>
        <w:b/>
        <w:bCs/>
        <w:sz w:val="24"/>
        <w:szCs w:val="28"/>
    </w:rPr>
</w:style>

这是一个自定义的二级数字编号列表样式,run为粗体,继承于a5,styleId是lv2。

之前的脚本中使用的是python-docx模块自带的default.docx作为基础模板。

而解决编号列表断档问题需要制作一个专门的模板,从文档模板中把需要生成的部分拷入新模板中,这样源样式也会一起拷进来,样式可以不用重命名。

将新模板解压,查看其中的styles.xml,找到数字编号列表样式的w:styleId,把值记下。

 

在py脚本中,初始化文档时,指定模板。在添加段落时,对pPr进行自定义节点添加。

 1 from docx import Document
 2 from docx.oxml.shared import OxmlElement, qn
 3 from docx.shared import Pt
 4 
 5 
 6 doc = Document('template.docx')
 7 para = doc.add_paragraph()
 8 pPr = para._element.get_or_add_pPr()
 9 pStyle = OxmlElement('w:pStyle', {qn('w:val'), 'lv3'}) # lv3 是从styles.xml里找到的对应样式w:styleId的值
10 pPr.append(pStyle)
11 para.add_run('表内容1')

最终生成的结果符合预期,全选复制到源文档模板内能够达到编号连续的效果。

 

相关学习文档

Python操纵Word神器——python-docx大全

DocumentFormat.OpenXml

 

posted @ 2022-05-26 10:37  Dr.P+P  阅读(3932)  评论(1编辑  收藏  举报