Ausgleichen des inkonsistenten Ladens von Daten am Seitenende in JasperReports – Teil 2

JasperReports ist ein leistungsstarkes Werkzeug zur Erstellung anspruchsvoller Berichte in zahlreichen Zielformaten auf der Grundlage unterschiedlichster Datenquellen für verschiedene Zielsysteme. Dieser Blog-Beitrag befasst sich mit einem Problem, welches bei der Erstellung von mehrseitigen Berichten auftritt. Genauer gesagt: JasperReports zeigt inkonsistentes Verhalten beim Laden des letzten Datensatzes am Ende einer Seite.

Der erste Teil dieses Beitrags hat das grundlegende Problem skizziert und erläuterte zwei einfache, aber nicht zufriedenstellende Lösungen. Dieser zweite Teil demonstriert eine kompliziertere, aber voll funktionsfähige Lösung.

Lösung 3: Kompliziert, aber voll funktionsfähig

Für wichtige Dokumente (z.B. Rechnungen) sind Fehler wie die im ersten Teil dieses Beitrags beschriebenen – auch wenn ihre Wahrscheinlichkeit durch die zweite Lösung verringert wird – nicht akzeptabel. Diese dritte Lösung verwendet eine Reihe von Variablen, um einen Mechanismus zur Unterscheidung zwischen dem ersten und dem zweiten Fall, der am Ende einer Seite auftreten kann, bereitzustellen. Natürlich handelt es sich auch hier nur um eine Abhilfe, die ihre Grenzen hat und die Erfüllung einiger Voraussetzungen erfordert.

Die Voraussetzungen sind recht simpel:

  1. Alle Felder im Detailband müssen eine feste Höhe haben. Dies kann erfüllt werden, indem man den Felder die Möglichkeit nimmt sich nach Belieben zu vergrößern.
  2. Der für das Detailband verfügbare Platz auf der Seite muss bekannt sein. Dies kann entweder erfüllt werden, indem man den Feldern in anderen Bändern die Möglichkeit nimmt sich zu vergrößern oder indem man den Bänder selbst diese Möglichkeit nimmt.

Der Einfachheit halber wurde in dem hier skizzierten Szenario von der zweiten Möglichkeit zur Erfüllung der zweiten Voraussetzung Gebrauch gemacht. Auch die erste Voraussetzung wurde vereinfacht, indem zusätzlich alle Felder auf dieselbe Höhen gesetzt wurden, sodass wir die Berechnungen mit einer Anzahl von Zeilen statt mit Pixelwerten durchführen konnten. Die Lösung kann auch für unterschiedliche Höhen der Felder adaptiert werden.

Variablen in JasperReports besitzen ein ziemlich komplexes Verhalten, daher wird dazu geraten die Dokumentation (in englischer Sprache) und/oder den Wiki-Eintrag (in englischer Sprache) der Jaspersoft-Community über Variablen zu lesen, um diese Lösung vollständig zu verstehen.

Die kurze Version des ziemlich komplexen Verhaltens lautet: Variablen haben einen Anfangswert, mehrere berechnete Werte, eine Berechnungsart, eine Inkrementart und eine Rücksetzart. Eine Variable wird zu Beginn des Berichts und beim Zurücksetzen je nach Rücksetzart (z.B. am Anfang einer Seite) auf ihren Anfangswert gesetzt. Eine Variable wird über ihren Ausdruck und ihre Berechnungsart (z.B. Summe) in Abhängigkeit von ihrer Inkrementart (z.B. am Ende jeder Seite) berechnet oder, falls keine Inkrementart angegeben ist, für jeden Artikel berechnet. Da Variablen in Abhängigkeit von der Inkrementart berechnet werden, haben sie keinen Auswertungszeitpunkt im klassischen Sinne von JasperReports; wenn der Wert einer Variable in einem Textfeld verwendet wird, wird der richtige Wert in Abhängigkeit vom Auswertungszeitpunkt des Feldes ausgewählt.

