Генерация word файла python

Время на прочтение
2 мин

Количество просмотров 70K

Исполняем обязанности по получению сведений о своих бенефициарных владельцах

Небольшая вводная

Начиная с 21 декабря 2016 года вступили изменения в ФЗ РФ «О противодействии легализации (отмыванию) доходов, полученных преступным путем, и финансированию терроризма», касательно обязанности юридического лица по раскрытию информации о своих бенефициарных владельцах. В связи с этим, многие компании направляют запросы по цепочке владения с целью выяснения своих бенефициарных владельцев. Кто-то формирует запросы на бумаге, кто-то рассылает электронные письма.

На наш взгляд, надлежащим доказательством исполнения обязанности «знай своего бенефициарного владельца» является наличие письма на бумаге с отметкой об отправке/вручении. Данные письма в идеале должны готовиться не реже одного раза в год. Если в ведении юриста находится всего несколько компаний, то составление писем не составляет особого труда. Но, если компаний больше 3-х десятков, составление писем превращается в уничтожающую позитив рутину. Дело усугубляется тем, что реквизиты писем постоянно меняются: подписанты увольняются, компании перерегистрируются, меняя адреса. Все это надо учитывать. Как здесь могут помочь навыки программирования на python?

Очень просто — хорошо бы иметь программу, которая сама будет подставлять в письма необходимые реквизиты. В том числе формировать сами письма, не заставляя создавать документ за документом вручную. Попробуем.

Структура письма в word. Модуль python docxtpl

Перед написанием кода программы посмотрим как должен выглядеть шаблон письма, в который мы будем помещать наши данные.

Текст письма от общества своему участнику/акционеру будет примерно следующим:

Напишем простую программу, которая заполнит для начала одно поле в нашем шаблоне, чтобы понять принцип работы.

Для начала в самом шаблоне письма Word вместо одного из полей, например, подписанта поставим переменную. Данная переменная должна быть на либо на англ. языке, либо на русском, но в одно слово.Также переменная должна быть обязательно заключена в двойные фигурные скобки. Выглядеть это будет примерно так:

Сама программа будет иметь следующий вид:

from docxtpl import DocxTemplate
doc = DocxTemplate("шаблон.docx")
context = { 'director' : "И.И.Иванов"}
doc.render(context)
doc.save("шаблон-final.docx")

Вначале мы импортируем модуль для работы с документами формата Word. Далее мы открываем шаблон, и в поле директор, которое бы обозначили ранее в самом шаблоне, вносим ФИО директора. В конце документ сохраняется под новым именем.

Таким образом, чтобы заполнить все поля в файле-шаблоне Word нам для начала необходимо определить все поля ввода в самом шаблоне скобками {} вместе с переменными и потом написать программу. Код будет примерно следующим:

from docxtpl import DocxTemplate
doc = DocxTemplate("шаблон.docx")
context = { 'emitent' : 'ООО Ромашка', 'address1' : 'г. Москва, ул. Долгоруковская, д. 0', 'участник': 'ООО Участник', 'адрес_участника': 'г. Москва, ул. Полевая, д. 0', 'director': 'И.И. Иванов'}
doc.render(context)
doc.save("шаблон-final.docx")

На выходе при исполнении программы мы получим готовый заполненный документ.

Скачать готовый шаблон Word можно здесь.

Модуль python-docx-template в Python.

Модуль 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

Содержание:

  • Базовый пример использования;
  • Синтаксис шаблона DOCX;
  • Расширения jinja для DOCX;
  • Объект RichText;
  • Добавление одного или нескольких изображений;
  • Вложенные (вставляемые) документы DOCX;
  • Экранирование служебных символов, перевод строки, новый абзац;
  • Объединение ячеек таблицы;
  • Замена изображений в шаблоне DOCX;
  • Использование 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")

Синтаксис шаблона 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.

Специальные теги шаблона 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 -%}
 местности и мне это нравится.

Важно:

  • Используйте неразрывный пробел (Ctrl+Shift+Space), если нужен пробел в начале или конце строки.
  • Теги {%- xxx -%} должны быть в строке без дополнительного текста: не добавляйте какой-либо текст до или после этого тега.

