Создание отчётов с помощью JasperReports

Внимание Задачка повышенной сложности

Предпосылки

В широком смысле, “отчёт” это удобное для просмотра представление некоторых данных, а точнее, сведений. Квитанция об оплате, акт приёма-передачи, договор, спецификация, журнал регистрации, доверенность, школьный классный журнал и прочие документы, которые мы встречаем в повседневной жизни, имеют одно важное сходство – все они сочетают в себе два вида информации: описательную/формальную и информативную/содержательную. Таким образом, процесс создания отчёта сводится к двум задачам:

  1. Создание формы/структуры
  2. Заполнение формы содержанием

Например. Квитанция об оплате: изготавливается бланк по заранее продуманному шаблону, а затем заполняются соответствующие поля. Договор, доверенность и т.п.: составляется шаблон документа (т.н. “рыба”), а затем вносятся изменения/дополнения в нужных местах текста. Задача создания разового отчёта (в самом себе) абсолютно тривиальна, и не требует использования каких-либо особенных технических средств. Но очень часто возникают ситуации, когда одни и те же данные/сведения должны фигурировать в отчётах разного типа. Ну и, разумеется, не стоит забывать о том, что даже изначально разовый отчёт, своей содержательной частью, может служить источником данных для других отчётов.

Итак, для создания отчёта необходимы:

  1. Форма/структура/шаблон
  2. Источник данных
  3. Средство ввода данных
  4. Средство создания отчёта (форма+содержание)

Установка ПО

В качестве подходящего инструмента рассматривается проект “JasperReports” от группы “Jaspersoft Community”.

Перейти на страницу: http://community.jaspersoft.com/download и загрузить “JasperReports Library” (потребуется бесплатная регистрация). Распаковать загруженный архив и собрать проект с помощью “Apache Ant”. Например (Arch Linux):

[orca@blizzard ~]$ sudo pacman -S apache-ant                     <1>
[orca@blizzard ~]$ ant -version                                  <2>
Apache Ant(TM) version 1.9.2 compiled on July 24 2013
[orca@blizzard ~]$ cd /store/Install/Source/jasperreports-5.2.0/ <3>
[orca@blizzard jasperreports-5.2.0]$ ant -p                      <4>
[orca@blizzard jasperreports-5.2.0]$ ant jar                     <5>
[orca@blizzard jasperreports-5.2.0]$ cd ./demo/samples/table     <6>
[orca@blizzard table]$ ant                                       <7>
[orca@blizzard table]$ ls -altr ./build/reports/                 <8>
[orca@blizzard table]$ okular ./build/reports/TableReport.pdf    <9>
[orca@blizzard table]$ firefox ./build/reports/TableReport.html
[orca@blizzard table]$ libreoffice ./build/reports/TableReport.xls
  1. Установка “Apache Ant”
  2. Проверка “Apache Ant”
  3. Подготовка к сборке
  4. Отчёт о задачах сборки
  5. Сборка ПО JasperReports
  6. Выбор примера (для проверки)
  7. Создание отчёта
  8. Список вариантов отчёта
  9. Просмотр вариантов отчёта

Если перечисленные команды были выполнены без ошибок, то можно считать, что библиотека “JasperReports” установлена и работает. Далее можно переходить к созданию собственных отчётов.


Создание отчёта

Жизненный цикл отчёта

В процессе своего создания отчёты “JasperReports” проходят по следующим этапам жизненного цикла:

  1. Разработка и создание шаблона. Шаблон это XML-файл, составленный по особым правилам библиотеки “JasperReports”. По соглашению имеет расширение “jrxml”. Может быть создан вручную в текстовом редакторе (как и любой другой XML-файл), а может быть создан с помощью редактора шаблонов “iReport Designer”
  2. Сборка/компиляция шаблона. Проверка XML-файла шаблона, приведение его к типу “Java serialization data” и сохранение результата в виде файла с расширением “jasper”
  3. Заполнение отчёта данными. Используется собранный на предыдущем этапе шаблон и выборка из источника данных. В качестве источника могут выступать: JDBC, CALS Table Models, XML, XSL, CSV, JavaBeans, EJBQL, Hibernate. Заполненный шаблон сохраняется в файле с расширением “jrprint”
  4. Экспорт отчёта. Привидение его к требуемому читаемому формату (PDF, HTML, XLS, RTF, ODT, CSV, XML, XHTML, DOCX, XLSX)

