前言
- 工具:python-docx == 0.8.11
- 环境:Linux/windows
- 需求:使用python自动生成word文档时,生成目录。
- 先放结论:如果项目需求必须要基于linux环境,不能基于win32com等依赖于windows系统的库,目前没有找到完美的方案直接自动生成带标题页码的目录,只能通过一些折中或者间接的方式,尽可能简单实现,且“像”一个完整的目录。
背景-使用python-docx生成报告思路简述
使用python-docx生成word报告一般可以有两种思路:
- 直接使用python-docx逐段生成内容,如:
from docx import Document
doc = Document()
doc.add_paragraph('文档标题')
doc.add_paragraph('第一部分',style='Heading 1')
doc.add_paragraph('1.二级标题',style='Heading 2', )
# 任意生成些段落
for i in range(15):
doc.add_paragraph(str(i))
doc.add_paragraph('第二部分', style='Heading 1')
doc.add_paragraph('1.二级标题', style='Heading 2')
for i in range(15):
doc.add_paragraph(str(i))
doc.add_paragraph('2.二级标题', style='Heading 2')
for i in range(15):
doc.add_paragraph(str(i))
doc.add_paragraph( '3.二级标题', style='Heading 2')
doc.save('result.docx')
- 基于docx文件,事先准备.docx模板, 可采用特定的占位标记,遍历文档的
paragraphs
对象,向文件中填充内容。该方法适用于word内容大纲相对固定的报告生成,优点是方便设置文档的排版及内容格式等,因此在目录生成上可以直接在模板文档中插入目录,需要解决的问题是页码更新。
*.docx模板文档示例如下:
生成内容代码如下:
from docx import Document
doc = Document('template.docx') # 参数为.docx模板文件路径
def write_to_paragraph(paragraph, text):
# 该方法替换的文字内容可保持原段落格式
paragraph.runs[0].text = text
for i in par.runs[1:]:
i.clear()
for p in doc.paragraphs:
if p.text == '<<p1>>':
# write_to_paragraph(p, text)
p.text = 'replace p1 text'
elif p.text == '<<p2>>':
# write_to_paragraph(p, text)
p.text = 'replace p2 text'
# 其他段落略
doc.save('result.docx')
生成目录方法
使用python-docx生成目录(或者说基于修改xml的方式生成或处理docx文档的工具)的难点主要在于页码的生成和更新,目录需要获取的标题所在的页码,是通过布局引擎提供的分页功能实现的,布局引擎是Word 客户端中内置的一个非常复杂的软件,用 Python 编写页面布局引擎并不是一个好主意。
因此,简化折中的方式可以包括:
- 只包含各级标题,无页码;
- 包含各级标题且可点击链接至标题所在位置,无页码;
- 包含各级标题和页码,但需手动或半自动更新目录域。
不包含页码
1.遍历Document
对象的paragraph
列表,通过paragraph
对象的style.name
属性判断标题级别,并获取标题文字,生成目录。
from docx import Document
doc = Document('result.docx')
for paragraph in doc.paragraphs:
if 'Heading' in paragraph.style.name:
text = paragraph.text
# level = int(paragraph.style.name[-1])
new_p = doc.add_paragraph('text')
doc.save('result1.docx')
2.标题增加链接:标题添加bookmark书签,生成目录时添加超链接至书签位置。
- 方式一:使用python-docx生成标题
from docx import Document
def add_title_with_bookmark(doc, text, style, bookmark_id):
paragraph = doc.add_paragraph(text, style=style)
run = paragraph.add_run()
tag = run._r
start = OxmlElement('w:bookmarkStart')
start.set(qn('w:id'), str(bookmark_id))
start.set(qn('w:name'), bookmark_text)
tag.append(start)
tr = OxmlElement('w:r')
tr.text = ''
tag.append(tr)
end = OxmlElement('w:bookmarkEnd')
end.set(qn('w:id'), str(bookmark_id))
end.set(qn('w:name'), bookmark_text)
tag.append(end)
doc = Document()
doc_title