見出し画像

JasperReports で CSV から PDF 帳票 (2)

(見出し画像は,Wikimedia Commons にあった津軽錦石の写真)

1. はじめに

第1弾では,JasperReports で,一つの CSV データを一つの PDF 帳票に出力しました.

しかし,今回の業務では,二つの CSV データを一つの PDF 帳票に出力する必要がありました.言い換えれば,1つの帳票の中に二つ表が入っているのです.(こういう帳票はよくあると思います.例えば,表1に請求概要(商品価格合計,サービス価格合計,消費税,合計),表2に商品明細(,表3にサービス明細,…).簡単なことのように思えますが,結構苦労しましたので,以下メモ書きです.

2. 複数の表を一つの帳票にまとめる方法

多分以下の二通りがあります.

1. Basic Elements の Table を使用する方法
2. Subreport を使用する方法

Table を少し使用してみましたが,Table は複数ページにまたがった表示ができなさそうです.今回は,表が複数ページにわたって表示されることを期待しているので,Subreport を使用することにします.

また,お題ですが,前回と同じく町田市のオープンデータで,こんなもの(CSV)があります.

前回の名産品データと合わせて,表1に「名産品」,表2に「文化財」を表示するPDF 帳票を作ることにします.

3. Subreport を使った帳票デザイン

使用するものは以下です.

環境: Windows 10 Pro + Jaspersoft Studio (JSS) 6.12.2
CSV データ:
(名産品) Z:\datacrat\Machida\meisanhin.csv
(文化財) Z:\datacrat\Machida\132098_cultural_property.csv
…まその…ファイル名もバラバラです > オープンデータ

以下,JSS 上の作業です.

1. "Machida2" プロジェクトを作成
2. プロジェクトの Properties で,日本語フォントの使用設定を行う(前回と同じやり方)
3. "Machida2" プロジェクト内に,Jasper Report "meisanhin-bunkazai" を作成
 * "Report Templates" では,前回と同じく "Blank A4" を使用)
* "Data Source" では,前回と異なり "One Empty Record - Empty rows" を選択したまま,"Finish"

画像1

今作成した "meisanhin-bunkazai.jrxml" は,Main Report です.この中に,名産品 Subreport と,文化財 Subreport を配置します.配置のやり方は実は何通りもあるのかもしれませんが,以降では,"Detail 1" バンドに名産品を,"Detail 2" バンドに文化財を配置することにします.

バンドの追加

4. "Main Report" 画面の "Detail 1" あたりで右クリックして,"Add Detail Band" を選びます.
5. 使わないバンド "Page Header","Column Header","Column Footer","Summary" を消してしまいます.(それぞれのバンドで右クリックして,"Delete" を選択)

画像2

名産品 Subreport の配置と設定

6. "Basic Elements" ペインから "Subreport" を掴んで,"Detail 1" バンドあたりにドラッグ&ドロップします.
7. "Subreport" ウインドウで,"Create a new report" を選び,"Next"
8. "Report Templates" ウインドウで,"Blank A4" を選び,"Next"
9. "Report file" ウインドウで,"parent folder" を "Machida2" とし,"File name" を "meisanhin.jrxml" として "Next"
10. "Data Source" ウインドウには,前回作成した "meisanhin - CSV File" が存在するので,それを選択して "Next"
11. "Fields" ウインドウで,表に入れたい項目を選択して "Next"
12. "Group by" ではそのまま何も選ばず "Next"
13. "Connection" では "Don't use any connection or Data Source" を選択して "Next"
14. "Dataset Parameters" でも何も選ばす "Finish"

以上で,自動的に "meisanhin.jrxml" の編集画面に遷移します.

画像3

前回と同じやり方で,名産品の表を作成します.ただ,今回は,"Column Header" と "Detail 1" 以外のバンドは削除しておきます.

画像4

プレビューでこんな感じに出るようにします.

ここで,"meisanhin-bunkazai.jrxml" の編集画面に戻ると,

画像5

こんな感じで中途半端に "Detail 1" に Subreport が入った状態になっていますので,これを幅ぴったりに合わせるなどします.また,"Basic Elements" から "Static Text" を引っ張ってきて,"名産品" というラベルを入れておいてあげましょう.

画像6

これで "Preview" に切り替えると… Subreport の内容も挿入された状態でプレビューできそうですが,できません.

画像7

これが Subreport のいやらしいところです.Subreport のデータソースが定義されてない状態といえばいいでしょうか,要は JSS 上でメインレポートを Preview しても,Subreport によしなにデータを Fill してくれないんです.

