YARG — open-source библиотека для генерации отчётов

Практически каждый разработчик, создающий информационные системы, сталкивается с необходимостью формирования различных отчетов и печатных форм. Это характерно и для большинства приложений разработанных на нашей платформе. Например, в системе, над которой я работаю в настоящее время, их 264. Для того чтобы не писать каждый раз логику формирования отчетов с нуля, мы разработали специальную библиотеку (под катом будет объяснено, почему нам не подошли существующие). Называется она YARG — Yet Another Report Generator.YARG позволяет: Генерировать отчет в формате шаблона или конвертировать результат в PDF; Создавать шаблоны отчетов в привычных и распространенных форматах: DOC, ODT, XLS, DOCX, XLSX, HTML; Создавать сложные XLS и XLSX шаблоны: с вложенными областями данных, графиками, формулами и т.д.; Использовать в отчетах изображения и HTML-разметку; Хранить структуру отчетов в формате XML; Запускать standalone приложение для генерации отчетов, что делает возможным использование библиотеки вне Java-экосистемы (например для генерации отчетов в PHP); Интегрироваться с IoC-фреймворками (Spring, Guice). Эта библиотека используется в платформе CUBA в качестве основы для движка отчетов. Мы развиваем ее с 2010 года, но совсем недавно решили сделать ее открытой, и выложили ее код на GitHub с лицензией Apache 2.0.Данная статья призвана привлечь к ней внимание сообщества.В основе библиотеки лежит простая идея разделения выборки данных и отображения данных в готовый отчет (data layer & presentation layer). Выборка данных описывается разнообразными скриптами, а отображение данных настраивается прямо в документах-шаблонах. При этом, для того, чтобы создать шаблон не требуются специальные средства, достаточно иметь под рукой Open Office или Microsoft Office.

Отчет состоит из так называемых полос. Полоса одновременно является и набором данных и областью в шаблоне, куда эти данные отображаются (связывает data layer и presentation layer).

Рассмотрим для начала пример в стиле Hello World.

Очень простой примерПредставим, что у нас есть фирма и нам нужно вывести список всех сотрудников фирмы, с указанием должности сотрудника.Мы создаем полосу отчета c именем Staff, в которой указываем, что данные загружаются SQL запросом select name, surname, position from staff Java code ReportBuilder reportBuilder = new ReportBuilder (); ReportTemplateBuilder reportTemplateBuilder = new ReportTemplateBuilder () .documentPath (»/home/haulmont/templates/staff.xls») .documentName («staff.xls») .outputType (ReportOutputType.xls) .readFileFromPath (); reportBuilder.template (reportTemplateBuilder.build ()); BandBuilder bandBuilder = new BandBuilder (); ReportBand staff= bandBuilder.name («Staff») .query («Staff», «select name, surname, position from staff», «sql») .build (); reportBuilder.band (staff); Report report = reportBuilder.build ();

Reporting reporting = new Reporting (); reporting.setFormatterFactory (new DefaultFormatterFactory ()); reporting.setLoaderFactory ( new DefaultLoaderFactory ().setSqlDataLoader (new SqlDataLoader (datasource)));

