python-docx生成目录方法探索及汇总整理
前言
- 工具:python-docx == 0.8.11
- 环境:Linux/windows
- 需求:使用python自动生成word文档时,生成目录。
- 先放结论:如果项目需求必须要基于linux环境,不能基于win32com等依赖于windows系统的库,目前没有找到完美的方案直接自动生成带标题页码的目录,只能通过一些折中或者间接的方式,尽可能简单实现,且“像”一个完整的目录。
背景-使用python-docx生成报告思路简述
使用python-docx生成word报告一般可以有两种思路:
- 直接使用python-docx逐段生成内容,如:
from docx import Documentdoc = 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 Documentdoc = Document('template.docx') # 参数为.docx模板文件路径def write_to_paragraph(paragraph, text):# 该方法替换的文字内容可保持原段落格式paragraph.runs[0].text = textfor 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 Documentdoc = 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 Documentdef add_title_with_bookmark(doc, text, style, bookmark_id):paragraph = doc.add_paragraph(text, style=style)run = paragraph.add_run()tag = run._rstart = 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 = doc.add_paragraph('文档标题')add_title_with_bookmark('第一部分',style='Heading 1', bookmark_id='1')
add_title_with_bookmark('1.二级标题',style='Heading 2', , bookmark_id='2')
for i in range(15):doc.add_paragraph(str(i))
add_title_with_bookmark('第二部分', style='Heading 1', bookmark_id='3')
add_title_with_bookmark('1.二级标题', style='Heading 2', bookmark_id='4')
for i in range(15):doc.add_paragraph(str(i))
add_title_with_bookmark('2.二级标题', style='Heading 2', bookmark_id='5')
for i in range(15):doc.add_paragraph(str(i))
add_title_with_bookmark('3.二级标题', style='Heading 2', bookmark_id='6')for paragraph in doc.paragraphs:if 'Heading' in paragraph.style.name:b = paragraph._element.findall('.//' + qn('w:bookmarkStart'))bookmark_name = b[0].get(qn('w:name'))text = paragraph.textlevel = int(paragraph.style.name[-1])print(text, bookmark_name)toc_paragraph = doc.add_paragraph()hyperlink = OxmlElement('w:hyperlink')hyperlink.set(qn('w:anchor'), bookmark_name)hyperlink.set(qn('w:history'), '1')hr2 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'begin')hr2.append(rPr)hr2.append(fldChar)hyperlink.append(hr2)hr3 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')instrText = OxmlElement('w:instrText')instrText.set(qn('xml:space'), 'preserve')instrText.text = ' PAGEREF {} \h '.format(bookmark_name)hr3.append(rPr)hr3.append(instrText)hyperlink.append(hr3)hr4 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'separate')hr4.append(rPr)hr4.append(fldChar)hyperlink.append(hr4)hr5 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')hr5.text = ''hr5.append(rPr)hyperlink.append(hr5)hr6 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'end')hr6.append(rPr)hr6.append(fldChar)hyperlink.append(hr6)toc_paragraph._p.append(hyperlink)doc.save('result.docx')
- 方式二:使用docx模板设置好标题及标题级别,通常标题已经包含书签中,可以参考方式一遍历段落,通过
paragraph.style.name
判断获取标题及其标签。
包含页码
一些网上查阅到的方案:
1. 对于word文档中已添加目录(如使用基于模板生成的方法,事先插入目录),通过更改setting.xml
设置,在末尾加上 <w:updateFields w:val="true"/>
,打开word文档时弹出对话框询问是否更新域,需手动点击“是”,完成更新。
# 方法一:引用网上查到的方法,使用lxml库
import lxml
from docx import Documentdoc = Document('**.docx') # 待更新目录
name_space = "http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
update_name_space = "%supdateFields" % name_space
val_name_space = "%sval" % name_space
element_update_field_obj = xml.etree.SubElement(doc.settings.element, update_name_space)element_update_field_obj.set(val_name_space,"true")doc.save('result.docx')# 方法二:使用python-docx库的方法
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qnupdate = OxmlElement('w:updateFields')
update.set(qn('w:val'), 'true')
doc.settings.element.append(update)doc.save('result.docx')
个人测试效果:若word中包含其他域,打开word后会弹出提示框询问是否更新域,点击“是”后,继续询问更新目录“只更新页码”或”更新整个目录“。目录确实可以更新,但是保存后下次打开文档依然会询问是否更新,另存为也会提示,体验并不友好。需要在文档的【文件】-【选项】-【高级】选项卡的常规项中,取消勾选”打开时更新自动链接“。(可能不同word版本或wps会有差异)
总结下来就是这个方法稍显鸡肋,正常打开文档后再点击更新目录的操作跟该方法复杂度差别不大。
2. 从stackoverflow和github搬运的方法:使用python-docx写入TOC域代码。
stackoverflow链接: https://stackoverflow.com/questions/18595864/python-create-a-table-of-contents-with-python-docx-lxml
github链接: https://github.com/python-openxml/python-docx/issues/36
该方法前提是word中已经定义好各级标题。
from docx.oxml.ns import qn
from docx.oxml import OxmlElementparagraph = self.document.add_paragraph()
run = paragraph.add_run()
fldChar = OxmlElement('w:fldChar') # creates a new element
fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve') # sets attribute on element
instrText.text = 'TOC \\o "1-3" \\h \\z \\u' # change 1-3 depending on heading levels you needfldChar2 = OxmlElement('w:fldChar')
fldChar2.set(qn('w:fldCharType'), 'separate')
fldChar3 = OxmlElement('w:t')
fldChar3.text = "右击更新目录" # 文字内容可调整
fldChar2.append(fldChar3)fldChar4 = OxmlElement('w:fldChar')
fldChar4.set(qn('w:fldCharType'), 'end')r_element = run._r
r_element.append(fldChar)
r_element.append(instrText)
r_element.append(fldChar2)
r_element.append(fldChar4)
p_element = paragraph._p
效果如下图:
该方法不能直接生成目录列表,需要右击弹出菜单,选择【更新域】后,可生成目录。或可与方法一结合,只需打开文档是选择更新域或更新目录,打开后即为完整目录。
综合实践案例
目标:使用python-docx生成标题和不带页码的目录,目录按层级缩进,打开文档后可手动更新整个目录。
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
from docx.shared import RGBColor
from docx.oxml.ns import qn
from docx.oxml import OxmlElementdoc = Document()# 添加标题和书签
def add_title_with_bookmark(doc, text, style, bookmark_id):paragraph = doc.add_paragraph(text, style=style)run = paragraph.add_run()tag = run._rstart = OxmlElement('w:bookmarkStart')start.set(qn('w:id'), str(bookmark_id))start.set(qn('w:name'), 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'), text)tag.append(end)# 开始写入文档
doc.add_paragraph('文档标题')
doc.add_paragraph('目录')
# 标记目录的位置
catalog_p = doc.add_paragraph('')# 写入标题和段落
add_title_with_bookmark(doc, '第一部分',style='Heading 1', bookmark_id='1')
add_title_with_bookmark(doc, '1.二级标题',style='Heading 2', bookmark_id='2')
for i in range(15):doc.add_paragraph('<<p{}>>'.format(str(i)))
add_title_with_bookmark(doc, '第二部分', style='Heading 1', bookmark_id='3')
add_title_with_bookmark(doc, '1.二级标题', style='Heading 2', bookmark_id='4')
for i in range(15):doc.add_paragraph('<<p{}>>'.format(str(i)))
add_title_with_bookmark(doc, '2.二级标题', style='Heading 2', bookmark_id='5')
for i in range(15):doc.add_paragraph('<<p{}>>'.format(str(i)))
add_title_with_bookmark(doc, '3.二级标题', style='Heading 2', bookmark_id='6')# 开始写入目录# 目录开头增加值域,方便手动更新整个目录。自动生成的目录标题包含在值域中。
toc_paragraph = catalog_p.insert_paragraph_before() # 在标记的目录位置前添加段落
r1 = toc_paragraph.add_run()
toc_field = OxmlElement('w:fldChar')
toc_field.set(qn('w:fldCharType'), 'begin')
r1._r.append(toc_field)r2 = toc_paragraph.add_run()
toc_field = OxmlElement('w:instrText')
toc_field.set(qn('xml:space'), 'preserve')
toc_field.text = 'TOC \\o "1-3" \\h \\z '
r2._r.append(toc_field)r3 = toc_paragraph.add_run()
toc_field = OxmlElement('w:fldChar')
toc_field.set(qn('w:fldCharType'), 'separate')
r3._r.append(toc_field)# 自动生成目录内容,不包含页码
for paragraph in doc.paragraphs:if 'Heading' in paragraph.style.name:b = paragraph._element.findall('.//' + qn('w:bookmarkStart'))bookmark_name = b[0].get(qn('w:name'))text = paragraph.textlevel = int(paragraph.style.name[-1])# print(text, bookmark_name)toc_paragraph = catalog_p.insert_paragraph_before(style='Normal')# 二级标题设置缩进if level == 2:toc_paragraph.paragraph_format.first_line_indent = Pt(24)# 设置制表符,可显示页码前的"…………" tabs = OxmlElement('w:tabs')tab1 = OxmlElement('w:tab')tab1.set(qn('w:val'), "left")tab1.set(qn('w:leader'), "dot")tab1.set(qn('w:pos'), "8400")tabs.append(tab1)toc_paragraph._p.pPr.append(tabs) # toc_paragraph若未设定style,toc_paragraph._p没有pPr属性,需注释前一句代码,使用以下语句# pPr = OxmlElement('w:pPr')# pPr.append(tabs)# toc_paragraph._p.append(pPr)hyperlink = OxmlElement('w:hyperlink')hyperlink.set(qn('w:anchor'), bookmark_name)hyperlink.set(qn('w:history'), '1')hr1 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')rStyle = OxmlElement('w:rStyle')rStyle.set(qn('w:val'), "a4")rPr.append(rStyle)hr1.text = texthr1.append(rPr)hyperlink.append(hr1)hr2 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'begin')hr2.append(rPr)hr2.append(fldChar)hyperlink.append(hr2)hr3 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')instrText = OxmlElement('w:instrText')instrText.set(qn('xml:space'), 'preserve')instrText.text = ' PAGEREF {} \h '.format(bookmark_name)hr3.append(rPr)hr3.append(instrText)hyperlink.append(hr3)hr4 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'separate')hr4.append(rPr)hr4.append(fldChar)hyperlink.append(hr4)hrt = OxmlElement('w:r')tab = OxmlElement('w:tab')hrt.append(tab)hyperlink.append(hrt)hr5 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')hr5.text = ''hr5.append(rPr)hyperlink.append(hr5)hr6 = OxmlElement('w:r')rPr = OxmlElement('w:rPr')fldChar = OxmlElement('w:fldChar')fldChar.set(qn('w:fldCharType'), 'end')hr6.append(rPr)hr6.append(fldChar)hyperlink.append(hr6)toc_paragraph._p.append(hyperlink)# 目录结尾的值域
toc_paragraph = catalog_p.insert_paragraph_before()
r4 = toc_paragraph.add_run()
toc_field = OxmlElement('w:fldChar')
toc_field.set(qn('w:fldCharType'), 'end')
r4._r.append(toc_field)
# 分页
break_page_p = catalog_p.insert_paragraph_before()
break_page_p.add_run().add_break(WD_BREAK.PAGE)doc.save(r'result.docx')
效果:
python-docx生成目录方法探索及汇总整理相关推荐
- python docx table 生成_从Python docx生成docx文件,pythondocx
python-docx是python编程语言的一个库,可以对docx文档进行读,同时也可以进行生成docx文档,这篇文档主要是讲生成docx文档. 1.生成一个空docx文档 # coding:utf ...
- python学习 生成目录树
第一种最速方式,cmd直接用tree命令,如果还要文件用 tree /f C:\Users\ds>tree d:\9月考勤 > d:\test.xlsC:\Users\ds>tree ...
- python图片生成gif方法汇总
一.使用 PIL 和 images2gif 库 第一种方法主要是采用PIL和imagegif库,由于版本的原因,可能有的imagegif对图片进行处理的时候,以下语句会报错 writeGif(outf ...
- Python自动化生成代码以及验证代码汇总
python 软件自动化 0 绪 1. 自动打开.编译.下载: 1.1 自动打开工程,打开软件 1.2. 自动点击编译,下载按钮, 2. 自动写代码 2.1 对工程进行更改 2.2 对C文件更改 3 ...
- Python OS 文件/目录方法
目录 一.python os.path模块 二.os.listdir 与os.walk获得文件的路径 情况1:在一个目录下面只有文件,没有文件夹,这个时候可以使用os.listdir 情况2:递归的情 ...
- CSDN博客生成目录
CSDN博客生成目录方法如下: 在博文的首部写@[TOC]目录名,自己定)(注意是英文括号) TOC可以是小写toc,是table of contents的缩写 我的目录 简介 简介A 简介B 发展近 ...
- wps怎么添加附录_如何将附录生成目录 - 卡饭网
word如何自动生成目录并动态更新 word如何自动生成目录并动态更新 在编辑文档的时候,目录有时是必不可少的一项,但在实际情况当中,很多使用者不知道怎样使用word中的目录自动生成的功能,而是自己在 ...
- Java 使用iText7生成带页码的PDF文件(同时生成目录,但是不会合并两个PDF)
一.效果图 1.带页码效果 2.目录效果 前言:Java 使用iText7生成带页码的PDF文件,同时生成目录PDF,但限于水平,暂时还在摸索合并两个PDF.不过看了一下,iText好像有生成目录的代 ...
- markdown 自动生成目录索引
使用@[TOC]命令可以自动生成目录 方法: 将要放入目录中的行用"标题"符号注明 然后在文章中单独用一行写@[TOC]即可 举例: 在编辑目录中写: 文章目录 我是谁 我是帅哥 ...
最新文章
- 配置MUX VLAN示例(汇聚层设备)
- 终于要揭开神秘面纱?Magic Leap将要展示产品
- jQuery操作Dom、jQuery事件机制、jQuery补充部分
- [Android]反编译apk + eclipse中调试smali
- 国内数据中心分布及供电系统概述
- 机器学习基础-特征缩放交叉验证法-05
- 2048——Java控制台版本
- 【转】四阶魔方还原1
- Java获取函数参数名称
- Jquery 屏蔽右键菜单,识别右键事件
- python datetime格式_python time和datetime常用写法格式
- 从小米摄像头事件,到物联网安全的“三重门”
- 解决安装office2007的各种工具时提示“安装程序找不到office.zh-cn/*”的问题
- Inverse design of 3d molecular structures with conditional generative neural networks(2022论文阅读)
- 北京大学肖臻老师《区块链技术与应用》公开课笔记8——BTC挖矿篇
- [blog] Speech Recognition Is Not Solved 语音识别领域尚待解决的子问题
- FL Studio教程之在线面板详解
- Android学习笔记——APP页面转换与Intent学习
- e3服务器性能,揭Xeon E3-1200 V3变化:性能测试见分晓
- 视频处理-按帧或者按秒截取图像