【问题标题】:python-docx insertion pointpython-docx 插入点
【发布时间】:2014-09-17 20:35:22
【问题描述】:

我不确定我是否遗漏了任何明显的东西,但我没有找到任何关于如何在文档中的某个特定位置插入 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 的“范围”对象的东西,这将有效地解决这个问题。同时,有没有办法指示document 对象方法在哪里插入新元素?

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

【问题讨论】:

    标签: python-3.x insertion cursor-position python-docx


    【解决方案1】:

    我记得一句古老的格言,“使用来源,卢克!”,并且可以理解。 python-docx 所有者在其 git 项目页面上的帖子也给了我一个提示:https://github.com/python-openxml/python-docx/issues/7。

    可以使用其_document_part._element 属性访问完整的XML 文档模型。它的行为与 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))
    

    然后我将我的 docx 模板(包含名为“insertion_point”的书签)加载到第二个 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))
    

    下一步是解析文档 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')
    

    我希望这可以帮助某人,因为有关此的文档尚未编写。

    【讨论】:

    • 至于0.8.7版本,写doc_element = doc.part.element而不是doc_element = document._document_part._element。
    • 感谢您的回答对我帮助很大。能够弄清楚。如果书签存在,我可以如何替换段落中的文本。例如。只想更改段落中的一个单词是书签。
    【解决方案2】:

    您将 [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')
      

      【讨论】:

        【解决方案4】:

        看看 python-docx-template 它允许 jinja2 样式模板在 docx 文件中插入点而不是 Word 书签:

        https://pypi.org/project/docxtpl/

        https://docxtpl.readthedocs.io/en/latest/

        【讨论】:

          【解决方案5】:

          非常感谢您花时间解释所有这些。

          我或多或少地遇到了同样的问题。我的具体观点是最后如何合并两个或多个 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 ...

          【讨论】:

          • 感谢分享!我还不需要尝试图像,所以我不确定这方面的挑战。
          猜你喜欢
          • 1970-01-01
          • 2019-10-17
          • 1970-01-01
          • 2014-04-13
          • 2018-03-13
          • 1970-01-01
          • 2022-10-25
          • 1970-01-01
          • 2019-06-13
          相关资源
          最近更新 更多