Генератор документов - феррари на которой можно ездить только вокруг дома, Новый модуль получился хорошим но не предназначен для использования разработчиками.
Но раздел написан ради "отписки", нет даже итогового примера. Если вы проследуете инструкция вы не получаете рабочего провайдера - так как нет ни слова о том что провайдер должен поддерживать sourseType шаблона. А шаблоны на данный момент можно создавать только в CRM и привязкой к CRM сущности.
Так что если вы решили использовать его в БП или своих компонентах - не тут то было, можно конечно, но готовитесь открывать права к CRM для любого человека который будет пользоваться этим генератором документов.
Вопрос - почему!? Почему такой модуль написан таким образом что я не могу элементарно передать ему файл шаблона (или создать свободный шаблон не привязанный к CRM), массив ключ-значение и получить сгенерированный документ?
Всё же это провал. Компоненты использовать нельзя, либо придётся кастомить и причём сильно (documentgenerator.view, bitrix:crm.document.view). Мы снова опускаемся да низкоуровневой логики и генерим временные файлы вручную, потом пылесосим их и сами загружаем на диск группы или пользователя.
Но раздел написан ради "отписки", нет даже итогового примера. Если вы проследуете инструкция вы не получаете рабочего провайдера - так как нет ни слова о том что провайдер должен поддерживать sourseType шаблона. А шаблоны на данный момент можно создавать только в CRM и привязкой к CRM сущности.
Так что если вы решили использовать его в БП или своих компонентах - не тут то было, можно конечно, но готовитесь открывать права к CRM для любого человека который будет пользоваться этим генератором документов.
Вопрос - почему!? Почему такой модуль написан таким образом что я не могу элементарно передать ему файл шаблона (или создать свободный шаблон не привязанный к CRM), массив ключ-значение и получить сгенерированный документ?
Если я ошибаюсь - код в студию.
Здравствуйте. Опишите, пожалуйста, подробнее, что у вас не получается.
Всё же это провал. Компоненты использовать нельзя, либо придётся кастомить и причём сильно (documentgenerator.view, bitrix:crm.document.view). Мы снова опускаемся да низкоуровневой логики и генерим временные файлы вручную, потом пылесосим их и сами загружаем на диск группы или пользователя.
Прям всё то что я ожидал от платного API.
Почему компоненты нельзя использовать? Там есть базовый класс компонента, от него наследуйтесь и пишите свой. Компонент для crm предназначен для конкретного места, он, да, не особо расчитан на изменение.
По поводу синхронизации с диском - сделайте свою реализацию \Bitrix\DocumentGenerator\Storage и там пишите, что хотите. Задать своё хранилище по умолчанию для модуля можно через опции, можно через событие в рантайме переопределить, примеры есть.
Апи есть, оно работает, рест для него имеется. Пишите конкретнее свои пожелания.
Антон Горбылев, в данный момент единственное, что не нравится в официальном пути - это генерация документов через шаблоны. Например, у меня есть активити, где выбираешь
файл, который будет использоваться в качестве шаблона (далее исходник)
директорию для сохранения результата
замены, которые необходимо произвести
Не хотелось бы исходник сохранять как шаблон, хотя бы потому, что при удалении активити, непонятно как чистить таблицу шаблонов. Использовать провайдеров без шаблонов у меня не получилось - они тесно связаны. Была мысль в момент выполнения активити временно записывать исходник в шаблон и использовать ваш подход, но это ведь костыль Поэтому и приходится вызывать напрямую DocumentGenerator\Body\Docx и в целом прекрасно понимаю о чём говорит irinadoman,
Александр Медведев написал: Антон Горбылев , в данный момент единственное, что не нравится в официальном пути - это генерация документов через шаблоны. Например, у меня есть активити, где выбираешь файл, который будет использоваться в качестве шаблона (далее исходник) директорию для сохранения результата замены, которые необходимо произвести
Не хотелось бы исходник сохранять как шаблон, хотя бы потому, что при удалении активити, непонятно как чистить таблицу шаблонов. Использовать провайдеров без шаблонов у меня не получилось - они тесно связаны. Была мысль в момент выполнения активити временно записывать исходник в шаблон и использовать ваш подход, но это ведь костыль Поэтому и приходится вызывать напрямую DocumentGenerator\Body\Docx и в целом прекрасно понимаю о чём говорит irinadoman ,
Отличное активити. Список полей из шаблона подтягиваете динамически при загрузке нового файла? Интерфейс симпатичней моей версии, надо будет доделать.
Всё-таки, почему не использовать шаблоны? Вы можете взять существующие компоненты, завязать их на свой модуль, шаблоны тоже привязать к своему модулю - и пожалуйста, интерфейс готов. Выводите здесь в выпадашке шаблоны только своего модуля. В интерфейс crm не вывалится ничего лишнего. Будут шаблоны для модуля - можно и свои провайдеры сделать. Вроде бы там ничего сложного, старался сделать попроще (getFields() - массив описаний и весь провайдер). Зато можно использовать стандартные DocumentTitle - для изменения имени документа и файла, DocumentCreateTime для даты.
И почему не получилось использовать провайдеры без шаблонов? Вы можете создать свой провайдер для модуля crm и он появится в интерфейсе (не забываем обработчик события). Можете отнаследовать провайдера crm и привязать его к своему модулю (не забыв добавить подключение модуля crm перед наследованием - можно в обработчик события добавить, как в моем сообщении выше).
В целом ваш способ тоже вполне рабочий, рад, что хотя бы сам парсер используется )
Антон Горбылев написал: Список полей из шаблона подтягиваете динамически при загрузке нового файла? Интерфейс симпатичней моей версии, надо будет
Нет, вручную, но кстати спасибо за идею)
Думаю до шаблонов я всё таки приду, понимаю как делать, но сейчас пока только активити.
Вы автор, я так полагаю? Не понял для чего значения заворачиваются в CDATA. Так, для вставки множественного значения так, чтобы разделителем была не запятая (как по дефолту), а перенос строки - пришлось "выходить" из CDATA. Разделитель получался таким
Антон Горбылев написал: Список полей из шаблона подтягиваете динамически при загрузке нового файла? Интерфейс симпатичней моей версии, надо будет
Нет, вручную, но кстати спасибо за идею)
Думаю до шаблонов я всё таки приду, понимаю как делать, но сейчас пока только активити.
Вы автор, я так полагаю? Не понял для чего значения заворачиваются в CDATA. Так, для вставки множественного значения так, чтобы разделителем была не запятая (как по дефолту), а перенос строки - пришлось "выходить" из CDATA. Разделитель получался таким
Про динамические списки полей - посмотрите мою активити crm/install/activities/bitrix/crmgenerateentitydocumentactivity/crmgenerateentitydocumentactivity.php можно оттуда скопировать. Единственное, без шаблона по-быстрому получить список полей не получится, придётся делать свой вызов Body::getPlaceholders() на вашем файле.
да, модуль мой.
Значения заворачиваются в CDATA для большей надежности, чтобы ничего лишнего не пропустить внутрь.
В планах стоит более глубокий парсинг исходного текста (если это html) и переработка его в нормальный xml с сохранением форматирования. Сейчас с этим беда.
Так вот кого я мысленно ругал за final у DocumentGenerator\Document ,пока не догнал, что можно через DocumentGenerator\Body\Docx сделать В качестве идеи: было бы неплохо реализовать в шаблонах простейшие условия, нам уж точно актуально. Например, как у Twig Кейс: есть БП "Заявление на отпуск", при запуске можно выбрать замещающего сотрудника и если выбран, в приказ добавляется строчка
Цитата
Бухгалтерии, произвести доплату ___________ в размере ___% от установленного размера должностного оклада по основной работе.
Сейчас эту строчку строим в самом БП, но это жутко неудобно (поля мелкие) и не красиво как-то.
Ещё кейс: CRM используется для логистической компании и вид накладной зависит от входных параметров, а их много. И удобнее строить документ в Word или LibreOffice, чем в БП.
final у ряда основных классов проставлен специально, как раз чтобы не было наследников (хотя до выпуска в коробку его там не было). Версия ещё достаточно сырая, так у меня нет необходимости строго следовать текущей сигнатуре. Вы можете его убрать, но с выходом каждого обновления придётся руками проверять, что ничего не сломается.
Простейшие условия и обработка входных параметров - есть в планах.
Антон Горбылев написал: final у ряда основных классов проставлен специально, как раз чтобы не было наследников (хотя до выпуска в коробку его там не было).
Эм... а с какой целью? Вот мне например final в классах битрикса доставляют очень большие неудобства и поэтому приходится извращаться.
Антон Горбылев написал: Простейшие условия ... - есть в планах.
можете привести пример?
Детали на стадии обсуждения, в ближайшую итерацию добавления каких-либо операций в шаблоны не входит. Пока что анонсировать что-то в этом направлении рано, но оно обязательно будет.
Антон Горбылев написал: final у ряда основных классов проставлен специально, как раз чтобы не было наследников (хотя до выпуска в коробку его там не было).
Эм... а с какой целью? Вот мне например final в классах битрикса доставляют очень большие неудобства и поэтому приходится извращаться.
Потому что класс и сигнатура методов ещё не приняли своего окончательного вида. Представьте, что у класса нет final. Вы спокойно сделали наследника, переопределили часть методов, добавили свои. Потом вышло обновление, где я какой-нибудь из методов сделал private, или добавил обязательных параметров.
И всё, у вас fatal каждый раз при использовании вашего класса. Если это ваш портал с какими-то специфическими доработками - можно оперативно исправить и ущерба будет немного. А вдруг кто-нибудь сделает приложение со своим наследником?
Одна из проблем битрикса, как продукта - это большое legacy. А большое legacy обусловлено политикой максимальной обратной совместимости. Поддержка обратной совместимости замедляет скорость обновления кода.
Проставив final, я, в первую очередь, оставляю себе возможность вносить изменения в классы без оглядки на пользовательский код. Говорю только за себя, но коллеги часто руководствуются этим же принципом.
Если у вас есть какие-то потребности, которые нельзя перекрыть без наследования - пишите, что-нибудь придумаем.
Антон Горбылев написал: final у ряда основных классов проставлен специально, как раз чтобы не было наследников (хотя до выпуска в коробку его там не было).
Эм... а с какой целью? Вот мне например final в классах битрикса доставляют очень большие неудобства и поэтому приходится извращаться.
Потому что класс и сигнатура методов ещё не приняли своего окончательного вида. Представьте, что у класса нет final. Вы спокойно сделали наследника, переопределили часть методов, добавили свои. Потом вышло обновление, где я какой-нибудь из методов сделал private, или добавил обязательных параметров.
И всё, у вас fatal каждый раз при использовании вашего класса. Если это ваш портал с какими-то специфическими доработками - можно оперативно исправить и ущерба будет немного. А вдруг кто-нибудь сделает приложение со своим наследником?
Одна из проблем битрикса, как продукта - это большое legacy. А большое legacy обусловлено политикой максимальной обратной совместимости. Поддержка обратной совместимости замедляет скорость обновления кода.
Проставив final, я, в первую очередь, оставляю себе возможность вносить изменения в классы без оглядки на пользовательский код. Говорю только за себя, но коллеги часто руководствуются этим же принципом.
Если у вас есть какие-то потребности, которые нельзя перекрыть без наследования - пишите, что-нибудь придумаем.
Антон, здравствуйте. На самом деле, есть потребность в генерации нормальных вложенных списков. Если docx предполагает какой-то такой формат:
То есть мы видим, что это параграфы с Paragraph Properties, уровни нумерации, то в Вашей реализации мы видим просто строки с текстом и пробелами как отступ:
Скажите, есть ли в планах приведение списков в более удобный и правильный вид? Как раз столкнулись с генерацией приказов, где регламентирован чуть ли не каждый отступ в документе. Пришлось пока что отложить, решаем что делать, писать некую свою реализацию или Битрикс будет семимильными шагами развивать генератор и стоит подождать. Опять же final запретит отнаследоваться и реализовать свой метод htmlNodeToXml (оно в принципе и к лучшему)
Может быть в течение месяца-двух выйдет (до вас с этим вопросом никто не обращался). Обязательно напишите в техподдержку, чтобы ваше обращение зафиксировали - это может ускорить процесс (там работы немного, будет время - сделаю).
Пока что можно попробовать подвеситься на событие onCreateDocument, в обработчике доставать Body и менять контент файла с помощью регулярных выражений (для поиска нужного места можно расставить метки внутри xml). Это не очень удобно, но должно работать.
Может быть в течение месяца-двух выйдет (до вас с этим вопросом никто не обращался). Обязательно напишите в техподдержку, чтобы ваше обращение зафиксировали - это может ускорить процесс (там работы немного, будет время - сделаю).
Пока что можно попробовать подвеситься на событие onCreateDocument, в обработчике доставать Body и менять контент файла с помощью регулярных выражений (для поиска нужного места можно расставить метки внутри xml). Это не очень удобно, но должно работать.
Здравствуйте, Антон. Вы не могли бы привести пример, как можно в обработчике события onCreateDocument поменять контент файла?
Точно надо напрямую изменить контент файла? Если надо изменить значения полей, то есть событие onBeforeProcessDocument. В обработчике можно вызвать \Bitrix\DocumentGenerator\Document::setValues(). Пример есть в документации https://dev.1c-bitrix.ru/api_d7/bitrix/documentgenerator/customization.php
Если всё-таки нужен именно контент, то алгоритм будет такой: В обработчик события приходит объект \Bitrix\DocumentGenerator\Document. Из него можно достать FILE_ID По этому ид дергаем \Bitrix\DocumentGenerator\Model\FileTable::getContent() Меняем контент, а потом обновляем его методом \Bitrix\DocumentGenerator\Model\FileTable::updateContent()
Антон Горбылев, спасибо. На самом деле, мне нужно вставить на место плейсхолдера таблицу (или любой код, как есть, без его дополнительной обработки).
В первом случае, фильтруются значения полей перед вставкой в документ и от таблицы остается только текст. Во втором случае, в переменную $content попадает не содержимое файла (word/document.xml), а его структура ([Content_Types].xml). Возможно, я что-то не так понял.
Подскажите, пожалуйста, возможно ли плейсхолдер заменять на произвольный код?
Произвольное значение сейчас передать нельзя, перед вставкой дергается \Bitrix\DocumentGenerator\Body\DocxXml::printValue() - там строка подготавливается для вставки в xml.
По поводу работы с контентом - в примере $content - это не содержимое xml-файла, это весь docx файл, который представляет собой zip-архив. Работать с ним напрямую нельзя, надо сначала его распаковать, найти внутри нужный xml и работать уже с ним. Для примеров можно посмотреть \Bitrix\DocumentGenerator\Body\ZipDocument::open() - тут архив распаковывается потом через \ZipArchive::getFromName() - можно получить доступ к xml-файлу и работать с его контентом. После работы надо сохранить содержимое через \ZipArchive::addFromString()
Зачем описание массивов "Items" делать таким громоздким? Может есть вариант попроще и я просто его не увидел? Почему отказались от реализации через обычный массив? Как обстоят дела со вложенными циклами? Есть примеры?
Прямо очень не хватает своих меток как единичных, так и блочных
PS. final - имхо, это почти всегда зло. final не оставляет возможности переиспользовать существующий код. Как результат чтобы, решить ряд задач приходится заниматься копипастой, потому что кто-то за нас подумал и поставил final.
PSS. Как обстоят дела с конвертацией файлов, например docx->pdf НЕ на серверах Битрикса? а скажем если есть установленный локальный libreoffice?
По поводу массивов. Основная сложность - это сопоставление имен полей в таблице и значений в массиве. У ArrayDataProvider есть два поля, которые берутся не из коллекции. Ну и основной момент, что внутри ArrayDataProvider лежит набор других провайдеров, а не просто набор массивов. В вашем коде, кстати, в ITEM_PROVIDER надо использовать HashDataProvider - это простой массив. Там много всего завязано на ArrayDataProvider, работать напрямую с массивами было бы сложнее из-за большого количества проверок в разных местах (чтобы понять, тот это массив или нет). Я за строгую типизацию )
Сейчас вариантов попроще нет. Может потом придумаю какой-то способ, чтобы можно было снаружи передать структуру попроще, как-то её пометить, а внутри я уже сам сформирую объект.
Вложенных циклов сейчас нет. Можно только поиграться со вставкой множественных значений из коллекции с модификатором mseparator. Вот тут есть про него https://helpdesk.bitrix24.ru/open/8087285/. Т.е. у вас может быть коллекция товаров. У товара множественное свойство. Можно вставить в одно место документа все значения этого свойства всех товаров без таблицы. {ProductsProductList~mseparator=2,all=y,mseparator=1} - это выведет на каждый товар новую строку, в каждой строке - все значения свойства через запятую.
Была ещё мысль сделать генерацию таблиц с произвольным набором данных (неизвестное количество столбцов). Но результат получился не очень, не стал делать. Оно ещё осталось в комментариях у класса \Bitrix\DocumentGenerator\Body\DocxXml в самом конце.
Цитата
Mikhail Kryachek написал: Прямо очень не хватает своих меток как единичных, так и блочных
О каких метках речь? Опишите подробнее, подумаю, что можно сделать.
final позволяет мне не задумываться над обратной совместимостью. Это позволяет мне рефакторить код по своему усмотрению. Для разработчиков - да, неудобно. Но если бы после обновления сломалась реализация из-за того, что я изменил сигнатуру, тоже было бы не очень.
Как только дойдут руки, я планирую убрать final с наследников Body, дать возможность задавать свой тип Body в шаблонах. Станет легче - наследуйтесь сколько хотите, парсите файлы самостоятельно. По срокам ничего не скажу. Надеюсь, в этом году.
Цитата
Mikhail Kryachek написал: Как обстоят дела с конвертацией файлов, например docx->pdf НЕ на серверах Битрикса
Планируется, что модуль конвертации файлов transformercontroller уйдёт в коробку в составе виртуальной машины. Можно будет с помощью неё развернуть у себя инфраструктуру и конвертировать. По срокам тоже ничего не скажу, может быть в этом месяце.
1. Не совсем понятно зачем нужны вложенные параметры. Есть какой то жизненный пример?
Цитата
Там много всего завязано на ArrayDataProvider, работать напрямую с массивами было бы сложнее из-за большого количества проверок в разных местах (чтобы понять, тот это массив или нет). Я за строгую типизацию )
Мне пока не видно сложности, но возможно это случилось из-за того, что вы не используете разделители в именах плейсхолдеров? Например, до того как Битрикс создал собственную реализацию у нас был схожий функционал, хотя возможно несколько попроще, однако проблем с массивами не было.
2. На счет final, сейчас вот из-за такой сложной описательной части, мы хотим реализовать свой setValues метод который на вход будет принимать обычный массив и уже внутри пересобирать согласно вашим требованиям. Мы конечно извернулись с помощью кучи волшебных методов и выглядит как будто отнаследовали, но все это как то не очень хорошо
Mikhail Kryachek написал: возможно это случилось из-за того, что вы не используете разделители в именах плейсхолдеров
Поначалу я хотел вставлять в таблицу поля в виде {PRODUCTS.NAME} - и тогда оно работало без этих промежуточных описаний. Но решили, что надо придерживаться единообразия в названиях полей, и пришлось городить такую прослойку, чтобы имена в таблицах были красивыми.
Цитата
Mikhail Kryachek написал: сейчас вот из-за такой сложной описательной части, мы хотим реализовать свой setValues метод который на вход будет принимать обычный массив и уже внутри пересобирать согласно вашим требованиям
А в чем сложность с составлением описаний? У меня была логика такая, что всё описание прячется в провайдере (который можно наследовать), составить его надо только один раз и потом оно просто работает. Если не используете провайдеры, то можно сделать обертку (какой-нибудь BodyFieldsFormatter), который будет переваривать ваш массив в нужный формат.