Subreport の内容が出るようにするために,ちょっと面倒なことをやる羽目になります.JSS の中で Java API を操作する必要が出てきます…(本当は,Subreport の DataSource に突っ込める DataSource オブジェクトを JSS で操作できるよう露わにしてくれていれば,良いのだけど)← 言っていることあっているか自信がない

15. "Outline" ペインで "Detail 1" 内の "Subreport" を選択
16. "Properties" ペインで "Subreport" タブを選択
17. "Connection Expression" の内容を空にする
18. "Data Source Expression" の入力欄の右にあるボタンをクリックする
19. "Expression Editor" で,以下のように "new net.sf.jasperreports.engine.data.JRCsvDataSource(new File("Z:\\datacrat\\Machida\\meisanhin.csv"), "UTF-8")" と入れて "Finish"

画像8

これで,前回 Java コードの中で見た,JRCsvDataSource オブジェクトを作成して,当てはめてやろういうわけです.では "Preview" !

画像9

…遺憾ながら,エラーになりました.エラーメッセージはこんなのです.

net.sf.jasperreports.engine.JRException: net.sf.jasperreports.engine.JRRuntimeException: net.sf.jasperreports.engine.JRException: Unknown column name: 店舗名.
	at com.jaspersoft.studio.editor.preview.view.control.ReportController.fillReport(ReportController.java:551)
	at com.jaspersoft.studio.editor.preview.view.control.ReportController.access$18(ReportController.java:526)
	at com.jaspersoft.studio.editor.preview.view.control.ReportController$1.run(ReportController.java:444)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
Caused by: net.sf.jasperreports.engine.JRRuntimeException: net.sf.jasperreports.engine.JRException: Unknown column name: 店舗名.
	at net.sf.jasperreports.engine.fill.JRFillSubreport.prepare(JRFillSubreport.java:962)
	at net.sf.jasperreports.engine.fill.JRFillElementContainer.prepareElements(JRFillElementContainer.java:542)
	at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:453)
	at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:428)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillColumnBand(JRVerticalFiller.java:2599)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillDetail(JRVerticalFiller.java:823)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReportStart(JRVerticalFiller.java:264)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:110)
	at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:615)
	at net.sf.jasperreports.engine.fill.BaseFillHandle$ReportFill.run(BaseFillHandle.java:135)
	at java.lang.Thread.run(Thread.java:748)
Caused by: net.sf.jasperreports.engine.JRException: Unknown column name: 店舗名.
	at net.sf.jasperreports.engine.data.JRCsvDataSource.getColumnIndex(JRCsvDataSource.java:438)
	at net.sf.jasperreports.engine.data.JRCsvDataSource.getFieldValue(JRCsvDataSource.java:324)
	at net.sf.jasperreports.engine.fill.JRFillDataset.setOldValues(JRFillDataset.java:1501)
	at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:1402)
	at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:1378)
	at net.sf.jasperreports.engine.fill.JRBaseFiller.next(JRBaseFiller.java:1194)
	at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:108)
	at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:615)
	at net.sf.jasperreports.engine.fill.BaseReportFiller.fill(BaseReportFiller.java:433)
	at net.sf.jasperreports.engine.fill.JRFillSubreport.fillSubreport(JRFillSubreport.java:824)
	at net.sf.jasperreports.engine.fill.JRSubreportRunnable.run(JRSubreportRunnable.java:61)
	at net.sf.jasperreports.engine.fill.AbstractThreadSubreportRunner.run(AbstractThreadSubreportRunner.java:221)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	... 1 more

"店舗名" というカラムが見つけらんねーというわけです.思い当たるのは,setUseFirstRowAsHeader(true) メソッドを実行できていないということ.JRCsvDataSource クラスのコンストラクター内で,このプロパティが設定できれば良いのだが!(調べた範囲では多分できない)

結局,JRCsvDataSource オブジェクトを作って,setUseFirstRowAsHeader(true) を噛ませたものを渡したい…のですが,さっきの "Expression Editor" では,コンストラクター一発しか書けなさそう orz
じゃあどうするの,と調べると,どうも Scriptlet というのを使うとできそうです.

DataSource 生成のための Scriptlet の作成

