我正在使用 python-docx 模块来读取和编辑 .docm 文件, 该文件包含书签,如何访问已使用该模块存储的所有书签,doc对象中似乎没有任何方法。
正如@D Malan 所评论的,现在到了 2021 年 11 月,它仍然是 python-docx 中的一个开放的问题。
同时我们可以接受我们自己的实现。
请在可导入的文件夹中创建一个名为
docxbookmark.py
的文件:
from docx.document import Document as _innerdoclass
from docx import Document as _innerdocfn
from docx.oxml.shared import qn
from lxml.etree import Element as El
class Document(_innerdoclass):
def _bookmark_elements(self, recursive=True):
if recursive:
startag = qn('w:start')
bkms = []
def _bookmark_elements_recursive(parent):
if parent.tag == startag:
bkms.append(parent)
for el in parent:
_bookmark_elements_recursive(el)
_bookmark_elements_recursive(self._element)
return bkms
else:
return self._element.xpath('//'+qn('w:bookmarkStart'))
def bookmark_names(self):
"""
Gets a list of bookmarks
"""
return [v for bkmkels in self._bookmark_elements() for k,v in bkmkels.items() if k.endswith('}name')]
def add_bookmark(self, bookmarkname):
"""
Adds a bookmark with bookmark with name bookmarkname to the end of the file
"""
el = [el for el in self._element[0] if el.tag.endswith('}p')][-1]
el.append(El(qn('w:bookmarkStart'),{qn('w:id'):'0',qn('w:name'):bookmarkname}))
el.append(El(qn('w:bookmarkEnd'),{qn('w:id'):'0'}))
def __init__(self, innerDocInstance = None):
super().__init__(Document, None)
if innerDocInstance is not None and type(innerDocInstance) is _innerdoclass:
self.__body = innerDocInstance.__body
self._element = innerDocInstance._element
self._part = innerDocInstance._part
def DocumentCreate(docx=None):
"""
Return a |Document| object loaded from *docx*, where *docx* can be
either a path to a ``.docx`` file (a string) or a file-like object. If
*docx* is missing or ``None``, the built-in default document "template"
is loaded.
"""
return Document(_innerdocfn(docx))
现在我们可以像旧的一样使用外观实现,以及新的
add_bookmark
和 bookmark_names
。
要在新文件中添加书签,请导入我们的实现并在文档对象上使用
add_bookmark
:
from docxbookmark import DocumentCreate as Document
doc = Document()
document.add_paragraph('First Paragraph')
document.add_bookmark('FirstBookmark')
document.add_paragraph('Second Paragraph')
document.save('docwithbookmarks.docx')
要查看文档中的书签,请导入我们的实现并在文档对象上使用
bookmark_names
:
from docxbookmark import DocumentCreate as Document
doc = Document('docwithbookmarks.docx')
doc.bookmark_names()
返回的列表比其他对象更简单,它只显示字符串而不是对象。有一个内部
_bookmark_elements
它将返回与 python-docx 对象不同的 lxml 节点。
只进行了一些测试,在很多情况下可能不起作用。如果不起作用请在评论中告诉我。
知道这是旧的,这个问题在 GitHub 上仍然处于开放状态。 布鲁诺夫给出的答案确实很有帮助,但似乎根本不起作用。即使可以读取书签,多个节的页眉和页脚中可能存在的书签也会被忽略。它也会以这种方式产生重复项。
from docx.document import Document as _innerdoclass
from docx import Document as _innerdocfn
from docx.oxml.shared import qn
from lxml.etree import Element as El
class Document(_innerdoclass):
def _bookmark_elements(self, recursive=True):
if recursive:
startag = qn('w:bookmarkStart')
bkms = {}
def _bookmark_elements_recursive(parent):
if parent.tag == startag:
bookmark_name = parent.attrib.get(qn('w:name'))
bookmark_id = str(parent.attrib.get(qn('w:id')))
if bookmark_name:
bkms[bookmark_id] = {
'name': bookmark_name,
'current_value': parent.text,
'new_value': '',
}
for el in parent:
_bookmark_elements_recursive(el)
for section in self.sections:
for header in section.header.part.element:
_bookmark_elements_recursive(header)
_bookmark_elements_recursive(self.element)
for footer in section.footer.part.element:
_bookmark_elements_recursive(footer)
return bkms
else:
return self._element.xpath('//' + qn('w:bookmarkStart'))
def bookmark_names(self):
"""
Returns a list of bookmarks
"""
return dict(sorted(self._bookmark_elements().items(), key=lambda x: x[0]))
def add_bookmark(self, bookmarkname):
"""
Adds a bookmark with bookmark with name bookmarkname to the end of the file
"""
el = [el for el in self._element[0] if el.tag.endswith('}p')][-1]
el.append(El(qn('w:bookmarkStart'), {qn('w:id'): '0', qn('w:name'): bookmarkname}))
el.append(El(qn('w:bookmarkEnd'), {qn('w:id'): '0'}))
def __init__(self, innerDocInstance=None):
super().__init__(Document, None)
if innerDocInstance is not None and type(innerDocInstance) is _innerdoclass:
self.__body = innerDocInstance.__body
self._element = innerDocInstance._element
self._part = innerDocInstance._part
def DocumentCreate(docx=None):
"""
Return a |Document| object loaded from *docx*, where *docx* can be
either a path to a ``.docx`` file (a string) or a file-like object. If
*docx* is missing or ``None``, the built-in default document "template"
is loaded.
"""
return Document(_innerdocfn(docx))
我刚刚更改了 _bookmark_elements_recursive,因此它可以与 bookmarkStart 一起使用,并确保将这些部分纳入字典 bkms 中,该字典也从列表中进行了更改,因此我将 ID 作为键处理,这变得更易于使用。
我根本不想拨打电话以防止重复。
Thomas Barnekow 做了一个很好的示例,说明了 docx 文件的结构以及 XML 的使用方式:https://stackoverflow.com/a/59111255/2823842
简而言之:当找到 bookmarkStart 时,会提供 run (w:r) 和 if could 内的文本 (w:t)。