ReportOutputDocument reportOutputDocument = reporting.runReport ( new RunParams (report), new FileOutputStream (»/home/haulmont/reports/staff.xls»)); Далее мы создаем xls-шаблон, в котором отмечаем именованный регион Staff и расставляем алиасы в ячейках.0ce9575b899934c0d6645e77531055d4.jpgПримеры посложнее рассматриваются ниже.Немного истории Несколько лет назад у нас возникла потребность в массовом создании отчетов в одном из наших проектов. Нужно было создавать отчеты в формате XLS и DOC, а также конвертировать результат из DOC и XLS в PDF. Нам требовалось, чтобы библиотека: позволяла создавать отчеты (по крайней мере, шаблоны отчетов) обычным пользователям; поддерживала загрузку данных из различных источников; поддерживала различные форматы шаблонов (XLS, DOC, HTML); поддерживала конвертацию отчетов в PDF; была расширяемой (позволяла быстрое добавление новых способов загрузки данных и новых форматов шаблонов); легко встраивалась в различные IoC контейнеры. Сначала мы пытались использовать JasperReports, но он, во-первых, не умеет создавать DOC отчеты (есть платная библиотека для этого), во-вторых, его возможности по генерации XLS отчетов сильно ограничены (не получится использовать графики, формулы, форматы ячеек), и, в-третьих, создание шаблонов требует определенного навыка и специальных инструментов, а для описания загрузки данных нужно писать Java-код. Существовало также много библиотек, концентрирующихся на каком-то конкретном формате, но единой библиотеки мы не нашли.Поэтому мы решили создать механизм, позволяющий единообразно описывать отчеты, независимо от типа шаблона и способа загрузки данных.Первые шаги Для работы с XLS уже тогда существовало много разных библиотек (POI-HSSF, JXLS и т.д.) и было решено использовать Apache POI, как самую на тот момент популярную. А вот для работы с DOC файлами такого разнообразия не наблюдалось. Вариантов было совсем немного: использовать UNO Runtime — API для интеграции с сервером Open Office или работать с DOC файлами через COM- объекты. Проект POI-HWPF тогда находился в зачаточном состоянии (недалеко ушел он и сейчас). Мы решили использовать интеграцию с Open Office, потому что увидели много положительных отзывов от людей, которые успешно интегрировались с Open Office на совершенно разных языках (Python, Ruby, C#).Если с POI-HSSF все было более или менее просто (за исключением полного отсутствия возможности работы с графиками), то с UNO Runtime нам пришлось испытать множество проблем.Отсутствует внятный API для работы с таблицами. Например, чтобы скопировать строку таблицы, нужно использовать системный буфер обмена (выделяя строку, копируя и вставляя ее в нужное место). Для каждой генерации отчета порождается процесс Open Office (и уничтожается после печати). Изначально мы использовали библиотеку bootstrapconnector для порождения процессов, но вскоре убедились, что во многих случаях она оставляет процесс в живых (в зависшем состоянии) или вообще не пытается процесс завершить, что приводило к коллапсу системы через какое-то время. Нам пришлось переписать логику запуска и уничтожения процессов Open Office, воспользовавшись наработками ребят, написавших jodconverter. UNO Runtime (и сам Open Office сервер) имеет проблемы с потокобезопасностью, из-за чего под нагрузкой сервер может зависнуть или внезапно остановиться из-за внутренней ошибки. Это привело к тому, что пришлось делать механизм повторного запуска отчетов (если отчет не напечатался — попробовать напечатать его еще раз). Это, естественно, сказывается на скорости работы данного вида отчетов. Docx4j Долгое время мы использовали только XLS и DOC шаблоны, но затем было решено поддержать также XLSX и DOCX. Выбор пал на библиотеку DOCX4J, которая к тому времени набрала популярность.Важным достоинством этой библиотеки для нас стало то, что она предоставляет низкоуровневый доступ к структуре документа (фактически оперируя с XML). С одной стороны это несколько усложнило код и логику, а с другой открыло почти безграничные возможности по управлению документом, так как любые операции над ним были теперь возможны.Еще более серьезным достоинством стала возможность отказаться от запуска Open Office для генерации DOCX-отчетов.Пример посложнее Представим, что у нас есть книжный магазин. Давайте попробуем с помощью нашей библиотеки сделать отчет, выводящий в XLS список магазинов и список книг проданных в каждом из магазинов.Представим также, что мы (владельцы магазина) совсем не знаем язык программирования Java, но на наше счастье наш системный администратор знаком с SQL, и у нас даже есть база данных, содержащая информацию обо всех продажах.В первую очередь давайте создадим шаблон отчета в формате xls. Сразу отметим полосы отчета с помощью именованных регионов.e1dc283728ca8e2cc5efca2f3f6633f9.jpgЗатем опишем загрузку данных с помощью SQL. select shop.id as «id», shop.name as «name», shop.address as «address» from store shop

select book.author as «author», book.name as «name», book.price as «price», count (*) as «count» from book book where book.store_id = ${Shop.id} group by book.author, book.name, book.price Теперь мы должны описать отчет с помощью XML.