Сохранение промежуточных результатов в виде файлов “jasper” и “jrprint” позволяет при необходимости создавать отчёт с этих точек, что сэкономит время в процессе промышленного использования системы. Например, если уже есть утверждённый и собранный шаблон отчёта, то процесс можно начинать с этапа заполнения его данными, а если отчёт с введёнными данными нужно сохранить в каком-либо другом формате, то сразу можно переходить к экспорту.

Замечание Строго говоря, отчёты, как таковые, это всё же документы где содержательная часть превалирует над описательной составляющей. В договорах, доверенностях и в других подобных документах содержательная часть (т.е. вводимые данные) идут небольшими вкраплениями в заметно большем тексте (шаблоне) и поэтому в таких применениях намного проще и эффективнее использовать шаблоны и электронные формы обычных офисных программ.Поэтому, если нужна автоматизированная система только для заполнения шаблонов документов и нет необходимости повторного использования введённых данных, то библиотека “JasperReports” не нужна.Однако, если вводимые данные должны использоваться в будущем, то необходимо должным образом сохранить их с тем, чтобы можно было повторно использовать в отчётах “JasperReports”.

Пример отчёта “Лента новостей”

Требуется собрать новостной дайджест в формате pdf.

Загрузка данных

Загрузить файл новостной ленты:

[orca@blizzard ~]$ cd /store/Install/Source/jasperreports-5.2.0/demo
[orca@blizzard demo]$ mkdir -p ./myreports/rss
[orca@blizzard demo]$ cd ./myreports/rss
[orca@blizzard demo]$ curl http://www.vesti.ru/vesti.rss -o "vesti.xml"
[orca@blizzard demo]$ file vesti.xml
vesti.xml: XML document text

Теперь XML-файл “vesti.xml” содержит снимок ленты интернет-газеты “Вести”.

Создание шаблона

Далее необходимо создать шаблон для отчёта “JasperReports”.

Замечание Для лучшего понимания устройства библиотеки “JasperReports” рекомендуется создавать шаблон отчёта вручную. Далее, после ознакомления, проще, конечно же, использовать графические средства, например, “iReport”.

Шаблон отчёта состоит из обязательного корневого элемента:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport   xmlns="http://jasperreports.sourceforge.net/jasperreports"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
                name="VESTI"
                columnCount="3" columnWidth="178" columnSpacing="5"
                leftMargin="20" rightMargin="20" topMargin="10" bottomMargin="10">
</jasperReport>

Здесь можно только заменить имя отчёта на любое подходящее (пробелы в имени не допускаются).

