С недавних пор в Битрикс24 появился замечательный функционал – генератор документов. Он способен удовлетворить большинство потребностей в генерации документов для CRM, однако иногда его функционала не хватает для решения специфических задач. Одну из таких задач мы недавно решали.
Основная проблема заключалась в том, что необходимо было генерировать сам шаблон документа, по которому вдальнейшем произойдет его генерация. Что-то вроде ситуации, когда у вас есть несколько организаций (филиалов), совместно использующих Битрикс24, и необходимо для каждой организации генерировать собственные «шапки» и «футеры» документов по определенной логике.
Да, конечно можно создать несколько шаблонов, но в данном случае требовалось передать некие параметры сначала в сами эти шаблоны, а уже потом уже сгенерировать документ. Также по условиям заказчика мы должны были получать от пользователя DOCX с телом документа, вставлять в него шапку нужной организации, вставлять данные, файлы и картинки, как в шапку, так и в документ, и отдавать пользователю сгенерированный PDF-файл.
В качестве шаблонов необходимо было использовать файлы .DOCX
Соответственно, следует разобраться в том, как они устроены. По своей сути .DOCX файл это обычный ZIP-архив, содержащий в себе файлы XML-разметки документа, сохраненные в файле изображения, стили и т.п. Открыть его для изучения можно любым ZIP-архиватором, например 7Zip:
Самый интересный файл находится по пути \word\document.xml
В этом XML-файле и представлен наш документ, точнее его разметка. В нем уже можно искать текстовое содержимое вашего шаблона, и заменять его на необходимые данные. Например расставив «плейсхолдеры» для данных, найти их и заменить реальными данными.
Работать с ZIP-архивами в PHP можно с помощью расширения ZipArchive:
$docx = new ZipArchive(); $docx->open($path); $content = $ docx ->getFromName(‘word\document.xml’); $content = str_replace($placeholder, $text, $content); $ docx ->addFromString(‘word\document.xml’, $content); $docx->close();
На этом этапе все достаточно просто. Однако нам необходимо иметь возможность вставлять не только текстовые данные, но и изображения (например образцы подписей), и даже другие docx-файлы прямо в этот шаблон (сохраняя внутренние стили форматирования). Неудобство составляет сама структура DOCX. К тому же на выходе заказчик должен получить PDF-документ. То есть необходимо сгенерированный DOCX переконвертировать в PDF.
Для решения этих двух проблем мы решили использовать OpenOffice, а точнее его форк — LibreOffice. Он без проблем ставиться на CentOS, и что главное умеет работать из командной строки. То есть мы можем его запустить прямо из PHP-скрипта. LibreOffice умеет конвертировать DOCX в PHP, и также DOCX в ODT.
Например конвертация DOCX в ODT из PHP:
shell_exec('/usr/bin/libreoffice --headless -convert-to odt:"writer8" --outdir '.realpath("/template.docx"));
Зачем нам нужен ODT? Это аналог DOCX от мира свободного ПО, и его внутренняя структура намного проще и чище (особенно это ценно при вставке одного документа в тело другого). Таким образом план такой:
1. Берем у клиента DOCX-шаблон.
2. Конвертируем его в ODT с помощью LibreOffice
3. Производим манипуляции с файлами
4. Конвертируем в PDF и отдаем клиенту.
ODT-файл также представляет из себя ZIP-архив:
Все основное содержимое хранится в \content.xml, стили в styles.xml, картинки в папке Pictures и файле meta.xml
Для наглядности посмотрим на тот же фрагмент кода основного содержимого:
Как видите, практически идентично с DOCX, но несколько понятнее и больше похоже на привычный HTML. Из-за более простой структуры, с этим файлом можно легче «играться» — например для вставки изображения мы просто вставили его в файл, и посмотрели что изменилось. Добавился лишь такой блок:
<draw:frame draw:style-name="fr1" draw:name="Picture1" text:anchor-type="as-char" svg:width="5cm" svg:height="5cm" draw:z-index="0"><draw:image xlink:href="Pictures/file1.jpg'" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/></draw:frame>
Как видите – все совсем не сложно и интуитивно понятно.
Ну и самое сложное – вставка одного DOCX файла в другой DOCX файл. Мы также конвертируем оба эти файла в ODT, затем берем их содержимое и переименовываем все стили в них, например, давая им префикс (file1_ и file2_ например). Вставляем содержимого одного файла в другой на месте заранее вставленного текстового «плейсхолдера». Затем копируем картинки одного файла в другой, также давая им префикс, и переименовывая их соответственно в файле meta.xml.
Так содержимое одного файла без проблем вставляется в другой. Например, у нас есть документ и шаблон. Шаблон содержит в себе шапку, в которую нужно вставить графический файл и данные, а после этого все вместе вставить в документ:
Вставлемый файл:
Шапка-шаблон:
Результат: