Report goes Programm
Um eine JRXML-Datei ins Programm zu packen, gibt es mehrere Möglichkeiten.
Compile durch das Programm
Hier liegt nur die JRXML-Datei vor, das Programm compiliert selbst. Das Codefragment ist dieses:
net.sf.jasperreports.engine.JasperReport js =
net.sf.jasperreports.engine.JasperCompileManager.compileReport("beandatasourcereport.jrxml");
Bei meinem ersten Versuch gab es hier eine Fehlermeldung "javac nicht gefunden", diese konnte ich allerdings nicht mehr reproduzieren.
Tritt sich erneut auf, dann sollte man darauf achten, dass in den Projekt-Properties ein "JDK 1.6" referenziert wird und keine Runtime.
Vorcompilierung mittels Ant
Im JasperReports-Paket liegt ein Tool, mittels dem aus der "*.jrxml"-Datei eine "*.jasper"-Datei compiliert werden kann. Diese kann das Programm
dann ohne weiteren Compilevorgang verwenden.
Ich habe in obigem Beispiel eine "build.xml" gebaut, die diesen Vorgang startet:
<project name="JasperReportsTeste" default="compile" basedir=".">
<description>Compiliert *.jasper-Dateien aus allen *.jrxml-Dateien.</description>
<path id="classpath">
<!--Alle JASPER-Jars einbinden! -->
<fileset dir="C:/temp/jasperreports/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="C:/temp/jasperreports/dist">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="compile">
<taskdef name="jrc" classname="net.sf.jasperreports.ant.JRAntCompileTask">
<classpath refid="classpath"/>
</taskdef>
<jrc
destdir="."
tempdir="."
keepjava="false">
<src>
<fileset dir=".">
<include name="**/*.jrxml"/>
</fileset>
</src>
<classpath refid="classpath"/>
</jrc>
</target>
</project>
Wichtig ist hier, dass im Classpath alle JARs des Jasper-Pakets eingebunden werden. Das Target "compile" verwendet den Task "jrc", der
in einem übergebenen Verzeichnis alle *.jrxml-Dateien durchcompiliert.
Anmerkung: es werden scheinbar Änderungen erkannt, d.h. der Task compiliert nicht immer alle Reports - hier wäre eine Erweiterung
meines Ant-Tasks um ein Löschen der "*.jasper"-Dateien angebracht.
Das Laden des Reports ist jetzt einfacher:
net.sf.jasperreports.engine.JasperPrint jp =
net.sf.jasperreports.engine.JasperFillManager.fillReport("beandatasourcereport.jasper", new HashMap(), dataSource);
Der Parameter "dataSource" wird weiter unten erklärt.
Programmatisches Vorcompilieren
Statt eines Ant-Tasks könnte man das Compilieren in die "*.jasper"-Datei auch selbst vornehmen:
net.sf.jasperreports.engine.JasperCompileManager.compileReportToFile("beandatasourcereport.jrxml","beandatasourcereport.jasper");
Zu empfehlen ist vermutlich die Variante über Ant-Task.
Beispiel 2: XML-DataSource
In diesem Beispiel wird eine XML-Datei als Datenquelle verwendet, und außerdem wird gezeigt, wie eine Gruppierung über ein Unterelement als Subreport umgesetzt werden kann.
Die XML-Datei stammt aus der Anwendung von Gruppe 1, ich habe allerdings die XML-Struktur modifiziert (alle "answer"-Elemente in ein Subelement "answers" gepackt).
Ob dies allerdings wirklich nötig ist, kann ich nicht sagen, ich habe ziemlich lange experimentiert, bis ich den Subreport am Laufen hatte.
Die Struktur ist:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<questioncatalogue CREATOR_ID="42" ID="73537353" NAME="Romane">
<questionset ID="13371337">
<question>Wie heißen die Autoren von Märchenmond?</question>
<answers>
<answer ID="17841234___37" TRUTH="false">Die Gebrüder Grimm - 13371337</answer>
<answer ID="12341454___37" TRUTH="true">Wolfgang und Heike Hohlbein - 13371337</answer>
<answer ID="123445634___37" TRUTH="false">Richard von Weizäcker - 13371337</answer>
<answer ID="121234___37" TRUTH="false">Terry Pratchett - 13371337</answer>
</answers>
</questionset>
<questionset ID="13371338">
<question>...</question>
...
Report anlegen:
In "IReport" wird diesmal eine "XML file datasource" angelegt.

Es wird die Quelldatei ausgewählt, außerdem wird die "Query" definiert. Bei einer XML-DataSource ist das eine XPath-Expression, die die Liste von
Daten definiert, über die die oberste Report-Detailebene laufen soll. In diesem Fall ist das "/questioncatalogue/questionset".
Diese XPath-Expressions müssen immer das letztes Element die Ebene im DOM referenzieren, über das gelaufen werden soll, hier also "questionset",
weil wir über alle "questionset"-Elemente laufen wollen.