Следующие за “name” атрибуты корневого элемента обязательными не являются:

  • columnCount. Количество колонок. По умолчанию: “1”
  • printOrder. Порядок вывода текста (имеет смысл если кол-во колонок 2 и более). Возможные значения: (Vertical | Horizontal). По умолчанию: “Vertical”
  • pageWidth. Ширина страницы (в пикселах). По умолчанию: “595”
  • pageHeight. Высота страницы (в пикселах). По умолчанию: “842”
  • orientation. Ориентация страницы. Возможные значения: (Portrait | Landscape). По умолчанию: “Portrait”
  • columnWidth. Ширина колонки. По умолчанию: “555”
  • columnSpacing. Расстояние между колонками. По умолчанию: “0”
  • leftMargin. Левое поле. По умолчанию: “20”
  • rightMargin. Правое поле. По умолчанию: “20”
  • topMargin. Верхнее поле. По умолчанию: “30”
  • bottomMargin. Нижнее поле. По умолчанию: “30”
  • whenNoDataType. Поведение при отсутствии данных. Возможные значения: (NoPages | BlankPage | AllSectionsNoDetail). По умолчанию: “NoPages”
  • isTitleNewPage. Размещать или нет раздел заголовка на отдельной странице. Возможные значения: (true | false). По умолчанию: “false”
  • isSummaryNewPage. Размещать или нет итоговый раздел на отдельной странице. Возможные значения: (true | false). По умолчанию: “false”
  • isSummaryWithPageHeaderAndFooter. Добавлять или нет колонтитулы, если итоговый раздел будет на отдельной странице. Возможные значения: (true | false). По умолчанию: “false”
  • isFloatColumnFooter. Размещать или нет нижний колонтитул колонки внизу колонки. Возможные значения: (true | false). По умолчанию: “false”

Первыми в корневом элементе перечисляются стили, которые будут использованы в отчёте. Элемент “style” также имеет богатый перечень атрибутов. Вот некоторые из них:

  • name. Имя стиля. Обязательный атрибут
  • isDefault. Будет ли использован этот стиль “по умолчанию”. Возможные значения: (true | false). По умолчанию: “false”
  • style. Ссылка на родительский стиль
  • mode. Прозрачность. Возможные значения: (Opaque | Transparent)
  • forecolor. Цвет
  • backcolor. Фон
  • pen. Тип линии для графических элементов. Возможные значения: (None | Thin | 1Point | 2Point | 4Point | Dotted) #IMPLIED
  • fill. Тип закраски для графических элементов. Возможные значения: (Solid) #IMPLIED
  • border, topBorder, leftBorder, bottomBorder, rightBorder. Толщина рамки: (None | Thin | 1Point | 2Point | 4Point | Dotted)
  • borderColor, topBorderColor, leftBorderColor, bottomBorderColor, rightBorderColor. Цвет рамки
  • padding, topPadding, leftPadding, bottomPadding, rightPadding. Отступы
  • rotation. Поворот. Возможные значения: (None | Left | Right | UpsideDown)
  • lineSpacing. Междустрочный интервал. Возможные значения: (Single | 1_1_2 | Double)

Кроме этого, в отчёте можно использовать условное форматирование.

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

Параметры объявляются с указанием их имени и класса, т.е. типа объекта. Например:

<parameter name="VESTI" class="java.lang.String"/>

После объявления параметров следует запрос к источнику данных. Вслед за этим, полученные данные должны быть привязаны к “полям”. Например:

<queryString language="xPath"><![CDATA[/rss/channel/item]]></queryString>

<field name="Title" class="java.lang.String">
  <fieldDescription><![CDATA[title]]></fieldDescription>
</field>
<field name="Link" class="java.lang.String">
  <fieldDescription><![CDATA[link]]></fieldDescription>
</field>
<field name="Description" class="java.lang.String">
  <fieldDescription><![CDATA[description]]></fieldDescription>
</field>
<field name="Category" class="java.lang.String">
  <fieldDescription><![CDATA[category]]></fieldDescription>
</field>

С этого момента в шаблоне отчёта начинают перечисляться те его части, которые должны быть выведены на печать.

Как и в других движках создания отчётов, в библиотеке “JasperReports” используется структура из т.н. разделов, а именно:

  • title
  • pageHeader
  • columnHeader
  • groupHeader
  • detail
  • groupFooter
  • columnFooter
  • pageFooter
  • lastPageFooter
  • summary
  • background

Особый интерес представляет раздел “detail”, который прорисовывается для каждой записи источника данных (результата запроса). Раздел должен содержать хотя бы один контейнер для печатаемых элементов. Этот контейнер, называемый связкой (“band”), представляет собой блок, в котором, в свою очередь, перечисляются элементы отчёта.

