Вопрос:
Я не уверен, что я упустил что-то очевидное, но я не нашел ничего документированного о том, как можно было бы вставлять элементы 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, чтобы найти node и передать это этим методам python-docx? Любая помощь по этому вопросу была бы высоко оценена! Спасибо.
Лучший ответ:
Я вспомнил старую пословицу, “используйте источник, Люк!”, и мог понять это. Сообщение от владельца python-docx на странице проекта git также дало мне подсказку: https://github.com/python-openxml/python-docx/issues/7.
Доступ к полной модели документа XML можно получить с помощью свойства _document_part._element. Он ведет себя точно так же, как элемент lxml etree. Оттуда все возможно.
Чтобы решить мою конкретную проблему с вставкой, я создал объект temp 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-документа doc, чтобы найти индекс точки вставки. Я определил небольшую функцию для задачи, которая возвращает элемент абзаца родительского элемента с именем:
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’)
Теперь мы можем использовать index_par etree index для вставки нашего сгенерированного содержимого 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
Владелец 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
Спасибо, что нашли время, чтобы объяснить все это.
Я пережил более или менее ту же проблему. Моя особая точка заключалась в том, как объединить два или более документа 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…
Ответ №3
Вы помещаете [изображение] в качестве токена в документе шаблона:
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))
у вас есть параграф в ячейке таблицы. просто найдите ячейку и выполните действия, описанные выше.
Ответ №4
Взгляните на python-docx-template, который позволяет вставлять шаблоны стиля jinja2 в файл docx, а не в закладки Word: