【问题标题】:combine word document using python docx使用 python docx 合并 word 文档
【发布时间】:2014-07-21 18:52:42
【问题描述】:

我有几个单词文件,每个文件都有特定的内容。我想要一个 sn-p,它可以向我展示或帮助我弄清楚如何在使用 Python docx 库时将单词文件合并到一个文件中。

例如在 pywin32 库中,我做了以下操作:

rng = self.doc.Range(0, 0)
for d in data:
    time.sleep(0.05)

    docstart = d.wordDoc.Content.Start
    self.word.Visible = True
    docend = d.wordDoc.Content.End - 1
    location = d.wordDoc.Range(docstart, docend).Copy()
    rng.Paste()
    rng.Collapse(0)
    rng.InsertBreak(win32.constants.wdPageBreak)

但我需要在使用 Python docx 库而不是 win32.client 时这样做

【问题讨论】:

  • 我再次写了这个问题@abarnert
  • 重写后的问题看起来很容易回答。谢谢@omri_saadon
  • @AdamSmith:可以回答,是的,但现在他要求我们将他的代码从一个库移植到另一个库,这仍然不适合 SO。尤其是因为他没有展示他的任何 docx 代码,或者描述了他已经走了多远以及他在哪里卡住了,除了最模糊的术语。
  • 我不知道该怎么做,我的想法是遍历每个文档(遍历段落和表格)并以某种方式将其复制到新的 word 文件中。即使您对如何做到这一点有一个大致的了解,我也会很高兴。我熟悉这个图书馆几天了。 @abarnert –

标签: python python-2.7 python-docx


【解决方案1】:

合并两个包含所有样式的文档的替代方法是使用 python 库 docxcompose (https://pypi.org/project/docxcompose/)。我们不需要明确定义样式,也不需要逐段阅读文档并将其附加到主文档。 python docxcompose的用法如下代码所示

#Importing the required packages

from docxcompose.composer import Composer
from docx import Document as Document_compose
#filename_master is name of the file you want to merge the docx file into
master = Document_compose(filename_master)

composer = Composer(master)
#filename_second_docx is the name of the second docx file
doc2 = Document_compose(filename_second_docx)
#append the doc2 into the master using composer.append function
composer.append(doc2)
#Save the combined docx with a name
composer.save("combined.docx")

如果您想将多个文档合并到一个 docx 文件中,您可以使用以下函数


#Filename_master is the name of the file you want to merge all the document into
#files_list is a list containing all the filename of the docx file to be merged
def combine_all_docx(filename_master,files_list):
    number_of_sections=len(files_list)
    master = Document_compose(filename_master)
    composer = Composer(master)
    for i in range(0, number_of_sections):
        doc_temp = Document_compose(files_list[i])
        composer.append(doc_temp)
    composer.save("combined_file.docx")
#For Example
#filename_master="file1.docx"
#files_list=["file2.docx","file3.docx","file4.docx",file5.docx"]
#Calling the function
#combine_all_docx(filename_master,files_list)
#This function will combine all the document in the array files_list into the file1.docx and save the merged document into combined_file.docx

【讨论】:

  • 这是一个很老的问题,但你代表作曲家得到了我的支持 :)
  • @Shashank 我可以使用 docxcompose 将一个文档中的文本添加到另一个文档中的特定位置吗?
  • @ShashankShekharShukla 非常感谢。您的评论很有价值。
  • 这正是我要找的,投我一票!
  • 这个解决方案比其他迭代元素的主体更好,因为它可以正确处理图像!
【解决方案2】:

我已调整上面的示例以使用最新版本的 python-docx(撰写本文时为 0.8.6)。请注意,这只是复制元素(合并元素的样式更复杂):

from docx import Document

files = ['file1.docx', 'file2.docx']

def combine_word_documents(files):
    merged_document = Document()

    for index, file in enumerate(files):
        sub_doc = Document(file)

        # Don't add a page break if you've reached the last file.
        if index < len(files)-1:
           sub_doc.add_page_break()

        for element in sub_doc.element.body:
            merged_document.element.body.append(element)

    merged_document.save('merged.docx')

combine_word_documents(files)

【讨论】:

  • 是的,但仍然相关 :)
  • 这非常有用,谢谢。在我的情况下,我有很多自定义样式要处理(但对于所有文档都是相同的),因此发现使用列表中的第一个文档作为 merged_document 然后将所有其他文档附加到它更容易。这样,与默认模板的样式就不会发生冲突,这是Document() 默认使用的。
  • 很好的解决方案。请注意,python-docx 逻辑中的 append 表示剪切和粘贴,而不是复制和粘贴,因此如果您使用的是上述接受现有文档的修改版本(就像我一样),那么您需要先保存doc 到临时路径,从该路径实例化一个新文档,然后清理临时路径(我能找到克隆文档的最佳方法)。
  • 这显然工作正常,但是,请注意,如果文档包含图像,则它们不会被正确复制....最终文档说它无法显示图像...
【解决方案3】:

如果您的需求很简单,这样的事情可能会奏效:

source_document = Document('source.docx')
target_document = Document()

for paragraph in source_document.paragraphs:
    text = paragraph.text
    target_document.add_paragraph(text)

您还可以做一些其他事情,但这应该可以帮助您入门。

事实证明,在一般情况下,将内容从一个 Word 文件复制到另一个文件是相当复杂的,例如协调源文档中可能与目标文档冲突的样式。因此,这不是我们可能会在明年添加的功能。