Возвращаясь к задаче создания новостного дайджеста, получаем следующий шаблон:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport   xmlns="http://jasperreports.sourceforge.net/jasperreports"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
                name="VESTI"
                columnCount="3" columnWidth="178" columnSpacing="5"
                leftMargin="20" rightMargin="20" topMargin="10" bottomMargin="10">

  <style name="Basic" isDefault="true" fontName="FuturisC" fontSize="9"
        isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false"
        isPdfEmbedded ="true"/>
  <style name="Header" isDefault="false" fontName="HeliosCond" fontSize="15"
        isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false"
        isPdfEmbedded ="true" bottomBorder="Thin"/>

  <queryString language="xPath"><![CDATA[/rss/channel/item]]></queryString>

  <field name="Title" class="java.lang.String">
    <fieldDescription><![CDATA[title]]></fieldDescription>
  </field>
  <field name="Link" class="java.lang.String">
    <fieldDescription><![CDATA[link]]></fieldDescription>
  </field>
  <field name="Description" class="java.lang.String">
    <fieldDescription><![CDATA[description]]></fieldDescription>
  </field>
  <field name="Category" class="java.lang.String">
    <fieldDescription><![CDATA[category]]></fieldDescription>
  </field>

  <title>
    <band height="30">
      <staticText>
        <reportElement x="0" y="0" width="575" height="20" style="Header"/>
        <textElement textAlignment="Left">
          <font isBold="true"/>
        </textElement>
        <text><![CDATA[ВЕСТИ]]></text>
      </staticText>
      <staticText>
        <reportElement x="45" y="3" width="300" height="10" style="Basic"/>
        <textElement textAlignment="Left"/>
        <text><![CDATA[Интернет-газета]]></text>
      </staticText>
    </band>
  </title>

<detail>
  <band height="150">
    <textField>
      <reportElement x="0" y="0" width="178" height="25" style="Basic"/>
      <textElement textAlignment="Center">
        <font isBold="true"/>
      </textElement>
      <textFieldExpression class="java.lang.String"><![CDATA[$F{Title}]]></textFieldExpression>
    </textField>

    <textField>
      <reportElement x="0" y="30" width="178" height="15" style="Basic"/>
      <textFieldExpression class="java.lang.String"><![CDATA[$F{Link}]]></textFieldExpression>
    </textField>

    <textField>
      <reportElement x="0" y="45" width="178" height="100" style="Basic"/>
      <textFieldExpression class="java.lang.String"><![CDATA[$F{Description}]]></textFieldExpression>
    </textField>

    <textField>
      <reportElement x="0" y="130" width="178" height="15" style="Basic"/>
      <textFieldExpression class="java.lang.String"><![CDATA[$F{Category}]]></textFieldExpression>
    </textField>
  </band>
</detail>

</jasperReport>

Код Java

Итак, к данному моменту, в директории проекта имеется два XML-файла:

  • vesti.xml – данные с ленты интернет-газеты
  • report.jrxml – шаблон отчёта

Необходимо собрать их вместе, а результат сохранить в файл формата PDF, т.е. необходимо пройти по трём этапам жизненного цикла отчёта:

  1. Сборка/компиляция шаблона. Результат: файл “report.jasper”
  2. Заполнение отчёта данными. Результат: файл “report.jrprint”
  3. Экспорт отчёта. Результат: файл “report.pdf”

Код Java-программы:

import java.util.HashMap;
import java.util.Map;

import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;

import net.sf.jasperreports.engine.fonts.*;

import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;

import net.sf.jasperreports.engine.data.JRXmlDataSource;

import org.w3c.dom.Document;

