python

您所在的位置:网站首页 word自动生成目录找不到目录项 python

python

2023-12-14 12:22| 来源: 网络整理| 查看: 265

前言 工具: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 == '': # write_to_paragraph(p, text) p.text = 'replace p1 text' elif p.text == '': # 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 = 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.text level = 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设置,在末尾加上 ,打开word文档时弹出对话框询问是否更新域,需手动点击“是”,完成更新。

方法一:引用网上查到的方法,使用lxml库 import lxml from docx import Document doc = 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 qn update = 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 OxmlElement paragraph = 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 need fldChar2 = 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 OxmlElement doc = 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'), 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(''.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(''.format(str(i))) add_title_with_bookmark(doc, '2.二级标题', style='Heading 2', bookmark_id='5') for i in range(15): doc.add_paragraph(''.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.text level = 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 = text hr1.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')

效果:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3