20. "Project Explorer" ペインの "Machida2" を右クリック -> "New" -> "Other"
21. "Select a wizard" ウインドウで,"Java" を展開し,"Source Folder" を選択して "Next"
22. "Source Folder" ウインドウで,"Folder name: " 欄に "src" と入れて "Finish"
23. "Project Explorer" ペインで,今作った "src" フォルダを右クリック -> "New" -> "Other"
23. "Select a wizard" ウインドウで,"Java" を展開し,"Package" を選択して "Next"
24. "Java Package" ウインドウで,"Name: " 欄に "dev.datacrat" と入れて "Finish"
25. "Project Explorer" ペインで,今作った "dev.datacrat" パッケージを右クリック
26.  "Select a wizard" ウインドウで,"General" を展開し,"File" を選択して "Next"
27. "File" ウインドウで,"File name: " 欄に "meisanhinCsvDataSource.java" と入れて "Finish"

画像10

遺憾ながらここに Java コードを書き,Save します.

package dev.datacrat;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;

import net.sf.jasperreports.engine.JRDefaultScriptlet;
import net.sf.jasperreports.engine.JRException;

public class meisanhinCsvDataSource extends JRDefaultScriptlet {
	
	String csvpath = "Z:\\datacrat\\Machida\\meisanhin.csv";
	net.sf.jasperreports.engine.data.JRCsvDataSource csvds;
	
	public net.sf.jasperreports.engine.data.JRCsvDataSource get() throws JRException {
		try {
			csvds = new net.sf.jasperreports.engine.data.JRCsvDataSource(new File(csvpath), "UTF-8");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		csvds.setUseFirstRowAsHeader(true);
		return csvds;
	}
}

次に,この Scriptlet をメインレポートに登録します.

28. "meisanhin-bunkazai.jrxml" に戻り,"Design" タブを選択した状態で "Outline" ペインの "Scriptlets" を右クリックし,"Create Scriptlet" を選択
29. 勝手に "Scriptlet_1" ができるので,"Properties" ペインで "Name" を "Scriptlet_1" から meisanhinCsvDataSource に変更.
30. その下にある "Class" の "..." ボタンを押す
31. "Open Type" ウインドウで,"Enter the name prefix..." 欄に "meisa.." とタイプすると,meisanhinCsvDataSource が出てくるので,それを選択し "OK"

次に,この Scriptlet を使って,Subreport の DataSource を指定します.

32. "Project Explorer" ペインで,"Machida2" を右クリックし,"Build Project" (←これ大事.さもないと,35. でメソッド一覧が出てこない)
33. "Outline" ペインで "Detail 1" 内の "Subreport" をクリックし,"Properties" ペインの "Subreport" タブをクリック
34. "Data Source Expression" の右のボタンを押し,"Expression Editor" を登場させる
35. 左のカラムの "Parameters" を選ぶと,真ん中の蘭に "meisaihinCsvDataSource_SCRIPT" が出てくるのでそれを選択する(ダブルクリックしない)と,一番右にメソッド一覧が出てくるので,その中から "get()" をダブルクリック

画像11

この状態で,"meisanhin-bunkazai.jrxml" の Preview をすると…

画像12

Subreport の内容がハマった状態で表示されました!

では同様に文化財 Subreport を "Detail 2" バンドに配置します…なのですが,なぜか,Subreport の Data Source を指定するところで

画像13

こんなエラーが出てきて,進めません.やれやれ…仕方がないので,一旦,名産品 Subreport に関する Data Souce と Scriptlet 登録を削除して,まず文化財 Subreport を配置します.

文化財 Subreport を配置したところ.

画像14

で,文化財 Data Source 用 Scriptlet の作成等々を実施します.最終形はこちら.全体6ページの中の3ページ目を表示していますが,ここが名産品 Subreport と文化財 Subreport の境目になっています.

画像15

4. Java ブログラムへの埋め込みのための考察

レイアウトはなんとかできました.あとは,Java プログラムにどう埋め込むかを考えます.ネットで Subreport を使うパターンを検索すると,fillSubReport() などという API があり,それを使っている人もいるようですが,正直あまりよくわかりません.ここでは,前回と同じく Fill 作業は Main Report に対して fillReport() を実行することで行います(その方が JSS で Preview するときの動作に沿っている気もするし)

ただ,現在のデータソースの作りは,Scriptlet 内で名産品,文化財それぞれの CSV データパスを定数で持たせてしまっているので,コマンドライン引数で CSV パスを渡せません.そこで,いろいろ調べた挙句,前回は無視していた "Parameter" を使うことにしました.「Parameter で Subreport に対して Data Source を渡す」です.

そのため,名産品 Data Source を示す Parameter "meisanhinCsvDataSource" と,文化財 Data Source を示す Parameter "bunkazaiCsvDataSource" を作成し,それぞれ Subreport の Data Source に設定します.ただ,このままでは JSS での Preview の時には,Data Source 設定なしの状態で Preview が走るので,Subreport に何も表示されなくなってしまいます.よって,それぞれの Parameter のデフォルト値を, $P{meisanhinCsvDataSource_SCRIPTLET.get()},$P{bunkazaiCsvDataSource_SCRIPTLET.get()} に設定することにより,JSS での Preview 時に支障がないようにします.

Parameter の作成

1. "meisanhin-bunkazai.jrxml" の "Design" タブを選択した状態で "Outline" ペインで "Parameters" を右クリック -> "Create Parameter" を選択(自動的に "Parameter1" が生成される)
2. "Properties" ペインで "Name" 欄を "Parameter1" から "meisanhinCsvDataSource" に変更
3. "Class" 欄の右側のボタンを押して,"Open Type" ウインドウを出し,"Enter the name prefix..." に "jrcsvdatasource.." と入力すると "Maching items" に選択肢が出てくるので,"JRCsvDataSource -net.sf.jasperreports.engine.data -..." をダブルクリック
4. "Is For Prompting" のチェックを外す
5. "Default Value Expression" の右側のボタンを押して,"Expression Editor" ウインドウを出し,"Parameters" を選んで,真ん中欄で "meisanhinCsvDataSource_SCRIPTLET" を選択し,一番右の欄で "get()" をダブルクリック
(上記 1〜5 を,文化財についても行う)

作成した Parameter を,Subreport の Data Source に設定します.

6. "Design" タブを選んだ状態で,名産品 Subreport をクリック
7. "Properties" ペインの "Subreport" タブで,"Data Source Expression" の右側のボタンをクリックして,"Expression Editor" ウインドウを表示
8. 一番左で "Parameters" を選んで,真ん中欄に出てくる Parameter のうち,"meisanhinCsvDataSource" をダブルクリックして "Finish"
(上記 6〜8 を,文化財についても行う)

以上の変更を施しても,JSS の Preview が変わらず動作することを確認します.

5. Java プログラムの作成

Subreport を導入したり,Build Project をやったりしているので,JSS でも .jrxml をコンパイルした .jasper ファイルが生成されていることがわかります.

画像16

今回の Java プログラムではこれをそのまま利用します (JRXML ファイルのコンパイルは行わない).それに伴い,JasperReport オブジェクトの作り方が,JasperCompileManager.compileReport() を用いたものから,JRLoader.loadObject() を用いたものに変わっています.あと,以下の部分で,Subreport に渡すための Data Source パラメーターを設定しています.

