Генерация документов docx из php с помощью библиотеки PHPWord
На первый взгляд, генерация документов docx не представляет какой-то особенной сложности — есть известная библиотека PHPOffice/PHPWord, есть документация с подробным ее описанием. Как говорится, берем и делаем. Не тут-то было.
Итак, поступила задача по доработке одного из сайтов, а именно — при генерации документов в текстовом формате Microsoft получается ошибка. Документ формируется, но не открывается. При попытке открыть файл мы иногда видим окно сообщения, где указано, что в файле присутствуют не закрытые либо не имеющие открывающего тега закрывающие теги (то есть «не открытые»). Причем для разных файлов теги и их расположение разные и некоторые файлы открываются, а некоторые — нет. В чем дело и как быть дальше?
Первым делом, я решил посмотреть, какие теги вызывают проблемы. Для этого нужно распаковать документ docx. Поскольку я пользуюсь для разработки ubuntu linux на рабочем месте, то делаю это, например, командой:
7z a -tzip файл.docx -mx0 ./директория/*
Если же вы используете Windows, воспользуйтесь архиватором 7zip или WinRAR (любым).
Надо сказать, что файл docx на самом деле обыкновенный архив, внутри которого уже после распаковки мы увидим массу разных файлов, служебных и содержательных, преимущественно, это — xml файлы. Среди них находим тот файл, который содержит непосредственно контент — document.xml. Просматриваем те теги, которые указаны в ошибке, и видим, что в качестве текста в узлах, помимо обычного текста, вставлены теги <p>, </p> и <br />, <br>. Откуда же они взялись? Поскольку генератор был написан ранее неизвестным разработчиком, смотрим в него, и видим, что данные для генерации получаются из различных полей элемента инфоблока Битрикс. Подробно это рассматривать неинтересно, у каждого сайта это могут быть другие поля и другой программный код, который получает их для добавления в генерируемый документ.
Достаточно сказать, что этот элемент находится в выводе компонента на странице, где формируется заказ товаров в админке менеджера. В нашем случае проблемное поле получалось в php из поля элемента (товара) «детальное описание». Смотрим в каталоге Битрикс детальные описания вызвавших проблемы товаров, и, действительно, видим, что контент-менеджеры вставили там тексты в формате html с указанными тегами, причем очень часто теги эти, действительно, не закрыты или не открыты — банальная ошибка при написании html. Однако, исправлять вручную это совершенно невозможно — на сайте более 10 тысяч товаров, кроме того, в будущем ошибочное заполнение также не исключено. Следовательно — необходимо предусмотреть программное исправление ошибок, насколько это возможно.
Прежде всего, пишем функцию для закрытия/открытия тегов. Однако, после ее тестирования обнаруживаем пару интересных моментов. Первое — иногда одиночные теги в тексте расставлены контент-менеджером так, что и не поймешь, что он хотел заключить внутрь параграфа, так что и программно функция тоже этого не может вычислить, и тогда наша функция просто закрывает одиночный тег пустым. Получаются пустые параграфы. С этим мы просто вынуждены смириться. В конце концов, при необходимости — контент-менеджер откроет отдельную карточку товара и сам поправит нужный параграф. Ни программист, ни даже никакая самая лучшая программа мысли читать не умеет. Второе — теги переноса строк <br> и <br /> попадают в текст узлов xml как часть текста, а не как реальные переносы строк, и это тоже вызывает ошибки — внутри текста генератор документа просто не знает, что с ними делать. Поэтому я добавил в функцию замену их и пустых параграфов на перенос строки «\n». Кроме того, нам не нужны были ссылки в формируемых документах, поэтому их я тоже удалил. Получилась такая функция:
", "\n", $text); $text = str_replace("
", "\n", $text); $text = str_replace("", "\n", $text); //удаляем ссылки из документов $pattern = "/(.*)<\/a>/iU"; return preg_replace($pattern, "", $text); }
Теперь все хорошо, формируются все документы docx, независимо от того, есть ошибки в тегах в дополнительном описании товара в каталоге или нет их. Однако оказалось, что это еще не вся проблема.
Формируемый документ состоит не только из данных, получаемых из поля дополнительного описания, обрабатывается еще несколько разных полей с информацией. И вот между ними в нужных местах должны быть переносы строк, а их нет — текст получается в одну строку. При этом, что интересно, в отдельных местах текста переносы работают, а других — нет. Открываем генератор текста из php, и смотрим, какие строки за это отвечают. Находим два варианта формирования переноса стандартным для PHPWord способом:
И вот второй-то способ АБСОЛЮТНО не работает.
Странно — идем на сайт PHPWord и ищем документацию.
На самом верху страницы https://phpword.readthedocs.io/en/latest/elements.html#breaks видим гордые плюсики для Text Break для всех типов элементов! Однако не поторопились ли разработчики? Может быть, все дело в том, что наш генератор разрабатывался довольно давно, и библиотека устарела с тех пор, как ее поставили на этот сайт? Действительно, в описаниях релизов за несколько последних лет можно найти, что по крайней мере один раз исправлен баг в Text Break, может быть, это он? Ничего подобного — обновляю библиотеку PHPWord на сайте до самой последней версии, и все равно перенос не работает!
В итоге пришлось полностью переработать генератор документа docx, исключив из него вообще Text Run и воспользовавшись только Section, с которым перенос работал нормально.
Вывод: не всегда следует верить задокументированным функциям даже у хорошо известных и проверенных программных продуктов. Конечно, вполне возможно, что на работоспособность повлияли какие-то особенные настройки php именно на данном конкретном сервере на данном сайте — но такие моменты тоже должны быть поштучно выявлены разработчиками и отражены в документации для PHPWord или любой другой программы.
А. Ямангулов