Модуль python-docx
в основном используется для создания документов MS Word, но не для их изменения. Модуль python-docx-template
был создан, для легкого и элегантного создания множества подобных документов из заготовленных шаблонов .docx
.
Идея состоит в том, чтобы создать нужный пример/шаблон документа с помощью Microsoft Word. Он может быть настолько сложным, насколько это необходимо: с изображениями, таблицами, колонтитулами, заголовками, в общем все, что можно сделать с Word. Затем вставить в него теги, используемые jinja2
, непосредственно там где ожидаются изменения и сохранить полученный шаблон DOCX.
Теперь можно использовать модуль python-docx-template
для создания любого количества подобных документов Word из созданного шаблона DOCX, изменяя "на лету" переменные контекста, которые транслируются в теги jinja2
.
python-docx-template
в виртуальное окружение.Модуль python-docx-template
размещен на PyPI, поэтому установка относительно проста.
# создаем виртуальное окружение, если нет $ python3 -m venv .venv --prompt VirtualEnv # активируем виртуальное окружение $ source .venv/bin/activate # ставим модуль python-docx-template (VirtualEnv):~$ python3 -m pip install -U docxtpl
jinja
для DOCX;RichText
;python-docx-template
в командной строке.Создайте новый документ DOCX, поместите туда строку {{ company_name }} и отформатируйте ее (поместите по центру, задайте шрифт, цвет и т.д.), а затем сохраните этот шаблон под названием word_tpl.docx
:
from docxtpl import DocxTemplate # определяем словарь переменных контекста, # которые определены в шаблоне документа DOCX context = {} context['company_name'] = 'Название компании.' doc = DocxTemplate("word_tpl.docx") # подставляем контекст в шаблон doc.render(context) # сохраняем и смотрим, что получилось doc.save("generated_docx.docx")
Так как в модуле используется пакет Jinja2
, то можно использовать все теги и фильтры jinja2 внутри документа Word. Тем не менее, есть некоторые ограничения, чтобы Jinja2
работал корректно внутри документа Word:
jinja2
.Обычные теги jinja2
должны использоваться только внутри одного и того же прогона одного и того же абзаца, его нельзя использовать в нескольких абзацах, строках таблицы, прогонах. Если лень управлять отдельными абзацами, строками таблицы и всем циклом с его стилем, то необходимо использовать специальный синтаксис тегов, как описано ниже.
Примечание:
Прогон - это объект Run
в Microsoft Word и представляет собой последовательность символов с одинаковым стилем. Например, если создать абзац с символами одного стиля, то MS Word внутренне создаст только один "прогон" в абзаце. Если выделить текст жирным шрифтом в середине этого абзаца, то Word превратит предыдущий "прогон" в 3 разных "прогона" (обычный - жирный - обычный).
Важно: Всегда ставьте пробел после начального разделителя, используемого модулем jinja2 {{
и пробел перед конечным разделителем }}
.
# избегайте такого написания {{myvariable}} {%if something%} # вместо этого используйте: {{ myvariable }} {% if something %}
Чтобы получить недостающие переменные (например, забыли определить в словаре переменных контекста) после рендеринга документа, используйте:
tpl = DocxTemplate('your_template.docx') tpl.render(context_dict) set_of_variables = tpl.get_undeclared_template_variables()
Важно: чтобы получить набор всех ключей, определенных в шаблоне, этот метод можно использовать перед обработкой шаблона tpl.render()
.
jinja
для DOCX.Для управления абзацами, строками таблицы, столбцами таблицы, прогонами необходимо использовать специальный синтаксис:
{%p jinja2_tag %}
,{%tr jinja2_tag %}
,{%tc jinja2_tag %}
,{%r jinja2_tag %}
.Используя эти теги, python-docx-template
размещает настоящие теги jinja2
в нужное место в исходный код xml
документа. Кроме того, эти теги сообщают модулю об удалении абзаца, строки таблицы, столбца таблицы или прогона, где расположены начальный и конечный теги, и заботится о том, что находится между ними.
Важно: не используйте {%p
, {%tr
, {%tc
или {%r
дважды в одном и том же абзаце, строке, столбце или прогоне.
Например:
# Неправильное использование {%p if display_paragraph %}Абзац №{{num}}.{%p endif %} # Правильно {%p if display_paragraph %} Абзац №{{num}}. {%p endif %}
MS Word рассматривает каждую строку как новый абзац, и следовательно теги {%p
во втором случае не находятся в одном и том же абзаце.
jinja2
с предыдущей строкой, используя {%-
,jinja2
со следующей строкой, используя -%}
.Текст, содержащий теги Jinja2
, может быть слишком длинный и плохо читаемым.
Мой дом находится в {% if living_in_town %}городской{% else %}сельской{% endif %} местности и мне это нравится.
Можно использовать Shift+Enter, чтобы разделить текст, как показано ниже, а затем использовать {%- и -%}
, чтобы docxtpl
объединил все это:
Мой дом находится в {%- if living_in_town -%} городской {%- else -%} сельской {%- endif -%} местности и мне это нравится.
Важно:
{%- xxx -%}
должны быть в строке без дополнительного текста: не добавляйте какой-либо текст до или после этого тега.Для отображения переменных контекста, модуль jinja2
использует синтаксис двойных фигурных скобок {{ context_var }}
.
Если переменная context_var
является строкой, то специальные символы \n
, \a
, \t
и \f
будут переведены соответственно в новые строки, новые абзацы, табуляции и разрывы страниц соответственно.
Но если context_var
является объектом RichText
(модуля docxtpl
), то необходимо указать, что изменяется фактический объект прогона Run
. Обратите внимание на дополнительный символ r
сразу после открывающих фигурных скобок:
{{r context_var }}
Важно:
r
в шаблоне DOCX, так как конструкция шаблона {{r}}
может быть интерпретировано как {{r
без указания переменной. Тем не менее, можно использовать более длинное имя переменной, начинающееся с r
. Например, {{render_color}}
будет интерпретироваться как {{ render_color }}
, а не как {{render_color}}
.{{r
в одном и том же прогоне. Используйте метод RichText.add()
для объединения нескольких строк и стилей на стороне кода python.Есть особый случай, когда необходимо изменить цвет фона ячейки в таблице, в этом случае нужно поставить следующий тег в самом начале ячейки:
{% cellbg color_var %}
Переменная контекста color_var
должна содержать шестнадцатеричный код цвета БЕЗ знака решетки.
Если нужно динамически распределить ячейку таблицы по нескольким столбцам (это полезно, когда есть таблица с динамическим количеством столбцов), то необходимо поместить следующий тег в самое начало ячейки для охвата num_var
столбцов:
{% colspan num_var %}
Переменная контекста num_var
должна содержать целое число для количества столбцов, которые нужно охватить. Смотрите пример dynamic_table.py
.
Чтобы отобразить {%
, %}
, {{
или }}
, можно использовать следующий синтаксис:
{_%, %_}, {_{ or }_}
RichText
.Когда используется тег {{ context_var }}
в шаблоне DOCX, то он будет заменен строкой, содержащейся в переменной context_var
. НО он сохранит стиль, заданный в шаблоне. Если необходимо добавить динамически изменяемый стиль, то нужно использовать: тег {{r context_var }}
И объект RichText
внутри переменной context_var
. Таким образом можно изменить цвет, размер, стиль (жирный, курсив) и так далее, но лучше конечно - использовать Microsoft Word для определения собственного стиля символов. Вместо использования RichText()
можно использовать его ссылку R()
.
Важно:
{{r}}
, модуль удаляет текущий стиль символов из шаблона DOCX, это означает, что если не указать стиль в RichText()
, то стиль вернется к стилю Microsoft Word по умолчанию. Это повлияет только на стили символов, но не на стили абзацев (MSWord управляет этими двумя типами стилей).RichText
преобразуются в xml
перед применением любого фильтра, поэтому RichText
несовместим с фильтрами Jinja2
. Нельзя написать в шаблоне DOCX что-то вроде {{r var | lower }}
. Единственное решение - выполнять любую фильтрацию в коде Python при создании объекта RichText
.Пример работы объекта RichText
:
Создайте новый документ DOCX, поместив в него строку {{r example }} и отформатируйте ее (задайте шрифт и размер), а затем сохраните этот шаблон под названием test_rich_tpl.docx
:
from docxtpl import DocxTemplate, RichText # открываем шаблон tpl = DocxTemplate('test_rich_tpl.docx') # создаем текст rt = RichText() # можно добавить стиль текста `mystyle`, # созданный при помощи модуля `python-docx` rt.add('a rich text', style='mystyle') rt.add(' with ') rt.add('some italic', italic=True) rt.add(' and ') rt.add('some violet', color='#ff00ff') rt.add(' and ') rt.add('some striked', strike=True) rt.add(' and ') rt.add('some small', size=14) rt.add(' or ') rt.add('big', size=60) rt.add(' text.') rt.add('\nYou can add an hyperlink, here to ') rt.add('google', url_id=tpl.build_url_id('http://google.com'), color='#0018f9') rt.add('\nEt voilà ! ') rt.add('\n1st line') rt.add('\n2nd line') rt.add('\n3rd line') rt.add('\n\aA new paragraph : <cool>\a') rt.add('--- Разрыв страницы здесь (см. следующую страницу) ---\n\f') for ul in ['single', 'double', 'thick', 'dotted', 'dash', 'dotDash', 'dotDotDash', 'wave']: rt.add('\nUnderline : ' + ul + ' \n', underline=ul) rt.add('\nFonts :\n', underline=True) rt.add('Arial\n', font='Arial') rt.add('Courier New\n', font='Courier New') rt.add('Times New Roman\n', font='Times New Roman') rt.add('\n\nHere some') rt.add('superscript', superscript=True) rt.add(' and some') rt.add('subscript', subscript=True) #Добавляем текст в начало объекта `rt` rt_embedded = RichText('An example of ') rt_embedded.add(rt) # передаем созданный текст в переменную контекста context = {'example': rt_embedded} # передаем контекст в шаблон tpl.render(context) # сохраняем и смотрим что получилось tpl.save('richtext.docx')
Можно добавить гиперссылку к тексту, используя объект RichText
со следующим синтаксисом:
from docxtpl import DocxTemplate, RichText # в шаблоне создайте строку: # "Добавим гиперссылку на {{r google}}." # и сохраните как `test_tpl.docx` tpl=DocxTemplate('test_tpl.docx') href = RichText() href.add('google', url_id=tpl.build_url_id('http://google.com'), color='#0018f9', underline='single') # передаем созданную ссылку в переменную контекста context = {'google': href} # передаем контекст в шаблон tpl.render(context) # сохраняем и смотрим что получилось tpl.save('test_href.docx')
Можно динамически добавлять в документ одно или несколько изображений (проверено с файлами JPEG и PNG). Для этого просто добавьте тег с контекстной переменной, например {{ img }} в шаблон DOCX, где img является экземпляром doxtpl.InlineImage
:
from docxtpl import DocxTemplate, InlineImage img = InlineImage(tpl, image_descriptor='python_logo.png', width=Mm(20), height=Mm(10))
В объекте InlineImage
указывается объект шаблона tpl
, путь к файлу изображения image_descriptor
, ширину и/или высоту указывать не обязательно. Для указания высоты/ширины используется объект Length
и его классы миллиметры (docx.shared.Мм
) или точки (docx.shared.Pt
).
Переменная шаблона, обозначенная как {{ context_var }}
может содержать сложный и/или построенный с нуля с помощью модуля python-docx
документ Word. Для этого нужно получить объект вставляемого документа из объекта шаблона методом .new_subdoc()
и использовать его как объект документа python-docx
. Смотрите пример tests/subdoc.py
from docxtpl import DocxTemplate from docx.shared import Inches tpl = DocxTemplate('templates/subdoc_tpl.docx') # создаем вложенный документ, который затем вставим в # `subdoc_tpl.docx` как переменную шаблона {{mysubdoc}} sd = tpl.new_subdoc() p = sd.add_paragraph('This is a sub-document inserted into a bigger one') p = sd.add_paragraph('It has been ') p.add_run('dynamically').style = 'dynamic' p.add_run(' generated with python by using ') p.add_run('python-docx').italic = True p.add_run(' library') sd.add_heading('Heading, level 1', level=1) sd.add_paragraph('This is an Intense quote', style='IntenseQuote') sd.add_paragraph('A picture :') sd.add_picture('templates/python_logo.png', width=Inches(1.25)) sd.add_paragraph('A Table :') table = sd.add_table(rows=1, cols=3) hdr_cells = table.rows[0].cells hdr_cells[0].text = 'Qty' hdr_cells[1].text = 'Id' hdr_cells[2].text = 'Desc' recordset = ((1, 101, 'Spam'), (2, 42, 'Eggs'), (3, 631, 'Spam,spam, eggs, and ham')) for item in recordset: row_cells = table.add_row().cells row_cells[0].text = str(item[0]) row_cells[1].text = str(item[1]) row_cells[2].text = item[2] # передаем созданный документ # в переменную контекста context = {'mysubdoc': sd} tpl.render(context) tpl.save('output/subdoc.docx')
Начиная с версии docxtpl 0.12.0
, можно объединить существующий .docx
в качестве вложенного документа, для этого необходимо указать его путь при вызове метода .new_subdoc()
tpl = DocxTemplate('merge_docx_master_tpl.docx') sd = tpl.new_subdoc('merge_docx_subdoc.docx') context = {'mysubdoc': sd}
Когда вы используется {{ <var> }}
, то модуль изменяет документ XML
Word, это означает, что в тексте документа нельзя использовать символы <
, >
и &
. Чтобы эти символы можно было использовать, необходимо их экранировать. Есть 4 способа:
context = {'var':R('my text')}
и в шаблоне: {{r var }}
(обратите внимание на r),context = {'var':'my text'}
и в шаблоне DOCX {{ var|e }}
, context = {'var':escape('my text')}
и в шаблоне: {{ var }}
.tpl.render(context, autoescape=True)
(по умолчанию autoescape=False
)Объект RichText()
или его ссылка R()
предлагают функции новой строки, нового абзаца, табуляции и разрыва страницы: для этого в тексте используйте специальные символы \n
, \a
, \t
или \f
соответственно.
Для получения дополнительной информации смотрите пример escape.py
.
Другое решение, если в документ нужно включить список, то есть экранировать текст и управлять \n
, \a
и \f
, то можно использовать класс Listing
:
context = {'mylisting':Listing('the listing\nwith\nsome\nlines \a and some paragraph \a and special chars : <>&')}
А в шаблоне DOCX просто используйте {{ mylisting }}
. С помощью Listing()
, сохраняется текущий стиль символов (за исключением после текста, следующего после \a
, когда начинается новый абзац).
Объединить ячейки таблицы по горизонтали двумя способами:
{% colspan <number_col_span> %}
. Для получения дополнительной информации смотрите пример dynamic_table.py
.{% hm %}
внутри цикла for
(смотрите пример horizontal_merge.py
):Объединить ячейки таблицы по вертикали внутри цикла for
можно при помощи тега {% vm %}
(смотрите пример vertical_merge.py
):
Модуль python-docx-template
не умеет динамически добавлять изображения в верхний/нижний колонтитулы, но может изменить их. Идея состоит в том, чтобы поместить фиктивное изображение в шаблон DOCX, обработать шаблон как обычно, а затем заменить фиктивное изображение другим. Это можно сделать для всех носителей одновременно.
Замена происходит в верхних и нижних колонтитулах и во всем теле документа.
Примечание:
Синтаксис для замены dummy_header_pic.jpg
:
tpl.replace_pic('dummy_header_pic.jpg', 'header_pic_i_want.jpg')
python-docx-template
в командной строке.Можно использовать модуль python-docx-template
непосредственно в командной строке для создания DOCX из шаблона и файла json
в качестве контекста:
$ python3 -m docxtpl -h usage: python -m docxtpl [-h] [-o] [-q] template_path json_path output_filename Make docx file from existing template docx and json data. positional arguments: template_path The path to the template docx file. json_path The path to the json file with the data. output_filename The filename to save the generated docx. optional arguments: -h, --help show this help message and exit -o, --overwrite If output file already exists, overwrites without asking for confirmation -q, --quiet Do not display unnecessary messages
Дополнительно смотрите пример module_execute.py
.