        parameters.put("meisanhinCsvDataSource", meisanhinDataSource);
       parameters.put("bunkazaiCsvDataSource", bunkazaiDataSource);

また,リソース関連では以下を揃えます.

画像17

src\main\resources\dev\datacrat\meisanhinCsvDataSouce.class
src\main\resources\dev\datacrat\bunkazaiCsvDataSouce.class

JaspersoftWorkspace ディレクトリ以下の,"Machida2\bin\dev\datacrat" フォルダ からコピーします.(これら,JSS でしか使わないんですが,無いと実行時に怒られます)

src\main\resources\meisanhin-bunkazai.jasper
src\main\resources\meisanhin.jasper
src\main\resources\bunkazai.jasper

JaspersoftWorkspace ディレクトリ以下の,"Machida2" フォルダからコピーします.

src\main\resources\jasperreports_extension.properties
src\main\resources\fonts\ipaexm.ttf
src\main\resources\fonts\ipaexm.xml

日本語フォント関連(前回と同じ内容です)

以上を揃え,pom.xml を前回と同様に編集して,

最後に,"D:\maven-projects\Machida2\meisanhin_bunkazai" フォルダで,

> mvn compile
> mvn package shade:shade

を実行すると,"D:\maven-projects\Machida2\meisanhin_bunkazai\target" に,Fat JAR "meisanhin_bunkazai-1.0-SNAPSHOT.jar" ができています.

この JAR ファイルを,java-11-openjdk を入れただけ(追加ライブラリ何もなし)の CentOS7 マシンで実行してみると,

$ java --illegal-access=deny \
> -jar /mnt/landisk1/datacrat/Machida/meisanhin_bunkazai-1.0-SNAPSHOT.jar \
> /mnt/landisk1/datacrat/Machida/meisanhin.csv \
> /mnt/landisk1/datacrat/Machida/132098_cultural_property.csv \
> /mnt/landisk1/datacrat/Machida/meisanhin_bunkazai.pdf

meisanhin_bunkazai.pdf ファイルが生成できました.ふう…


この記事が気に入ったらサポートをしてみませんか?