前言
入职了一家开发流程极其规范的公司,每个项目都要编写很多格式基本相同,但内容完全不同的文档。
在编写过程中,发现其中一份文档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')
最终生成的结果符合预期,全选复制到源文档模板内能够达到编号连续的效果。