Bei dieser Lösung hat das Zwischensummen-Textfeld die Einstellung now als Auswertungszeit und werden im Column Footer platziert. Da JasperReports den Inhalt von Column Footer (und anderen Bänder) auswerten muss, um zu bestimmen, ob das aktuell geladene Element auf die nächste Seite verschoben werden muss, werden die im Column Footer verwendeten Variablen für jeden in den Speicher geladenen Datensatz ausgewertet, bevor dieser auf der Seite platziert wird.

Jetzt wo diese Punkte erklärt sind, sind wir bereit, die erforderlichen Variablen zu definieren!

Die Variablen

availableLinesReport

Class Integer
Expression 25 + ($V{PAGE_NUMBER} – 1) * 35
Initial value -1

Diese Variable gibt die Anzahl der verfügbaren Zeilen im gesamten Bericht an. Auf der ersten Seite stehen 25 Zeilen für das Detailband zur Verfügung, auf der zweiten und allen weiteren Seiten jeweils 35 Zeilen, da diese Seiten das Titelband nicht enthalten (im Beispiel 10 Zeilen). Der Ausdruck sollte ohne weitere Erklärungen verständlich sein.

usedLinesReport

Class Integer
Calculation Sum
Expression 1 + ($F{DETAIL2} != null ? 1 : 0) + ($F{DETAIL3} != null ? 1 : 0) + ($F{DETAIL4} != null ? 1 : 0) + ($F{DETAIL5} != null ? 1 : 0) + 1
Initial value -1
Reset type Report

Diese Variable gibt die Anzahl der Zeilen an, die bisher im Bericht verwendet wurden. Für jede mögliche Detailinformation wird evaluiert, ob sie für einen Artikel verwendet wird, und die Variable um eins erhöht, wenn dies der Fall ist. Die erste Detailinformation liegt in der ersten Zeile des Artikels, welche immer gedruckt wird, daher ist hier keine Bedingung erforderlich. Natürlich könnte man die Konditionen so anpassen, dass leere Artikel von dieser Zählvariablen ausgeschlossen werden. Am Ende zählen wir eine zusätzliche Zeile pro Artikel, um den beabsichtigten Leerraum nach jedem Artikel zu berücksichtigen. Diese Variable wird für jeden Artikel aufgerufen und mit Hilfe der vordefinierten Berechnungsfunktion über den gesamten Bericht aufsummiert.

usedLinesItem

Class Integer
Expression 1 + ($F{DETAIL2} != null ? 1 : 0) + ($F{DETAIL3} != null ? 1 : 0) + ($F{DETAIL4} != null ? 1 : 0) + ($F{DETAIL5} != null ? 1 : 0) + 1
Initial value -1

Diese Variable gibt die Anzahl der Zeilen an, die pro Artikel verwendet werden. Der Ausdruck entspricht dem der vorherigen Variable, aber es ist keine Berechnungsfunktion ausgewählt, daher entspricht der zurückgegebene Wert immer dem zum Zeitpunkt der Auswertung gültigen tatsächlichen Wert (in diesem Szenario wird die Variable für jeden Datensatz ausgewertet).

remainingLinesPage

Class Integer
Expression $V{usedLinesReport} + $V{remainingLinesReport} > $V{availableLinesReport} ? $V{usedLinesItem} – (($V{usedLinesReport} + $V{remainingLinesReport}) – $V{availableLinesReport}) : -1
Initial value -1
Reset type [Group] Item

