Как стать автором
Поиск
Написать публикацию
Обновить

Формирование документов на основе ODT шаблонов. ODT to PDF

Время на прочтение4 мин
Количество просмотров11K
Здравствуйте, уважаемы хабровчане!

Не так давно мне пришлось столкнуться с типичной задачей – формировать документы с пользовательскими данными на основе шаблонов ODT средствами PHP. Звучит весьма тривиально, но намучиться пришлось сильно. Дело в том, что ни одно из доступных средств, так или иначе, не подошло. Одни библиотеки формировали документ криво, другие не поддерживали русские шрифты, третьи – двигали картинки в стиле Harlem Shake. Вот и пришлось «велосипедить».

Итак, задача вкратце:

  1. Обработать ODT шаблон. Заменить placeholder’ы на пользовательские значения
  2. Конвертировать в pdf. Показать пользователю



ЭТАП 1. Обработать ODT шаблон. Заменить placeholder’ы

Ни для кого не секрет, что ODT — это обычный архив с xml на борту. Все картинки прячутся в папке, название которой может быть любым, лишь бы на нее ссылались в файле описаний. Не будем вдаваться в подробности: достаточно лишь сказать, что за основной контент документа отвечает content.xml, за «описательную» часть – manifest.xml. Обращаю внимание, что стили текста нас не интересуют (по крайней мере, в условиях данной задачи). Копнув чуть глубже эти xml’ки выводим алгоритм:

  1. Распаковать архив
  2. Для подмены текста: парсим content.xml, заменяем placeholder’ы на нужные значения
  3. Для изображений: загружаем свои изображения в папку (создаем ее внутри распакованного .odt документа), парсим content.xml, заменяем placeholder’ы на frame вида

    <draw:frame draw:style-name="a0" draw:name="'.$file_name.'" text:anchor-type="as-char" svg:x="0in" svg:y="0in" svg:width="'.$width.'in" svg:height="'.$height.'in" style:rel-width="scale" style:rel-height="scale">
        <draw:image xlink:href="'.self::_ImgDir.DIRECTORY_SEPARATOR.$file_name.'" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
       <svg:title/>
       <svg:desc/>
    </draw:frame>
    


    Далее, добавляем в конец manifest.xml блок вида

    <manifest:file-entry manifest:full-path="'.self::_ImgDir.DIRECTORY_SEPARATOR.$file_name.'" manifest:media-type="image/'.$ext.'"/>
    

  4. Архивируем результат обратно в ODT.


Алгоритм явно упрощен, зато действенен и легок. На его основе был написан класс (на коленках) odtFormat. Небольшая справка о том, как им пользоваться:

Инициализация
$odtformat = new odtFormat(“$doc_path”, "$temp_dir");

$doc_path – путь до шаблона .odt
$temp_dir – папка, в которой будут храниться временные файлы.

Вставка текста
$odtformat->SetText(“$name”, “$value”);

$name – имя placeholder’а
$value – пользовательское значение

Вставка изображения
$odtformat->SetImage(“$name”, “$img_path” ,$width, $height);

$name – имя placeholder’a
$img_path – путь до картинки
$width – желаемая ширина изображения в документе (если не задать – ширина оригинала)
$height – желаемая длина изображения в документе (если не задать – длина оригинала)

Сохранение документа
$odtformat->SaveToDisk(“$path_to_save”);

$path_to_save – куда будем сохранять (путь+имя файла)

Остальные методы не привожу, все можно увидеть в исходнике. Тем более, что большинство вспомогательных методов взято из публичных источников. Да и сам код прост, как две копейки. Приведу лишь некоторые настройки:

const _SeporatorLeft = '{{';

Отделяет placeholder от текста слева

const _SeporatorRight = '}}';

Отделяет placeholder от текста справа

const _ImgDir = 'media';

Имя папки с пользовательскими изображениями
Ну вот, можно сказать, что файл .odt, заполненный нужными нам данными, уже сформирован. Время для второго этапа.

Внимание! Чтобы placeholder'ы заменялись корректно, при добавлении их в документ используйте «Очистить формат». Класс использовал для MS World 2013. Но что-то мне подсказывает, что содержание odt одинаково и в других версиях.

ЭТАП 2. Конвертировать в pdf. Показ пользователю

Сразу скажу, что идеального решения так и не нашел. Решений, по существу, практически нет. Перерыв «интернеты», наткнулся на горстку тяжеловесов, zend примочек и просто хлама. Так что, расскажу все, как было.
Первым делом, пытался использовать онлайн-сервисы. Сначала был Google Docs. Тут все ясно. Просто показ документа на странице через iframe, избегая самого конвертирования.
Пример:
<iframe src="http://docs.google.com/viewer?url=http%3A%2F%2F127.0.0.1%2Fa.odt&embedded=true" width="600" height="780" style="border: none;"></iframe>

Минусы:
  • Скорость отображения
  • Кривое форматирование
  • При использовании сторонних библиотек для формирования odt, появлялся странный белый лист вначале документа
  • Не pdf
  • Онлайн

Плюсы:
  • Простота


Очевидно, что данное решение долго не жило. Стоит глянуть в сторону Microsoft с их Office Apps. Интересным хинтом стало конвертирование pdf как версии для печати (встроенная функция онлайн сервиса). Таким образом, можно средствами Microsoft конвертировать файл и тут же его показать.
Пример:
<iframe src="http://co1-word-view.officeapps.live.com/wv/WordViewer/request.pdf?WOPIsrc=http%3A%2F%2Fco1%2D15%2Dview%2Dwopi%2Ewopi%2Elive%2Enet%3A808%2Foh%2Fwopi%2Ffiles%2F%40%2FwFileId%3FwFileId%3Dhttp://127.0.0.1/a.odt&type=printpdf" width="600" height="780" style="border: none;"></iframe>

Минусы
  • Безумно долго
  • Решение, само по себе, кривое
  • Непредсказуемое поведение в разных браузерах
  • Так и не удалось избавиться от окна печати
  • Онлайн

Плюсы
  • Качественное конвертирование


Не найдя больше достойных онлайн-вариантов, решено было использовать средства сервера. С этой задачей хорошо справляется Libreoffice. У него есть встроенный конвертер документов, работающий из командной строки. Идея заключалась в том, чтобы забрасывать сформированные odt в папку, передавать ее ключом к exec, отображать уже готовые pdf, лежащие в той же папке. Положим, что apt-get install libreoffice мы уже сделали. Осталось лишь дописать одну строку кода:

exec(“libreoffice --headless --invisible --convert-to pdf $full_path_to_file --outdir $full_path_to_dir”);

$full_path_to_file – полный путь до файлов (/var/www/*.odt)
$full_path_to_dir – полный путь до папки сохранения (/var/www/result/)

Как показать pdf в iframe, я думаю, вы и сами знаете.

Минусы
  • Нужен доступ к серверу
  • Тяжелый пакет libreoffice
  • exec (подобные команды в коде – дело не очень-то хорошее)

Плюсы
  • Заметный прирост в скорости
  • Качественное форматирование
  • Удобство использования готовых документов
  • Абсолютно локальное решение


Заключение
При всех недостатках решения — цели выполнены. Надеюсь, что эта статья поможет избежать некоторых трудностей при выполнении подобной задачи. Всем удачного кодинга!

Ссылки
Библиотека odtFormat
О LibreOffice

P.S. Прошу прощения за отвратительное форматирование.
Теги:
Хабы:
Всего голосов 6: ↑6 и ↓0+6
Комментарии8

Публикации

Ближайшие события