Один из моих недавних проектов требовал автоматической генерации контрактов для клиентов. Договор является юридическим документом объемом около 10 страниц. Для многих клиентов может применяться одна форма договора, поэтому документ представляет собой шаблон с информацией о клиенте, размещенной в определенных местах.
В этой статье я собираюсь показать вам, как я решил эту проблему.
Требования
Это начальная версия формализованных требований:
Указанные данные должны быть размещены в отмеченных местах сложного файла DOC / DOCX.
Впоследствии требования были уточнены и расширены:
- Указанные данные должны быть размещены в отмеченных местах сложного файла DOCX.
- Выходная разметка должна быть похожа на скриптлет: $ {}, <%%>, <% =%>.
- Выходные данные могут быть не только строками, но также хешами и объектами. Доступ к полю должен быть опцией.
- Язык вывода должен быть кратким и понятным для сценариев: Groovy, JavaScript.
- Возможность отображения списка объектов в таблице, каждая ячейка отображает поле.
Фон
Оказалось, что существующие продукты в этой области (я говорю о мире Java) не соответствуют начальным требованиям.
Краткий обзор продуктов:
Джаспер сообщает
Jasper Reports использует файлы * .jrxml в качестве шаблонов. Файл шаблона в сочетании с входными данными (набор результатов SQL или Карта параметров) передаются процессору, который формирует любой из следующих форматов: PDF, XML, HTML, CSV, XLS, RTF, TXT.
Не вписывался в:
- Это не WYSIWYG, даже с помощью iReport — визуального инструмента для создания jrxml-шаблонов.
- JasperReports API должен быть хорошо изучен для создания и стилизации сложного шаблона.
- JR не выводит в подходящем формате. PDF может быть в порядке, но возможность ручного редактирования предпочтительнее.
Docx4java
Docx4j — это библиотека Java для создания и управления файлами Microsoft Open XML (Word docx, Powerpoint pptx и Excel xlsx).
Не вписывался в:
- В документации docx4java нет ни одного случая, отвечающего моим требованиям. Краткая заметка о функциональности XMLUtils.unmarshallFromTemplate присутствует, но она выполняет только самые простые замены.
- Повторение вывода производится с подготовленными XML-источниками и XPath, ссылка
Apache POI
Apache POI — это инструмент Java для создания и управления частями *. документы, * .ppt, * .xls. Api POI api широко используется для приложений извлечения текста, таких как веб-пауки, построители индексов и системы управления контентом.
Не вписывался в:
- Нет вариантов, которые соответствуют моим требованиям.
Word Content Control Toolkit
Word Content Control Toolkit — это автономный, легкий инструмент, который открывает любой документ Word Open XML и перечисляет все элементы управления содержимым внутри него.
После того, как я разработал свое собственное решение со скриплетами, я услышал о решении, основанном на комбинации этого инструмента и XSDT-преобразований. Это может сработать для кого-то, но я не стал копать, потому что для простого использования моего решения требуется меньше шагов.
Решение проблемы
Это было весело!
1 Текстовое содержимое документа хранится в виде файла Open XML внутри zip-архива. Традиционная молния JDK 6 не поддерживает явный параметр кодирования. То есть сломанный docx-файл может быть создан с использованием этой молнии. Мне пришлось использовать Groovy-обертку AntBuilder для архивирования, которая имеет параметр кодирования.
2 Любой текст внутри MS Word, который вы вводите, может быть «произвольным» разбитым на части, завернутые в XML. Итак, мне пришлось решить проблему очистки колодок, сгенерированных из шаблона xml. Я использовал регулярные выражения для этой задачи. Я не пытался использовать XSLT или что-то еще, потому что я думал, что RegEx будет быстрее.
3 Я решил использовать Groovy в качестве языка сценариев из-за его простоты, Java-природы и встроенного процессора шаблонов . Я нашел интересную проблему, связанную с процессором. Оказалось, что даже в небольшом 10-листовом документе можно легко столкнуться с ограничением длины строки между двумя скриптлетами.
Мне пришлось заменить текст, идущий между парой скриптлетов, UUID-строкой, запустить обработчик шаблонов Groovy с использованием измененного текста и, наконец, заменить эти UUID-заполнители начальными фрагментами текста.
Преодолев эти трудности, я опробовал проект в реальной жизни. Оказалось хорошо!
Я создал сайт проекта и опубликовал его.
Адрес проекта: snowindy.github.com/scriptlet4docx/
Пример кода
1
2
3
4
5
6
|
<span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >HashMap<String, Object> params = new HashMap<String, Object>();</span> HashMap <String, Object> params = new HashMap <String, Object> ();</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >params.put( "name" , "John" );</span> params.put ( "имя" , "Джон" );</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >params.put( "sirname" , "Smith" );</span> params.put ( "sirname" , "Smith" );</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >DocxTemplater docxTemplater = new DocxTemplater( new File( "path_to_docx_template/template.docx" ));</span> DocxTemplater docxTemplater = new DocxTemplater (новый файл ( "path_to_docx_template / template.docx" ));</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >docxTemplater.process( new File( "path_to_result_docx/result.docx" ), params);</span> docxTemplater.process (новый файл ( "path_to_result_docx / result.docx" ), params);</span> |
Объяснение типов скриптов
$ {data}
Эквивалент out.print (данные)
<% = данные%>
Эквивалент out.print (данные)
<% any_code%>
Оценивает содержащий код. Выходные данные не применяются. Может использоваться для разделенных условий:
1
2
3
4
5
|
<span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" ><% if (cond) { %></span> <% if (cond) {%></span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >This text block will be printed in case of "cond == true" </span> Этот текстовый блок будет напечатан в случае «cond == true »</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" ><% } else { %></span> <%} else {%></span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" >This text block will be printed otherwise.</span> Этот текстовый блок будет напечатан иначе.</span> <span class = "notranslate" onmouseover= "_tipon(this)" onmouseout= "_tipoff()" ><span class = "google-src-text" style= "direction: ltr; text-align: left" ><% } %></span> <%}%></span> |
$ [@ listVar.field]
Это пользовательский тип сценария Scriptlet4docx, предназначенный для вывода коллекции объектов в таблицы docx. Он должен использоваться внутри ячейки таблицы.
Скажем, у нас есть список личных объектов. У каждого есть два поля: «имя» и «адрес». Мы хотим вывести их в таблицу с двумя столбцами.
- Создайте привязку с ключом personList, ссылающимся на эту коллекцию.
- Создайте таблицу из двух столбцов в шаблонном docx-документе: два столбца, одна строка.
- $ [@ person.name] переходит в ячейку первого столбца; $ [@ person.address] переходит ко второму.
- Вуаля, вся коллекция будет напечатана на столе.
Пример живого шаблона
Вы можете проверить использование всех упомянутых скриптлетов в демонстрационном шаблоне.
Будущее проекта
Если бы я действительно разработал новый подход к обработке docx-шаблонов, было бы неплохо его популяризировать.
Проекты ТОДО:
- Кэширование предварительно обработанных шаблонов,
- Поддержка скриптов в списках
- Потоковый API
Справка: Использование скриптов Groovy в документе * .docx от нашего партнера по W4G Евгения Полихаева