Статьи

Eclipse’s BIRT: динамический отчет

мотивация

BIRT Eclipse использует XML-файлы для описания макета отчета, которые имеют суффиксы, такие как: rptdesign, rptlibrary, rpttemplate. Файлы rptdesign, непосредственно используемые для генерации отчетов, создаются для любой новой структуры отчета. Когда проект генерирует много разных отчетов, число файлов проекта быстро увеличивается, а значит, управлять ими намного труднее. Вот почему этой ситуации следует избегать. Реализация этой идеи не является интуитивно понятной, если вы новичок в BIRT и следующим шагом для ее реализации является поиск новой информации о ней.

вдохновение

В книге о затмениях BIRT «BIRT: Полевое руководство по отчетности» (
ISBN-10: 0321733584 ISBN-13: 978-0321733580) дано
хорошее объяснение того, как организовать архитектуру отчетности, когда проект отчета более сложен и основываться на многих файлах, таких как rptlibraries и rpttemplates.

Предложение было использовать различные библиотеки, которые сгруппированы в зависимости от цели, для которой они были использованы. Одна группа rptlibraries должна содержать только общие элементы, которые используются из любого отчета. Другая группа rptlibrary состоит из элементов, которые являются специфическими для отчета. Окончательный результат состоит в том, что файл rptdesign содержит элементы из общего и специфического для проекта rptlibraries.

Это описание архитектуры дало мне идею разделить элементы на общие и конкретные, идея, которая мне понадобилась для последующих шагов и конечной цели.

Реализация

API функционал

Тестирование функциональности API Desing Engine Я нашел несколько устаревших методов, которые использовались для копирования элементов отчета из загруженного объекта rptlibrary в новый созданный объект дизайна отчета. Таким образом, я мог бы создать объект reportdesign во время выполнения и сначала загрузить содержимое из библиотеки по умолчанию, в которой находились общие элементы отчета, а затем, если необходимо, переопределить элементы отчета специфичными для тестов из тестовой библиотеки rptlibrary.

Состав

Для этого требовалась строго определенная структура отчетов. Каждый раздел имеет свою собственную главную страницу и элемент сетки в качестве корневого элемента, названного по смыслу для лучшей связи, так как имена используются в качестве идентификаторов, и они часто используются для программирования составления отчетов.

подмена

Была создана одна библиотека для раздела общего отчета и множество библиотек для особых случаев. Раздел был интерпретирован в библиотеке отчетов как сетка, то есть раздел титульной страницы отчета — это элемент сетки, также страница toc и так далее. Имя сетки — это также идентификаторы, которые используются для целей макетирования при программировании. Конкретная библиотека или новые разделы в определенных библиотеках определяются в случае необходимости.

Если в одном отчете есть раздел, который отличается от того же раздела стандартной библиотеки rptlibrary, тогда весь раздел будет заменен. Таким образом, только для различий между нужным разделом и разделом по умолчанию будут созданы новые rptlibraries, а значит, меньше rptlibraries для управления.

Простой графический пример того, как замена должна быть:

(имеет 4 секции) (имеет 1 секцию)
библиотека, специфичная для стандартной библиотеки

Раздел 1 — Сетка A
Раздел 2 — Сетка B
Раздел 3 — Сетка C <— замещать —> Раздел 3 Сетка C
Раздел 4 — Сетка D

Описание

В этой части статьи будет описан один пример реализации.

Программа построена следующим образом:

  • создание объекта отчета
  • скопировать части библиотеки по умолчанию в объект дизайна отчета
    • источник данных
    • набор данных oda
    • главная страница
    • ручка сетки
  • заменить части отчета по умолчанию
    • источник данных
    • набор данных oda
    • главная страница
    • ручка сетки

Основной процесс программы может выглядеть так:

// creating the secton
SessionHandle session = DesignEngine.newSession(null);
// creating the report design
ReportDesignHandle reportDesignHandle = session.createDesign();
// loading the library from file system into object
LibraryHandle defaultLibraryHandle = session.openLibrary(getLibraryPath(reportTypeEnum.DEFAULT));
// setting the elements from the library object to the design object
setDefaultLibraryElements(reportDesignHandle, defaultLibraryHandle);
// binding the report design to the return value
design = engine.openReportDesign(reportDesignHandle);
// if the report type is not the default then subtitute the sections
if (!ReportTypeEnum.DEFAULT.equals(reportTypeEnum)) {
    setExtraLibraryElements(reportTypeEnum, reportDesignHandle);
}
// order the sections
orderSections(reportDesignHandle);

