Pull to refresh

Шаблоны *.docx с использованием скриптлетов Groovy

Reading time 4 min
Views 13K
image

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

Задача


Первичные требования были такими:
В сложном стилизованном документе doc или docx вывести нужную информацию в помеченных местах.

В дальнейшем они были уточнены и расширены:
  • В сложном стилизованном документе docx вывести данные в помеченных местах.
  • Разметка вывода данных должна быть похожа на скриптлеты: ${}, <%%>, <%=%>.
  • Данными для вывода могут быть объект. Нужна возможность обращения к полям.
  • Для вывода использовать один из скриптовых языков: Groovy, JavaScript.
  • Нужно иметь возможность выводить списки объектов в таблицы, в каждой ячейке отображая поля.


Имеющиеся решения


Оказалось, что имеющиеся в области продукты (я говорю про платформу Java), не решают поставленную задачу. Ниже краткий обзор продуктов:

Jasper reports

В качестве шаблона использует файл xml-разметки *.jrxml. Файл разметки документа + данные (как из БД, так и Map параметров) отдаются процессору, который формирует любой из следующих форматов: PDF, XML, HTML, CSV, XLS, RTF, TXT.
Не устроило:
  • Это не WYSIWYG, даже при наличии iReport — визуального средства формирования jrxml-файлов.
  • Надо хорошо изучить JasperReports API, чтобы создать и стилизовать сложный шаблон.
  • Не выводит в нужном формате. Можно и PDF, но хотелось бы иметь возможность потом поредактировать выходной документ.

Docx4java

Инструмент манипулирования составными частями docx-, pptx-, xlsx- документов с помощью Java-API.
Не устроило:
  • В документации к Docx4Java нет совпадающего с моими требованиями кейса. Есть краткое упоминание функции XMLUtils.unmarshallFromTemplate, который делает простейшие подстановки.
  • Обработка повторов вывода реализована через XML-исходники с использование XPath, ссылка.

Apache POI

Инструмент манипулирования составными частями doc-, ppt-, xls- документов с помощью Java-API. Изначально создан для получения данных из документов этих форматов.
Не устроило:
  • Нет решешения поставленной задачи.


Решение задачи


Это было интересно :)

1. Контент документа хранится в виде xml, сжатом в zip-архиве. Распаковка-запаковка трудна тем, что традиционный зиппер JDK 6 не поддерживает явное указание кодировки (видимо, имен файлов). Получался битый docx при архивировании. Пришлось при запаковке контента использовать Groovy-класс-обертку AntBuilder, имеющий соответствующий параметр.

2. Любой введенный в MS Word текст может быть разбит программой на кусочки и помещен в разные группы xml-тэгов.Таким образом, мне пришлось вначале решить задачу очистки шаблона от сгенерированных прокладок xml. Для этой задачи я использовал regex-выражения, мне они показались быстрее SAX-парсера (хотя производительность не мерил).

3. Я решил использовать Groovy как скриптовый язык из-за простоты, Java-природы и входящего в ядро процессора шаблонов. С ним также возникли интересные трудности. Оказалось, что даже в небольшом документе на 10 листов, можно легко нарваться на ограничение по длине строки между двумя скриптлетами. Пришлось заменять весь текст, идущий между скриптлетами на UUID-строки, прогонять процессор шаблонов Groovy, и только на выходе заменять UUID-строки на исходные кусочки XML.

Преодолев эти трудности, я опробовал проект в реальной жизни. Получилось хорошо!

Я создал англоязычный сайт проекта и опубликовал его.
Адрес проекта: snowindy.github.com/scriptlet4docx

Пример использования API

HashMap<String, Object> params = new HashMap<String, Object>();
params.put("name", "John");
params.put("sirname", "Smith");

DocxTemplater docxTemplater = new DocxTemplater(new File("path_to_docx_template/template.docx"));
docxTemplater.process(new File("path_to_result_docx/result.docx"), params);


На этом можно закончить, адресуя вас на сайт проекта…
Но для улучшения понимания, переведу наиболее интересную секцию “Scriptlet types explanation”.

Типы скриптлетов, подробнее


Disclaimer: При работе с темплейтами, процессор шаблонов Groovy переводит все скриптлеты в Groovy-код, а текст между скриптлетами выводит таким образом: out.print('template_text')

${ data }

Эквивалент выводу data в out: out.print(data)

<%= data %>

Эквивалент выводу data в out: out.print(data)

<% any_code %>

Выполнение кода внутри скриптлета, ничего не выводит. Может быть использован для условного вывода:
<% if (cond) { %>
Этот текст будет вставлен, если "cond == true"
<% } else { %>
Этот текст будет вставлен, если "cond != true"
<% } %>


$[ @listVar.field ]

Это самый интересный вид скриптлетов! Он используется для вывода списков объектов в таблицу внутри docx-документа. Должен использоваться внутри ячейки таблицы.
К примеру, у нас есть список объектов Person. Объект имеет два поля: 'name' и 'address'. Мы хотим вывести список в таблице из двух колонок.

  1. Создание параметра 'personList' в карте входных значений, ссылается на список объектов.
  2. Создание таблицы с двумя колоноками и одной строкой в docx-документе.
  3. $[@person.name] надо ввести в первую ячейку; $[@person.address] — во вторую.
  4. Все готово, список personList будет распечатан в таблице.


Живой пример шаблона

Лежит здесь: ссылка.

Развитие проекта


Если я действительно изобрел новый подход к процессингу docx-шаблонов, хотелось бы популяризовать его.
Проекту есть куда стремиться:
  • Полноценное кеширование,
  • Поддержка скриптлетов в списках
  • Внедрение Streaming API


Буду рад советам в распространении проекта в англоязычной аудитории!
Tags:
Hubs:
+3
Comments 10
Comments Comments 10

Articles