Diese Variable gibt die Anzahl der Zeilen auf einer Seite an, die leer bleiben. Wenn die Anzahl der bereits in einem Bericht verwendeten Zeilen (abhängig vom aktuellen Artikel) zusammen mit der Anzahl der Zeilen, die auf allen Seiten des Berichts leer geblieben sind (abhängig vom vorherigen Artikel, aufgrund der Reihenfolge dieser Variablen im Bericht, siehe auch nächste Variable), die Anzahl der aktuell verfügbaren Zeilen (abhängig von der aktuellen Seitenzahl) übersteigt, wissen wir, dass der zuletzt in den Speicher eingelesene Artikel für diese Seite zu lang war. Wenn dies der Fall ist, können wir die Anzahl der Zeilen, die auf der Seite leer bleiben, berechnen, indem wir die überschüssige Zeilenzahl von der Zeilenzahl des Artikels subtrahieren. Diese Anzahl muss berechnet und zu der Anzahl der im Bericht verwendeten Zeilen addiert werden, da diese Zeilen leer bleiben. In jedem anderen Fall setzen wir die Variable auf -1 zurück, weil entweder die Anzahl der auf dieser Seite leer bleibenden Zeilen noch nicht bestimmt werden kann oder gleich Null ist, beide Fälle werden in dieser Lösung gleich behandelt. Um ein explizites Zurücksetzen der Variable am Anfang jedes Artikels zu gewährleisten, wurde im Breicht eine Pseudogruppe erstellt, die genau einen Artikel umschließt.

remainingLinesReport

Class Integer
Calculation Sum
Expression $V{remainingLinesPage} > 0 ? $V{remainingLinesPage} : 0
Initial value -1
Reset type Report

Diese Variable gibt die Anzahl der Zeilen an, die auf allen Seiten des Berichts insgesamt leer geblieben sind. Dies geschieht, indem die Ergebnisse der vorhergehenden Variable summiert werden, wenn sie nicht negativ sind. Negative Werte entsprechen der Tatsache, dass die Seite noch nicht fertig war; Nullwerte, die einer vollständig gefüllten Seite entsprechen würden, sind für diese Summe irrelevant. Diese Variable wird in der vorhergehenden Variable verwendet, um leere Zeilen als verwendete Zeilen zu berücksichtigen.

overfullPage

Class Boolean
Expression $V{remainingLinesPage} > 0
Initial value 0
Reset type [Group] Item

Damit die Tatsache, dass „der zuletzt in den Speicher geladene Datensatz nicht auf die Seite passte“, leichter in PrintWhenExpressions und anderen Konditionen verwendet werden kann, wurde diese Varibale eingeführt. Sie wird auf true gesetzt, wenn die Anzahl der verbleibenden Zeilen auf einer Seite positiv ist. Eine negative Zahl entspricht der Tatsache, dass die Seite noch nicht fertig war, ein Nullwert entspricht einer vollständig gefüllten Seite. In beiden Fällen war die Seite nicht überfüllt.

Wie die Variablen zusammen spielen

Anstelle eines weiteren langen Absatzes über die Werte, die die Variablen in verschiedenen Situationen haben, werden die Beispielgrafiken aus dem ersten Teil dieses Beitrags erneut verwendet und zeigen nun auch die Variablenwerte in Anmerkungen. Wie man sehen kann, hat die overfullPage Variable nur für den letzten Eintrag auf einer Seite den Wert true, wenn dieser auf die nächste Seite verschoben werden musste. Die Variable kann daher verwendet werden, um zu entscheiden, ob subtotal_incl oder subtotal_excl den korrekten Wert für die Zwischensumme enthält.

Dieses Problem und seine Lösung zeigen einmal mehr, dass die automatisierte Berichterstellung nicht immer eine einfache Aufgabe ist – selbst dann nicht, wenn man ein Expertenwerkzeug wie JasperReports verwendet und die Ausgabe auf ein einziges Format beschränkt. Sie zeigen aber auch, dass es für jedes Problem eine individuelle Lösung gibt! Unser Expertenteam freut sich darauf, Sie bei jedem Schritt auf dem Weg zu einem anspruchsvollen Bericht für Ihre Daten zu unterstützen.

Prodato verbindet.

Autor

Felix Lammermann
Senior Consultant

felix.lammermann@prodato.de