public class RSSReport {
  public static void main(String[] args) {
    try {
      JasperCompileManager.compileReportToFile("report.jrxml");

      Document document = JRXmlUtils.parse(JRLoader.getLocationInputStream("vesti.xml"));
      Map params = new HashMap();

      params.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT, document);

      JasperFillManager.fillReportToFile("VESTI.jasper", params);

      JasperExportManager.exportReportToPdfFile("VESTI.jrprint");

    } catch (JRException e) {
      e.printStackTrace();
    }
  }
}

Шрифты

“Русский букварь и PDF” – это всегда непросто. Но лучше один раз побороть эту проблему, чтобы потом в своих PDF-отчётах любоваться красивыми и интересными шрифтами с поддержкой “великаго и могучаго”. Процедура добавления нестандартных шрифтов выполняется в два этапа:

  1. Сборка JAVA-архива нестандартных шрифтов
  2. Добавление в этот архив своих шрифтов

JAVA-архив (JAR-файл) для поддержки нестандартных шрифтов:

[orca@blizzard ~]$ cd /store/Install/Source/jasperreports-5.2.0/        <1>
[orca@blizzard jasperreports-5.2.0]$ ant -p                             <2>
...
fonts   Builds the JAR containing the JasperReports default font extension
...
[orca@blizzard jasperreports-5.2.0]$ ant fonts                          <3>
[orca@blizzard jasperreports-5.2.0]$ ls -al ./dist/                     <4>
...
jasperreports-fonts-5.2.0.jar
  1. Переход в директорию с исходными кодами JasperReports
  2. Отчёт о задачах сборки. Убедиться в том, что есть задача “fonts”
  3. Сборка jasperreports-fonts
  4. Проверка наличия файла с jasperreports-fonts

Далее, распаковать этот архив (как ZIP), перейти во вложенную директорию net/sf/jasperreports/fonts и записать туда файлы TTF со шрифтами, которые будут использоваться в отчётах. В этой же директории найти файл fonts.xml открыть и отредактировать его, перечислив должным образом добавленные файлы, например:

<fontFamily name="CourierC">
  <normal>net/sf/jasperreports/fonts/courierc.ttf</normal>
  <bold>net/sf/jasperreports/fonts/couriercbd.ttf</bold>
  <italic>net/sf/jasperreports/fonts/courierci.ttf</italic>
  <boldItalic>net/sf/jasperreports/fonts/couriercbi.ttf</boldItalic>
  <pdfEncoding>Identity-H</pdfEncoding>
  <pdfEmbedded>true</pdfEmbedded>
  <exportFonts>
    <export key="net.sf.jasperreports.html">'CourierC', 'Courier New', Courier, monospace</export>
    <export key="net.sf.jasperreports.xhtml">'CourierC', 'Courier New', Courier, monospace</export>
  </exportFonts>
</fontFamily>

<fontFamily name="FuturisC">
  <normal>net/sf/jasperreports/fonts/futurisc.ttf</normal>
  <bold>net/sf/jasperreports/fonts/futuriscbd.ttf</bold>
  <italic>net/sf/jasperreports/fonts/futurisci.ttf</italic>
  <boldItalic>net/sf/jasperreports/fonts/futuriscbi.ttf</boldItalic>
  <pdfEncoding>Identity-H</pdfEncoding>
  <pdfEmbedded>true</pdfEmbedded>
  <exportFonts>
    <export key="net.sf.jasperreports.html">'FuturisC', Arial, Helvetica, sans-serif</export>
    <export key="net.sf.jasperreports.xhtml">'FuturisC', Arial, Helvetica, sans-serif</export>
  </exportFonts>
</fontFamily>

<fontFamily name="HeliosCond">
  <normal>net/sf/jasperreports/fonts/helioscond.ttf</normal>
  <bold>net/sf/jasperreports/fonts/helioscondbd.ttf</bold>
  <italic>net/sf/jasperreports/fonts/helioscondi.ttf</italic>
  <boldItalic>net/sf/jasperreports/fonts/helioscondbi.ttf</boldItalic>
  <pdfEncoding>Identity-H</pdfEncoding>
  <pdfEmbedded>true</pdfEmbedded>
  <exportFonts>
    <export key="net.sf.jasperreports.html">'HeliosCond', Arial, Helvetica, sans-serif</export>
    <export key="net.sf.jasperreports.xhtml">'HeliosCond', Arial, Helvetica, sans-serif</export>
  </exportFonts>
