Python-docx 插入点

9

我不确定是否错过了任何显而易见的东西,但我没有找到关于如何在文档中的某个特定位置插入Word元素(例如表格)的任何记录?

我正在使用以下方式加载现有的MS Word .docx文档:

my_document = Document('some/path/to/my/document.docx')

我的使用场景是获取文档中书签或章节的“位置”,然后在该位置下方插入表格。

我在考虑一个API,可以让我做类似以下操作:

insertion_point = my_document.bookmarks['bookmark_name'].position
my_document.add_table(rows=10, cols=3, position=insertion_point+1)

我看到有计划实现类似于MS Word API的“range”对象,这将有效解决该问题。同时,在此期间,是否有一种方法可以指示document对象方法在哪里插入新元素?

也许我可以粘贴一些lxml代码来查找节点并将其传递给这些python-docx方法?对于这个问题的任何帮助都将不胜感激!谢谢。

5个回答

15

我记得一个老谚语:“使用源代码,卢克!” ,所以我能够想通。来自python-docx所有者在其git项目页面上的一篇文章也给了我一个提示:https://github.com/python-openxml/python-docx/issues/7

完整的XML文档模型可以通过使用其_document_part._element属性来访问。它的行为就像一个lxml etree元素。从那里,一切皆有可能。

为了解决我特定的插入点问题,我创建了一个临时的docx.Document对象,用于存储我的生成内容。

import docx
from docx.oxml.shared import qn
tmp_doc = docx.Document()

# Generate content in tmp_doc document
tmp_doc.add_heading('New heading', 1)
# more content generation using docx API.
# ...

# Reference the tmp_doc XML content
tmp_doc_body = tmp_doc._document_part._element.body
# You could pretty print it by using:
#print(docx.oxml.xmlchemy.serialize_for_reading(tmp_doc_body))

然后我将包含名为 'insertion_point' 的书签的docx模板加载到第二个docx.Document对象中。

doc = docx.Document('/some/path/example.docx')
doc_body = doc._document_part._element.body
#print(docx.oxml.xmlchemy.serialize_for_reading(doc_body))
下一步是解析doc XML以查找插入点的索引。我为此任务定义了一个小函数,它返回一个带名称的书签父段落元素:

接下来要解析文档XML文件以查找插入点的索引。我为这个任务定义了一个小函数,该函数返回一个命名的书签父段落元素:

def get_bookmark_par_element(document, bookmark_name):
"""
Return the named bookmark parent paragraph element. If no matching
bookmark is found, the result is '1'. If an error is encountered, '2'
is returned.
"""
doc_element = document._document_part._element
bookmarks_list = doc_element.findall('.//' + qn('w:bookmarkStart'))
for bookmark in bookmarks_list:
    name = bookmark.get(qn('w:name'))
    if name == bookmark_name:
        par = bookmark.getparent()
        if not isinstance(par, docx.oxml.CT_P): 
            return 2
        else:
            return par
return 1

新定义的函数被用来获取书签“insertion_point”的父段落。错误控制留给读者自行处理。

bookmark_par = get_bookmark_par_element(doc, 'insertion_point')
我们现在可以使用 bookmark_par 的 etree 索引,在正确的位置插入我们生成的 tmp_doc 内容:
bookmark_par_parent = bookmark_par.getparent()
index = bookmark_par_parent.index(bookmark_par) + 1
for child in tmp_doc_body:
    bookmark_par_parent.insert(index, child)
    index = index + 1
bookmark_par_parent.remove(bookmark_par)

该文档现已定稿,生成的内容已插入到现有Word文档中书签位置。

# Save result
# print(docx.oxml.xmlchemy.serialize_for_reading(doc_body))
doc.save('/some/path/generated_doc.docx')

我希望这能帮到某些人,因为关于这个的文档还没有被编写。


1
关于版本0.8.7,应写成doc_element = doc.part.element而不是doc_element = document._document_part._element - Max
1
谢谢,这个答案帮了我很多。我已经弄明白了。你有什么想法可以让我只替换书签所在段落中的文本吗?例如,我只想更改书签所在段落中的一个单词。 - Tooblippe

3
你需要在模板文档中使用 [image] 作为令牌:
for paragraph in document.paragraphs:
    if "[image]" in paragraph.text:
        paragraph.text = paragraph.text.strip().replace("[image]", "")

        run = paragraph.add_run()
        run.add_picture(image_path, width=Inches(3))

你可以在一个表格单元格中拥有一个段落,只需找到该单元格并按照上述方法进行操作。

3

Python-docx的所有者建议如何将表格插入到现有文档的中间位置: https://github.com/python-openxml/python-docx/issues/156

这里附上改进后的代码:

import re
from docx import Document

def move_table_after(document, table, search_phrase):
    regexp = re.compile(search_phrase)
    for paragraph in document.paragraphs:
        if paragraph.text and regexp.search(paragraph.text):
            tbl, p = table._tbl, paragraph._p
            p.addnext(tbl)
            return paragraph

if __name__ == '__main__':
    document = Document('Existing_Document.docx')    
    table = document.add_table(rows=..., cols=...)
    ...
    move_table_after(document, table, "your search phrase")                    
    document.save('Modified_Document.docx')

2

0
非常感谢您抽出时间来解释所有这些。
我遇到了更多或更少相同的问题。我的具体问题是如何在最后合并两个或多个docx文档。
虽然这不完全是解决您问题的方法,但这是我想到的函数:
def combinate_word(main_file, files, output):   
    main_doc = Document(main_file)
    for file in files:
        sub_doc = Document(file)

        for element in sub_doc._document_part.body._element:
            main_doc._document_part.body._element.append(element)

    main_doc.save(output)

很遗憾,使用python-docx目前还无法轻松复制图像。我退而求其次使用win32com...


谢谢分享!我还没有尝试过处理图片,所以对于那方面的挑战不太确定。 - Apteryx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接