Установка набора данных по умолчанию для объекта дизайна отчета может выглядеть следующим образом (из метода setDefaultLibraryElements):

 for (Object defaultScriptDataSetHandle : defaultLibraryHandle.getDataSets().getContents()) {
     if (defaultScriptDataSetHandle instanceof OdaDataSetHandle) {
         try {
             reportDesignHandle.getDataSets().add((OdaDataSet)((OdaDataSetHandle)defaultScriptDataSetHandle).copy());
         } catch (ContentException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (NameException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
    }
 }

Если вы используете Scripted DataSet, то его следует использовать вместо ODA DataSet.

Пример замены сетки по умолчанию специальной сеткой может быть закодирован так:

    private static void replaceCopyComponents(
            ReportDesignHandle reportDesignHandle,
            LibraryHandle extratLibraryHandle) {
        try {
            for (Object extraGridHandleObject : extratLibraryHandle.getComponents().getContents()) {
                if (extraGridHandleObject instanceof GridHandle) {
                    for (Object defaultGridHandleObject : reportDesignHandle.getBody().getContents()) {
                        if (extraGridHandleObject instanceof GridHandle) {
                            if (
                                ((GridHandle)extraGridHandleObject).getName().equals( ((GridHandle)defaultGridHandleObject).getName())
                                ) {
                                    int pos = reportDesignHandle.getBody().findPosn((DesignElementHandle)(defaultGridHandleObject));
                                    reportDesignHandle.getBody().drop((DesignElementHandle)defaultGridHandleObject);
                                    reportDesignHandle.getBody().add((DesignElement)((GridHandle)extraGridHandleObject).copy(), pos);
                                    extratLibraryHandle.getComponents().drop((DesignElementHandle)extraGridHandleObject);
                            }
                        }
                    }
                }
            }
            for (Object extraGridHandleObject : extratLibraryHandle.getComponents().getContents()) {
                if (extraGridHandleObject instanceof GridHandle) {
                        reportDesignHandle.getBody().add((DesignElement)((GridHandle)extraGridHandleObject).copy());
                }
            }
        } catch (SemanticException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

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

private static void orderSections(ReportDesignHandle reportDesignHandle) {
        List<String> gridSectionNames = Arrays.asList("ZeroSectionGrid",
                "FirstSectionGrid", "SecondSectionGrid");
       
        Map<String, DesignElement> map = new HashMap<String, DesignElement>();
        for (Object bodyContent : reportDesignHandle.getBody().getContents()) {
            if (bodyContent instanceof GridHandle) {
                GridHandle gridHandle = (GridHandle)bodyContent;
                String name = gridHandle.getName();
                DesignElement designElement = (DesignElement)gridHandle.copy();
                map.put(name, designElement);
            }
        }
       
        for (int i = 0; i < gridSectionNames.size(); i++) {
            if (map.containsKey(gridSectionNames.get(i))) {
                try {
                    reportDesignHandle.findElement(gridSectionNames.get(i)).drop();
                    reportDesignHandle.getBody().add(map.get(gridSectionNames.get(i)), i);
                } catch (SemanticException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }       
    }

GitHub

Размещение всего кода здесь выглядит не очень хорошо, поэтому я разместил его на github [ref] . Исходный код проекта «динамические отчеты» об этой статье.

Пример был сделан с ранней версией Eclipse Luna IDE, использующей maven для библиотечных зависимостей.

Резюме

В этой статье описывается один из способов создания динамических отчетов с использованием простого способа каскадных разделов отчетов. Другое название, которое я хотел дать этой статье, было «другая архитектура», потому что оно описывает другой способ создания отчетов. Теперь, зная решение моей задачи, я могу сказать, что этот подход не сложен, если вы хорошо знаете DE API, если вы знаете методы, которые выполняют процесс копирования. Знание всего этого было мотивом для написания этой статьи.