</fontFamily>

Переместить оригинальный JAR-файл (чтобы была копия), а директорию упаковать как ZIP-файл указав “jar” в качестве расширения имени файла.

Сборка программы

Итак, в наличии три файла. К “vesti.xml” и “report.jrxml” добавляется файл “RSSReport.java” с JAVA-кодом.

Перейти в директорию проекта, установить значение переменной окружения CLASSPATH, собрать и запустить программу:

[orca@blizzard ~]$ cd /store/Install/Source/jasperreports-5.2.0/demo/myreports/rss

[orca@blizzard rss]$ export CLASSPATH=/store/Install/Source/jasperreports-5.2.0/dist/jasperreports-5.2.0.jar:/store/Install/Source/jasperreports-5.2.0/lib:/store/Install/Source/jasperreports-5.2.0/lib/commons-logging-1.1.1.jar:/store/Install/Source/jasperreports-5.2.0/lib/commons-digester-2.1.jar:.:/store/Install/Source/jasperreports-5.2.0/lib/commons-collections-2.1.1.jar:/store/Install/Source/jasperreports-5.2.0/lib/commons-beanutils-1.8.0.jar:/store/Install/Source/jasperreports-5.2.0/lib/xalan-2.7.1.jar:/store/Install/Source/jasperreports-5.2.0/lib/iText-2.1.7.js2.jar:/store/Install/Source/jasperreports-5.2.0/dist/jasperreports-fonts.jar
[orca@blizzard rss]$ ls
report.jrxml RSSReport.java  vesti.xml
[orca@blizzard rss]$ javac RSSReport.java
[orca@blizzard rss]$ java RSSReport

Особое внимание следует уделить переменной окружения CLASSPATH. Ниже перечислен список путей к файлам/директориям, которые должны быть перечислены в этой переменной:

  • /store/Install/Source/jasperreports-5.2.0/dist/jasperreports-5.2.0.jar
  • /store/Install/Source/jasperreports-5.2.0/dist/jasperreports-fonts.jar
  • /store/Install/Source/jasperreports-5.2.0/lib
  • /store/Install/Source/jasperreports-5.2.0/lib/commons-logging-1.1.1.jar
  • /store/Install/Source/jasperreports-5.2.0/lib/commons-digester-2.1.jar
  • /store/Install/Source/jasperreports-5.2.0/lib/commons-collections-2.1.1.jar
  • /store/Install/Source/jasperreports-5.2.0/lib/commons-beanutils-1.8.0.jar
  • /store/Install/Source/jasperreports-5.2.0/lib/xalan-2.7.1.jar
  • /store/Install/Source/jasperreports-5.2.0/lib/iText-2.1.7.js2.jar
  • . (указание на домашнюю директорию)

Итак, если все завершилось без ошибок, то результатом будет PDF-файл с текстом в три колонки. Отчёт, как видно, не идеальный: оформление довольно простое; в тех частях, где нет данных присутствует запись “null”; нет нумерации, колонтитулов и прочих структурных элементов, но процесс налажен и работает, и есть базовые понятия о принципах работы библиотеки “JasperReports”.

Заключение

В какой-то момент времени может показаться, что использование библиотеки “JasperReports” напоминает “стрельбу из пушки по воробьям”, но стоит только поглубже изучить её глубочайшие возможности, как все кажущиеся сложности отойдут на второй план.

Что же дальше? А дальше: освоить работу с базами данных; получить данные из нескольких источников; научиться пользоваться условным форматированием и группировкой; установить и разобраться с “iReport”.