Отображаемые переменные.

Для отображения переменных контекста, модуль jinja2 использует синтаксис двойных фигурных скобок {{ context_var }}.

Если переменная context_var является строкой, то специальные символы n, a, t и f будут переведены соответственно в новые строки, новые абзацы, табуляции и разрывы страниц соответственно.

Но если context_var является объектом RichText (модуля docxtpl), то необходимо указать, что изменяется фактический объект прогона Run. Обратите внимание на дополнительный символ r сразу после открывающих фигурных скобок:

Важно:

  • Не используйте переменную r в шаблоне DOCX, так как конструкция шаблона {{r}} может быть интерпретировано как {{r без указания переменной. Тем не менее, можно использовать более длинное имя переменной, начинающееся с r. Например, {{render_color}} будет интерпретироваться как {{ render_color }}, а не как {{render_color}}.
  • Не используйте 2 раза {{r в одном и том же прогоне. Используйте метод RichText.add() для объединения нескольких строк и стилей на стороне кода python.

Цвет ячейки в таблице.

Есть особый случай, когда необходимо изменить цвет фона ячейки в таблице, в этом случае нужно поставить следующий тег в самом начале ячейки:

Переменная контекста color_var должна содержать шестнадцатеричный код цвета БЕЗ знака решетки.

Растягивание ячейки по нескольким столбцам в таблице.

Если нужно динамически распределить ячейку таблицы по нескольким столбцам (это полезно, когда есть таблица с динамическим количеством столбцов), то необходимо поместить следующий тег в самое начало ячейки для охвата num_var столбцов:

Переменная контекста num_var должна содержать целое число для количества столбцов, которые нужно охватить. Смотрите пример dynamic_table.py.

Отображение зарезервированных символов.

Чтобы отобразить {%, %}, {{ или }}, можно использовать следующий синтаксис:

Объект 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('naA new paragraph : <cool>a')
rt.add('--- Разрыв страницы здесь (см. следующую страницу) ---nf')

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('Arialn', font='Arial')
rt.add('Courier Newn', font='Courier New')
rt.add('Times New Romann', font='Times New Roman')
rt.add('nnHere 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).

Вложенные (вставляемые) документы DOCX.

Переменная шаблона, обозначенная как {{ 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 listingnwithnsomenlines 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):

Замена изображений в шаблоне DOCX.

Модуль python-docx-template не умеет динамически добавлять изображения в верхний/нижний колонтитулы, но может изменить их. Идея состоит в том, чтобы поместить фиктивное изображение в шаблон DOCX, обработать шаблон как обычно, а затем заменить фиктивное изображение другим. Это можно сделать для всех носителей одновременно.

Замена происходит в верхних и нижних колонтитулах и во всем теле документа.

Примечание:

  • Соотношение сторон будет таким же, как у замененного изображения.
  • Укажите имя файла, которое использовалось для вставки изображения в шаблон 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.

Welcome to python-docx-template’s documentation!

Quickstart

To install using pip:

pip install docxtpl

or using conda:

conda install docxtpl --channel conda-forge

Usage:

from docxtpl import DocxTemplate

doc = DocxTemplate("my_word_template.docx")
context = { 'company_name' : "World company" }
doc.render(context)
doc.save("generated_doc.docx")

Introduction

This package uses 2 major packages :

  • python-docx for reading, writing and creating sub documents
  • jinja2 for managing tags inserted into the template docx

python-docx-template has been created because python-docx is powerful for creating documents but not for modifying them.

The idea is to begin to create an example of the document you want to generate with microsoft word, it can be as complex as you want :
pictures, index tables, footer, header, variables, anything you can do with word.
Then, as you are still editing the document with microsoft word, you insert jinja2-like tags directly in the document.
You save the document as a .docx file (xml format) : it will be your .docx template file.

Now you can use python-docx-template to generate as many word documents you want from this .docx template and context variables you will associate.

Jinja2-like syntax

As the Jinja2 package is used, one can use all jinja2 tags and filters inside the word document.
Nevertheless there are some restrictions and extensions to make it work inside a word document:

Restrictions

The usual jinja2 tags, are only to be used inside the same run of a same paragraph, it can not be used across several paragraphs, table rows, runs.
If you want to manage paragraphs, table rows and a whole run with its style, you must use special tag syntax as explained in next chapter.

Note:

a ‘run’ for Microsoft Word is a sequence of characters with the same style.
For example, if you create a paragraph with all characters of the same style,
MS Word will create internally only one ‘run’ in the paragraph. Now,
if you put in bold a text in the middle of this paragraph,
word will transform the previous ‘run’ into 3 different ‘runs’ (normal — bold — normal).

Extensions

Tags

In order to manage paragraphs, table rows, table columns, runs, special syntax has to be used:

{%p jinja2_tag %} for paragraphs
{%tr jinja2_tag %} for table rows
{%tc jinja2_tag %} for table columns
{%r jinja2_tag %} for runs

By using these tags, python-docx-template will take care to put the real jinja2 tags (without the p, tr, tc or r) at the right place into the document’s xml source code.
In addition, these tags also tell python-docx-template to remove the paragraph, table row, table column or run where the tags are located.

For example, if you have this kind of template:

{%p if display_paragraph %}
One or many paragraphs
{%p endif %}

The first and last paragraphs (those containing {%p ... %} tags) will never appear in generated docx, regardless of the display_paragraph value.

Here only:

One or many paragraphs

will appear in generated docx if display_paragraph is True, otherwise, no paragraph at all are displayed.

IMPORTANT : Always put space after a starting tag delimiter and a space before the ending one :

Avoid:

{%if something%}
{%pif display_paragraph%}

Use instead:

{% if something %}
{%p if display_paragraph %}

IMPORTANT : Do not use {%p, {%tr, {%tc or {%r twice in the same
paragraph, row, column or run. Example :

Do not use this:

{%p if display_paragraph %}Here is my paragraph {%p endif %}

But use this instead in your docx template:

{%p if display_paragraph %}
Here is my paragraph
{%p endif %}

This syntax is possible because MS Word considers each line as a new paragraph (if you do not use SHIFT-RETURN).

Display variables

As part of jinja2, one can used double braces:

{{ <var> }}

if <var> is a string, n, a, t and f will be translated respectively into newlines, new paragraphs, tabs and page breaks

But if <var> is a RichText object, you must specify that you are changing the actual ‘run’:

{{r <var> }}

Note the r right after the opening braces.

VERY IMPORTANT : Variables must not contains characters like <, > and & unless using Escaping

IMPORTANT : Always put space after a starting var delimiter and a space before the ending one :

Avoid:

{{myvariable}}
{{rmyrichtext}}

Use instead:

{{ myvariable }}
{{r myrichtext }}
Comments

You can add jinja-like comments in your template:

{#p this is a comment as a paragraph #}
{#tr this is a comment as a table row #}
{#tc this is a comment as a table cell #}

See tests/templates/comments_tpl.docx for an example.

Split and merge text
  • You can merge a jinja2 tag with previous line by using {%-
  • You can merge a jinja2 tag with next line by using -%}

A text containing Jinja2 tags may be unreadable if too long:

My house is located {% if living_in_town %} in urban area {% else %} in countryside {% endif %} and I love it.

One can use ENTER or SHIFT+ENTER to split a text like below, then use {%- and -%} to tell docxtpl to merge the whole thing:

My house is located
{%- if living_in_town -%}
 in urban area
{%- else -%}
 in countryside
{%- endif -%}
 and I love it.

IMPORTANT : Use an unbreakable space (CTRL+SHIFT+SPACE) when a space is wanted at line beginning or ending.

IMPORTANT 2 : {%- xxx -%} tags must be alone in a line : do not add some text before or after on the same line.

Escaping delimiters

In order to display {%, %}, {{ or }}, one can use:

{_%, %_}, {_{ or  }_}
Tables
Spanning

You can span table cells horizontally in two ways, by using colspan tag (see tests/dynamic_table.py):

{% colspan <var> %}

<var> must contain an integer for the number of columns to span. See tests/test_files/dynamic_table.py for an example.

You can also span horizontally within a for loop (see tests/horizontal_merge.py):

{% hm %}

You can also merge cells vertically within a for loop (see tests/vertical_merge.py):

{% vm %}
Cell color

There is a special case when you want to change the background color of a table cell, you must put the following tag at the very beginning of the cell:

{% cellbg <var> %}

<var> must contain the color’s hexadecimal code without the hash sign

RichText

When you use {{ <var> }} tag in your template, it will be replaced by the string contained within var variable.
BUT it will keep the current style.
If you want to add dynamically changeable style, you have to use both : the {{r <var> }} tag AND a RichText object within var variable.
You can change color, bold, italic, size, font and so on, but the best way is to use Microsoft Word to define your own character style
( Home tab -> modify style -> manage style button -> New style, select ‘Character style’ in the form ), see example in tests/richtext.py
Instead of using RichText(), one can use its shortcut : R()

The RichText() or R() offers newline, new paragraph, and page break features : just use n, a, t or f in the
text, they will be converted accordingly.

There is a specific case for font: if your font is not displayed correctly, it may be because it is defined
only for a region. To know your region, it requires a little work by analyzing the document.xml inside the docx template (this is a zip file).
To specify a region, you have to prefix your font name this that region and a column:

ch = RichText('测试TEST', font='eastAsia:微软雅黑')

Important : When you use {{r }} it removes the current character styling from your docx template, this means that if
you do not specify a style in RichText(), the style will go back to a microsoft word default style.
This will affect only character styles, not the paragraph styles (MSWord manages this 2 kind of styles).

IMPORTANT : Do not use 2 times {{r in the same run. Use RichText.add()
method to concatenate several strings and styles at python side and only one
{{r at template side.

Important : RichText objects are rendered into xml before any filter is applied
thus RichText are not compatible with Jinja2 filters. You cannot write in your template something like {{r <var>|lower }}.
Only solution is instead to do any filtering into your python code when creating the RichText object.

Hyperlink with RichText

You can add an hyperlink to a text by using a Richtext with this syntax:

tpl=DocxTemplate('your_template.docx')
rt = RichText('You can add an hyperlink, here to ')
rt.add('google',url_id=tpl.build_url_id('http://google.com'))

Put rt in your context, then use {{r rt}} in your template

Inline image

You can dynamically add one or many images into your document (tested with JPEG and PNG files).
just add {{ <var> }} tag in your template where <var> is an instance of doxtpl.InlineImage:

myimage = InlineImage(tpl, image_descriptor='test_files/python_logo.png', width=Mm(20), height=Mm(10))

You just have to specify the template object, the image file path and optionally width and/or height.
For height and width you have to use millimeters (Mm), inches (Inches) or points(Pt) class.
Please see tests/inline_image.py for an example.

Sub-documents

A template variable can contain a complex subdoc object and be built from scratch using python-docx document methods.
To do so, first, get the sub-document object from your template object, then use it by treating it as a python-docx document object.
See example in tests/subdoc.py.

Since docxtpl V0.12.0, it is now possible to merge an existing .docx as a subdoc, just specify its path when
calling method new_subdoc()

tpl = DocxTemplate('templates/merge_docx_master_tpl.docx')
sd = tpl.new_subdoc('templates/merge_docx_subdoc.docx')

See tests/merge_docx.py for full code.

Escaping

By default, no escaping is done : read carefully this chapter if you want to avoid crashes during docx generation.

When you use a {{ <var> }}, under the hood, you are modifying an XML word document, this means you cannot use all chars,
especially <, > and &. In order to use them, you must escape them. There are 4 ways :

  • context = { 'var':R('my text') } and {{r <var> }} in the template (note the r),
  • context = { 'var':'my text'} and {{ <var>|e }} in your word template
  • context = { 'var':escape('my text')} and {{ <var> }} in the template.
  • enable autoescaping when calling render method: tpl.render(context, autoescape=True) (default is autoescape=False)

See tests/escape.py example for more informations.

Another solution, if you want to include a listing into your document, that is to escape the text and manage n, a, and f
you can use the Listing class :

in your python code:

context = { 'mylisting':Listing('the listingnwithnsomenlines a and some paragraph a and special chars : <>&') }

in your docx template just use {{ mylisting }}

With Listing(), you will keep the current character styling (except after a a as you start a new paragraph).

Replace docx pictures

It is not possible to dynamically add images in header/footer, but you can change them.
The idea is to put a dummy picture in your template, render the template as usual, then replace the dummy picture with another one.
You can do that for all medias at the same time.
Note: the aspect ratio will be the same as the replaced image
Note2 : Specify the filename that has been used to insert the image in the docx template (only its basename, not the full path)

Syntax to replace dummy_header_pic.jpg:

tpl.replace_pic('dummy_header_pic.jpg','header_pic_i_want.jpg')

The replacement occurs in headers, footers and the whole document’s body.

Replace docx medias

It is not possible to dynamically add other medias than images in header/footer, but you can change them.
The idea is to put a dummy media in your template, render the template as usual, then replace the dummy media with another one.
You can do that for all medias at the same time.
Note: for images, the aspect ratio will be the same as the replaced image
Note2 : it is important to have the source media files as they are required to calculate their CRC to find them in the docx.
(dummy file name is not important)

Syntax to replace dummy_header_pic.jpg:

tpl.replace_media('dummy_header_pic.jpg','header_pic_i_want.jpg')

WARNING : unlike replace_pic() method, dummy_header_pic.jpg MUST exist in the template directory when rendering and saving the generated docx. It must be the same
file as the one inserted manually in the docx template.
The replacement occurs in headers, footers and the whole document’s body.

Replace embedded objects

It works like medias replacement, except it is for embedded objects like embedded docx.

Syntax to replace embedded_dummy.docx:

tpl.replace_embedded('embedded_dummy.docx','embedded_docx_i_want.docx')

WARNING : unlike replace_pic() method, embedded_dummy.docx MUST exist in the template directory when rendering and saving the generated docx. It must be the same
file as the one inserted manually in the docx template.
The replacement occurs in headers, footers and the whole document’s body.

Note that replace_embedded() may not work on other documents than embedded docx.
Instead, you should use zipname replacement:

tpl.replace_zipname(
    'word/embeddings/Feuille_Microsoft_Office_Excel1.xlsx',
    'my_excel_file.xlsx')

The zipname is the one you can find when you open docx with WinZip, 7zip (Windows) or unzip -l (Linux).
The zipname starts with «word/embeddings/». Note that the file to be replaced is renamed by MSWord, so you have to guess a little bit…

This works for embedded MSWord file like Excel or PowerPoint file, but won’t work for others like PDF, Python or even Text files :
For these ones, MSWord generate an oleObjectNNN.bin file which is no use to be replaced as it is encoded.

Get Defined Variables

In order to get the missing variables after rendering use

tpl=DocxTemplate('your_template.docx')
tpl.render(context_dict)
set_of_variables = tpl.get_undeclared_template_variables()

IMPORTANT : You may use the method before rendering to get a set of keys you need, e.g. to be prompted to a user or written in a file for manual processing.

Multiple rendering

Since v0.15.0, it is possible to create DocxTemplate object once and call
render(context) several times. Note that if you want to use replacement
methods like replace_media(), replace_embedded() and/or replace_zipname()
during multiple rendering, you will have to call reset_replacements()
at rendering loop start.

Microsoft Word 2016 special cases

MS Word 2016 will ignore t tabulations. This is special to that version.
Libreoffice or Wordpad do not have this problem. The same thing occurs for line
beginning with a jinja2 tag providing spaces : They will be ignored.
To solve these problem, the solution is to use Richtext:

tpl.render({
    'test_space_r' : RichText('          '),
    'test_tabs_r': RichText(5*'t'),
})

And in your template, use the {{r notation:

{{r test_space_r}} Spaces will be preserved
{{r test_tabs_r}} Tabs will be displayed

Jinja custom filters

render() accepts jinja_env optional argument : you may pass a jinja environment object.
By this way you will be able to add some custom jinja filters:

from docxtpl import DocxTemplate
import jinja2

def multiply_by(value, by):
   return value * by

doc = DocxTemplate("my_word_template.docx")
context = { 'price_dollars' : 5.00 }
jinja_env = jinja2.Environment()
jinja_env.filters['multiply_by'] = multiply_by
doc.render(context,jinja_env)
doc.save("generated_doc.docx")

Then in your template, you will be able to use:

Euros price : {{ price_dollars|multiply_by(0.88) }}

Command-line execution

One can use docxtpl module directly on command line to generate a docx from a template and a json file as a context:

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

See tests/module_execute.py for an example.

Examples

The best way to see how it works is to read examples, they are located in tests/ directory.
Docx test templates are in tests/templates/. To generate final docx files:

cd tests/
python runtests.py

Generated files are located in tests/output directory.

If you are not sure about your python environment, python-docx-template provides Pipfiles
for that:

pip install pipenv (if not already done)
cd python-docx-template (where Pipfiles are)
pipenv install --python 3.6 -d
pipenv shell
cd tests/
python runtests.py

Share

If you like this project, please rate and share it here : http://rate.re/github/elapouya/python-docx-template

Functions index

.. currentmodule:: docxtpl

Functions documentation

.. automodule:: docxtpl
   :members:


Indices and tables

  • :ref:`genindex`
  • :ref:`modindex`
  • :ref:`search`

Use a docx as a jinja2 template

Introduction

This package uses 2 major packages :

  • python-docx for reading, writing and creating sub documents

  • jinja2 for managing tags inserted into the template docx

python-docx-template has been created because python-docx is powerful for creating documents but not for modifying them.

The idea is to begin to create an example of the document you want to generate with microsoft word, it can be as complex as you want :
pictures, index tables, footer, header, variables, anything you can do with word.
Then, as you are still editing the document with microsoft word, you insert jinja2-like tags directly in the document.
You save the document as a .docx file (xml format) : it will be your .docx template file.

Now you can use python-docx-template to generate as many word documents you want from this .docx template and context variables you will associate.

Documentation

Please, read the doc

Other projects

Have a look at some of my other projects :

  • python-textops3 : Chainable text operations

  • django-robohash-svg : Create svg robots avatars

News

0.16.6 (2023-03-12)

  • PR #482 — thanks to dreizehnutters

0.16.5 (2023-01-07)

  • PR #467 — thanks to Slarag

  • fix #465

  • fix #464

0.16.4 (2022-08-04)

  • Regional fonts for RichText

  • Reorganize documentation

0.16.3 (2022-07-14)

  • fix #448

0.16.2 (2022-07-14)

  • fix #444

  • fix #443

0.16.1 (2022-06-12)

  • PR #442

0.16.0 (2022-04-16)

  • add jinja2 comment support — Thanks to staffanm

0.15.2 (2022-01-12)

  • fix #408

  • Multi-rendering with same DocxTemplate object is now possible
    see tests/multi_rendering.py

  • fix #392

  • fix #398

0.14.1 (2021-10-01)

  • One can now use python -m docxtpl on command line
    to generate a docx from a template and a json file as a context
    Thanks to Lcrs123@github

0.12.0 (2021-08-15)

  • Code has be split into many files for better readability

  • Use docxcomposer to attach parts when a docx file is given to create a subdoc
    Images, styles etc… must now be taken in account in subdocs

  • Some internal XML IDs are now renumbered to avoid collision, thus images are not randomly disapearing anymore.

  • fix #372

  • fix #374

  • fix #375

  • fix #369

  • fix #368

  • fix #347

  • fix #181

  • fix #61

0.11.5 (2021-05-09)

  • PR #351

  • It is now possible to put InlineImage in header/footer

  • fix #323

  • fix #320

  • n, a, t and f are now accepted in simple context string. Thanks to chabErch@github

0.10.5 (2020-10-15)

  • Remove extension testing (#297)

  • Fix spaces missing in some cases (#116, #227)

0.9.2 (2020-04-26)

  • Fix #271

  • Code styling

0.8.1 (2020-04-14)

  • fix #266

  • docxtpl is now able to use latest python-docx (0.8.10). Thanks to Dutchy-@github.

0.7.0 (2020-04-09)

  • Add replace_zipname() method to replace Excel and PowerPoint embedded files

0.6.4 (2020-04-06)

  • Add the possibility to add RichText to a Richtext

  • Prevent lxml from attempting to parse None

  • PR #207 and #209

  • Handle spaces correctly when run are split by Jinja code (#205)

  • PR #203

  • DocxTemplate now accepts file-like objects (Thanks to edufresne)

0.5.20 (2019-05-23)

  • Fix #199

  • Add support for file-like objects for replace_media (#197)

  • Fix #176

  • Delegated autoescaping to Jinja2 Environment (#175)

  • Force to use python-docx 0.8.7 (#170)

  • Add getting undeclared variables in the template (#171)

  • Added PAGE_BREAK feature (#168)

  • Fixed issue #159: autoescaped values for both str and unicode.

  • Fix tables with gridSpan that have less cells after the tc forloop (#164)

  • Smart double quotes in jinja tags are now converted into simple double quotes

  • Smart quotes in jinja tags are now converted into simple quotes

  • Add custom jinja filter example in tests/

  • Reformat the code to be a little more PEP8 compliant

  • Add {% hm %} tag for table columns horizontal merging (Thanks to nickgashkov)

  • Split tests/tests_files dir into templates and output dirs

  • autoescape support for python 2.7

  • fix issue #154

  • Render can now autoescape context dict

  • Fix invalid xml parse because using {% vm %}

  • Cast to string non-string value given to RichText or Listing objects

  • Import html.escape instead of cgi.escape (deprecated)

  • Declare package as python2 and python3 compatible for wheel distrib

  • Add sub/superscript in RichText

  • Fix table vertical merge

  • An hyperlink can now be used in RichText

0.4.13 (2018-06-21)

  • Subdocument can now be based on an existing docx

  • Add font option in RichText

  • Better tabs and spaces management for MS Word 2016

  • Wheel distribution

  • Manage autoscaping on InlineImage, Richtext and Subdoc

  • Purge MANIFEST.in file

  • Accept variables starting with ‘r’ in {{}} when no space after {{

  • Remove debug traces

  • Add {% vm %} to merge cell vertically within a loop (Thanks to Arthaslixin)

  • use six.iteritems() instead of iteritems for python 3 compatibility

  • Fixed Bug #95 on replace_pic() method

  • Add replace_pic() method to replace pictures from its filename (Thanks to Riccardo Gusmeroli)

  • Improve image attachment for InlineImage ojects

  • Add replace_media() method (useful for header/footer images)

  • Add replace_embedded() method (useful for embedding docx)

0.3.9 (2017-06-27)

  • Fix exception in fix_table()

  • Fix bug when using more than one {{r }} or {%r %} in the same run

  • Fix git tag v0.3.6 was in fact for 0.3.5 package version
    so create a tag 0.3.7 for 0.3.7 package version

  • Better head/footer jinja2 handling (Thanks to hugokernel)

  • Fix bug where one is using ‘%’ (modulo operator) inside a tag

  • Add Listing class to manage n and a (new paragraph) and escape text AND keep current styling

  • Add {%tc } tags for dynamic table columns (Thanks to majkls23)

  • Remove version limitation over sphinx package in setup.py

  • Add PNG & JPEG in tests/test_files/

  • You can now add images directly without using subdoc, it is much more faster.

0.2.5 (2017-01-14)

  • Add dynamic colspan tag for tables

  • Fix /n in RichText class

  • Add Python 3 support for footer and header

  • Fix bug when using utf-8 chracters inside footer or header in .docx template
    It now detects header/footer encoding automatically

  • Fix bug where using subdocs is corrupting header and footer in generated docx
    Thanks to Denny Weinberg for his help.

  • Add Header and Footer support (Thanks to Denny Weinberg)

0.1.11 (2016-03-1)

  • ‘>’ and ‘<’ can now be used inside jinja tags

  • render() accepts optionnal jinja_env argument :
    useful to set custom filters and other things

  • better subdoc management : accept tables

  • better xml code cleaning around Jinja2 tags

  • python 3 support

  • remove debug code

  • add lxml dependency

  • fix template filter with quote

  • add RichText support

  • add subdoc support

  • add some exemples in tests/

  • First running version

Понравилась статья? Поделить с друзьями:
  • Генерация word документов по шаблону
  • Генератор случайных чисел в excel что это
  • Генерация datamatrix в excel
  • Генератор случайных чисел word
  • Генератор штрихкодов онлайн ean 13 в excel