Leider sehen wir auch in dieser Variante keine Feldliste. Bei Verwendung einer XSD als Datenquelle war ich ebenfalls erfolglos. Mag aber auch daran liegen,
dass ich einen Fehler gemacht hatte, die XPath-Ausdrücke scheinen zu keinerlei Fehlermeldungen zu führen.
Es wird ein Report namens "romanereport.jrxml" angelegt.
Jetzt öffnen wir diese "jrxml"-Datei mit einem XML-Editor und passen sie so an (Language außerdem wieder von "Groovy" auf "Java" umstellen):
<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="romanereport"...">
...
<queryString language="xPath"><![CDATA[/questioncatalogue/questionset]]></queryString>
<field name="question" class="java.lang.String">
<fieldDescription><![CDATA[question]]></fieldDescription>
</field>
<field name="questionsetid" class="java.lang.String">
<fieldDescription><![CDATA[@ID]]></fieldDescription>
</field>
...
Zuerst wird in einem Element "queryString" ein xPath-Ausdruck definiert, der die bereits beim Report-Erstellen angegebene Filterbedingung enthält.
Wichtig: Alle folgenden relativen XPath-Ausdrücke beziehen sich auf diese Ebene im XML-Dokument, also auf Elemente "question".
Danach werden zwei Felder deklariert. Die "fieldDescription" enthält diesmal ebenfalls XPath-Ausdrücke. Diese beziehen sich in meinem Beispiel
relativ auf den "queryString", man könnte aber wohl auch absolute Pfade angeben. Fraglich nur, ob dann die Zuordnung klappt...
Der erste Ausdruck "question" liefert den String-Inhalt des Elements <question>.
Der zweite Ausdruck "@ID" verweist auf ein Attribut, und zwar ebenfalls relativ zum Element des "queryString", also referenziert er die "ID" des "questionSet".
Auf diese Felder wird im Report dann wie bekannt zugegriffen:
<textField isStretchWithOverflow="true">
<reportElement positionType="Float" x="0" y="0" width="200" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{answerid}]]></textFieldExpression>
</textField>
Feld-Editor
Nach dem Anlegen des Reports ist es möglich, einen Feldeditor aufzurufen.
Hierzu Rechtsklick auf den erzeugten Report im "Report Inspector" und "Edit Query":

Es öffnet sich dieses Fenster:

Subreport anlegen:
Alle n Antworten sollen innerhalb der Detailsektion "/questioncatalogue/questions" als Subreport mit variablener Höhe ausgegeben werden.
Hierzu wird eine neue Report-Datei (im Beispiel: "answerreport.jrxml") angelegt. Man kann wie oben "IReport" und die gleiche DataSource verwenden.
Einbinden des Subreports in "romanereport.jrxml":
<subreport isUsingCache="false">
<reportElement x="32" y="25" width="445" height="20" key="subreport-1" />
<subreportParameter name="XML_DATA_DOCUMENT">
<subreportParameterExpression>$P{XML_DATA_DOCUMENT}</subreportParameterExpression>
</subreportParameter>
<subreportParameter name="QuestionSetID">
<subreportParameterExpression>$F{questionsetid}</subreportParameterExpression>
</subreportParameter>
<subreportExpression class="java.lang.String"><![CDATA["answerreport.jasper"]]></subreportExpression>
</subreport>
Wie üblich sind hier z.B. im "subreport"-Element einige Attribute gesetzt, von denen ich nicht weiß, ob sie nötig sind. Internet-Fundstücke ;-).
Wichtig ist:
Subreport "answers.jrxml":
Der Subreport hat zwei Besonderheiten:
- Es wird ein Parameter "QuestionSetID" definiert, der den Primärschlüssel angibt, anhand derer der Subreport seine Daten ermittelt:
<parameter name="QuestionSetID" class="java.lang.String"/>
- Der Query-String sicht hier explizit das QuestionSet mit dieser übergebenen ID (Parameter-Referenz). Innerhalb dieses Elements
werden die "answers" durchlaufen, und hiervon die Elemente "answer" (genauso wie im Hauptreport wird hier also der Pfad inklusive des Childelements
angegeben, über das wir laufen wollen:
pre><queryString language="xPath"><![CDATA[/questioncatalogue/questionset[@ID='$P{QuestionSetID}']/answers/answer]]></queryString>
- Die aktuelle Antwort ist der Textinhalt von "answer", also dem aktuellen XPath-Element. Deshalb ist der XPath-Ausdruck nur ein Punkt, also "aktueller Pfad":
<field name="answer" class="java.lang.String">
<fieldDescription><![CDATA[.]]></fieldDescription>
</field>
- Und die Antwort hat ein Attribut "ID", das wir ausgeben wollen:
<field name="answerid" class="java.lang.String">
<fieldDescription><![CDATA[@ID]]></fieldDescription>
</field>
Der Rest ist identisch mit den bisherigen Report-Erkenntnissen. Ich habe allerdings in der jrxml-Datei alle Sektionen bis auf die "detail"-Sektion gekillt.
Report laden:
Das ist ziemlich einfach:
Map<String, Object> params = new HashMap<String, Object>();
Document document = JRXmlUtils.parse(JRLoader.getLocationInputStream("romane.xml"));
params.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT, document);
JasperPrint print = JasperFillManager.fillReport("romanereport.jasper", params);
JRViewer viewer = new JRViewer(print);
Hier wird mittels der Klasse net.sf.jasperreports.engine.util.JRXmlUtils
ein XML-Dokument geladen, und dieses Dokument als Parameter "XML_DATA_DOCUMENT"
in den Report gepackt (steht dort unter diesem Namen in der "Parameters"-Liste zur Verfügung, siehe oben.
Ich hoffe die Generics-Deklaration für die "params", die ich mir zusammengetüftelt hatte (Key = String, Value = Object), stimmt so ;-).
Stand 15.06.2010
Historie:
07.06.2010: Erstellt
15.06.2010: "Präzisierungen" beim XPath, "Report Query"-Editor