【讨论】:

  • 它还会复制表格吗? @scanny
  • 没有。有关与此相关的一些讨论,请参阅此页面:github.com/python-openxml/python-docx/issues/40
  • 我成功地将所有内容复制到一个新的 docx 文件中,但是所有格式都消失了(例如粗体)。有没有办法保留它们?
  • 好吧,就像我说的,解决一般情况下的问题很复杂。您可能会通过进入运行级别并在那里匹配粗体和斜体来取得一些进展。每个段落都由运行组成(大致上),字符格式位于运行级别。
【解决方案4】:

创建一个空文档 (empty.docx) 并将您的两个文档添加到其中。 在文件迭代的每个循环中,如有必要,请添加分页符。

完成后保存包含两个组合文件的新文件。

from docx import Document

files = ['file1.docx', 'file2.docx']

def combine_word_documents(files):
    combined_document = Document('empty.docx')
    count, number_of_files = 0, len(files)
    for file in files:
        sub_doc = Document(file)

        # Don't add a page break if you've
        # reached the last file.
        if count < number_of_files - 1:
            sub_doc.add_page_break()

        for element in sub_doc._document_part.body._element:
            combined_document._document_part.body._element.append(element)
        count += 1

    combined_document.save('combined_word_documents.docx')

combine_word_documents(files)

【讨论】:

  • AttributeError: 'Document' 对象没有属性 '_document_part' ?
  • @coachcal _document_part 是“私有的”,不应作为 API 访问。无论如何,这取决于版本/实现。例如。 Python3 可能会消失。试试 Martijn Jacobs 的解决方案。这看起来非常相似,但不使用私有成员。我刚试过那个,它工作了(Python 3.5.3)。
【解决方案5】:

如果只需要将简单的文档与纯文本结合起来,可以使用上面提到的python-docx。

如果您需要合并包含超链接、图像、列表、项目符号等的文档。您可以使用 lxml 来合并文档正文和所有参考文件,例如:

  • word/styles.xml
  • word/numbering.xml
  • 文字/媒体
  • [Content_Types].xml

【讨论】:

  • 这听起来很有希望。你能举例说明如何做到这一点吗?可以作为单独的问题+答案吗?非常感谢。
【解决方案6】:

这一切都非常有用。我结合了 Martijn Jacobs 和 Kriss 先生的答案。

def combine_word_documents(input_files):
    """
    :param input_files: an iterable with full paths to docs
    :return: a Document object with the merged files
    """
    for filnr, file in enumerate(input_files):
        # in my case the docx templates are in a FileField of Django, add the MEDIA_ROOT, discard the next 2 lines if not appropriate for you. 
        if 'offerte_template' in file:
            file = os.path.join(settings.MEDIA_ROOT, file)

        if filnr == 0:
            merged_document = Document(file)
            merged_document.add_page_break()

        else:
            sub_doc = Document(file)

            # Don't add a page break if you've reached the last file.
            if filnr < len(input_files)-1:
                sub_doc.add_page_break()

            for element in sub_doc.element.body:
                merged_document.element.body.append(element)

    return merged_document

【讨论】:

  • 如果将merged_document.add_page_break() 移动到else 树的开头,则不需要if filnr &lt; len(input_files)-1: 子句。然后,您将在除第一个文档之外的每个文档之前插入分页符。
  • 页眉和页脚文本重复三次!
【解决方案7】:

另一个替代解决方案是Aspose.Words Cloud SDK for Python。它根据 ImportFormatMode 参数保留文档的格式/样式。该参数定义将使用哪种格式:附加或目标文档。可能的值是 KeepSourceFormatting 或 UseDestinationStyles。

# For complete examples and data files, please go to https://github.com/aspose-words-cloud/aspose-words-cloud-python
import os
import asposewordscloud
import asposewordscloud.models.requests
from shutil import copyfile


# Please get your Client ID and Secret from https://dashboard.aspose.cloud.
client_id='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx'
client_secret='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

words_api = asposewordscloud.WordsApi(client_id,client_secret)
words_api.api_client.configuration.host='https://api.aspose.cloud'


remoteFolder = 'Temp'
localFolder = 'C:/Temp'
localFileName = 'destination.docx'
remoteFileName = 'destination.docx'
localFileName1 = 'source.docx'
remoteFileName1 = 'source.docx'

#upload file
words_api.upload_file(asposewordscloud.models.requests.UploadFileRequest(open(localFolder + '/' + localFileName,'rb'),remoteFolder + '/' + remoteFileName))
words_api.upload_file(asposewordscloud.models.requests.UploadFileRequest(open(localFolder + '/' + localFileName1,'rb'),remoteFolder + '/' + remoteFileName1))

#append Word documents
requestDocumentListDocumentEntries0 = asposewordscloud.DocumentEntry(href=remoteFolder + '/' + remoteFileName1, import_format_mode='KeepSourceFormatting')

requestDocumentListDocumentEntries = [requestDocumentListDocumentEntries0]
requestDocumentList = asposewordscloud.DocumentEntryList(document_entries=requestDocumentListDocumentEntries)
request = asposewordscloud.models.requests.AppendDocumentRequest(name=remoteFileName, document_list=requestDocumentList, folder=remoteFolder, dest_file_name= remoteFolder + '/' + remoteFileName)

result = words_api.append_document(request)

#download file
request_download=asposewordscloud.models.requests.DownloadFileRequest(remoteFolder + '/' + remoteFileName)
response_download = words_api.download_file(request_download)
copyfile(response_download, localFolder + '/' +"Append_output.docx")

【讨论】: