JasperReportsでExcelを作成する 

# 背景

- JasperReportsでPDFを作成する記事は結構あるけどExcelに関する記事は少なかったので
- JasperReports自体はJavaで動作する帳票出力ライブラリ。昔からあるライブラリで日本語の情報も豊富。

# 環境

- Java 11
- jasperreports 6.19.1
- Gradle 7.4.2

# 実装

## 下準備

- 関連ライブラリを入れておく

``` gradle: build.gradle
plugins {
    id 'java'
}

group = 'qiita.jasper.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
targetCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {

    // Test
    testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.0.1'

    // Jasper
    // https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports
    implementation 'net.sf.jasperreports:jasperreports:6.19.1'

    // Jasper Excel操作用
    // Excelを操作するライブラリで入れないと実行時にエラーになるので注意
    implementation 'org.apache.poi:poi:4.0.1'
}
```

- イメージだけですが以下のような帳票テンプレートを作成
- パラメーター名とフィールド名があっていればよいのでEXCEL出力においては重要なとこじゃないです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384027/06e471f3-dda4-22f1-6bf7-dce0aa12297e.png)

## メインのとこ

- 出力データの読み込みといった処理の大筋はPDFを出力する場合と同じ。ざっくり以下のような流れ。
    1. 帳票テンプレートの保存場所とかの基本設定を読込
    1. 出力データを設定
    1. 帳票テンプレートをコンパイル
    1. 出力データとコンパイルしたものから出力形式などを決めるJasperPrintクラスのインスタンスを生成
    1. お好みの形式で出力
- 出力の時にJRXlsExporterクラスを使うところが違う。ここだけ気をつければOK。

``` java: ReportSample.java
package qiita.jasper.sample;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.export.OutputStreamExporterOutput;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;

public class ReportSample {
	protected JasperReportConfig jasperReportConfig;
	/** 生成した帳票のパス */
	protected Path path;

	public Path getPath() {
		return path;
	}

    /** 
     * 帳票の設定値を読込
     */
	protected ReportSample() {
		this.jasperReportConfig = new JasperReportConfig();
	}

	/**
	 * 帳票を生成する
	 * 
	 * @throws IOException
	 * @throws JRException
	 * @throws FileNotFoundException
	 */
	public void generateExcelReport() throws FileNotFoundException, JRException, IOException {
		// サマリー（繰返し部分以外）を詰める
		Map<String, Object> params = makeBeansParams();

		// 明細（繰返し部分）を詰める
		JRDataSource dataSource = makeBeansDataSource();

		// 作成実行
		executeExcelReportGeneration(params, dataSource);
	}

	/**
	 * JasperReportsのParamtersに渡すインスタンスを生成する
	 * ※. 適当なサマリーデータを生成しているので、使う際は要修正
	 * 
	 * @return サマリーデータ
	 */
	protected Map<String, Object> makeBeansParams() {
		Map<String, Object> params = new HashMap<>();
		params.put("title", "サンプルのタイトル");
		params.put("pageHeader", "サンプルのページヘッダー");
		params.put("columnHeader", "サンプルのカラムヘッダー");
		params.put("columnFooter", "サンプルのカラムフッター");
		params.put("pageFooter", "サンプルのページフッター");
		params.put("summary", "サンプルのサマリー");
		return params;
	}

	/**
	 * JasperReportsのFieldsに渡すインスタンスを生成する
	 * ※. 適当な明細データを生成しているので、使う際は要修正
	 * 
	 * @return 明細データ
	 */
	protected JRDataSource makeBeansDataSource() {
		List<Detail> items = IntStream.range(1, 10).boxed().map(i -> new Detail("明細名" + i, i * 10, "個"))
				.collect(Collectors.toList());
		return new JRBeanCollectionDataSource(items);
	}

	/**
	 * jasperReportsを使用してExcelファイルを作成する。 今回の一番重要な処理！！！
	 *
	 * @param params サマリーデータ
	 * @param dataSource 明細データ
	 * @throws JRException
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private void executeExcelReportGeneration(Map<String, Object> params, JRDataSource dataSource)
			throws JRException, FileNotFoundException, IOException {
		complieJasperReports();

		JasperPrint basePrint = JasperFillManager.fillReport(jasperReportConfig.jasperFilePath, params, dataSource);

        // ↓↓ここ以降の処理がPDFの場合と異なる ↓↓
		JRXlsExporter exporter = new JRXlsExporter();
		exporter.setExporterInput(new SimpleExporterInput(basePrint));

		// EXCELファイルの出力先を指定
		String outputExcelFilePath = jasperReportConfig.outputExcelFilePath;
		try (ByteArrayOutputStream excelStream = new ByteArrayOutputStream();
				FileOutputStream fos = new FileOutputStream(outputExcelFilePath)) {
			OutputStreamExporterOutput exporterOutput = new SimpleOutputStreamExporterOutput(excelStream);
			exporter.setExporterOutput(exporterOutput);
			exporter.exportReport();

			excelStream.writeTo(fos);
		}
        // 生成したEXCELファイルのパスを入れておく
		path = FileSystems.getDefault().getPath(outputExcelFilePath);
	}

	/**
	 * jrxmlファイルをコンパイルする。
	 * 
	 * @throws JRException
	 *
	 */
	private void complieJasperReports() throws JRException {
		JasperCompileManager.compileReportToFile(jasperReportConfig.jrxmlFilePath, jasperReportConfig.jasperFilePath);
	}

	/**
	 * JasperReportのファイル参照設定
	 */
	private class JasperReportConfig {
        /** 帳票テンプレート */
		private String jrxmlFilePath;
        /** コンパイルしたもの */
		private String jasperFilePath;
        /** 生成したEXCELファイル */
		private String outputExcelFilePath;

		JasperReportConfig() {
			File jrxmlFile = new File("src/main/resources/input/sample.jrxml");
			this.jrxmlFilePath = jrxmlFile.getAbsolutePath();
			File jasperFile = new File("src/main/resources/output/sample.jasper");
			this.jasperFilePath = jasperFile.getAbsolutePath();
			File outputPdfFile = new File("src/main/resources/output/sample.xls");
			this.outputExcelFilePath = outputPdfFile.getAbsolutePath();
		}
	}

	/**
	 * 帳票の明細部
	 */
	public class Detail {
		private String name;
		private Integer volume;
		private String unit;

		public String getName() {
			return name;
		}

		public Integer getVolume() {
			return volume;
		}

		public String getUnit() {
			return unit;
		}

		public Detail(String name, Integer volume, String unit) {
			super();
			this.name = name;
			this.volume = volume;
			this.unit = unit;
		}
	}
}
```

## テスト

- EXCEL作成とは直接関係はないです

``` java: ReportSampleTest.java
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package qiita.jasper.sample;

import static org.junit.jupiter.api.Assertions.*;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import net.sf.jasperreports.engine.JRException;

public class ReportSampleTest {

	private static final String WORK_DIR = "src/main/resources/output/";

	@BeforeAll
	public static void initAll() throws IOException {
		// 作業フォルダを空にする
		cleanAllFile(WORK_DIR);
	}

	/**
	 * ディレクトリ以外のファイルを削除する。
	 * 
	 * @param dir
	 * @throws IOException
	 */
	private static void cleanAllFile(String dir) throws IOException {
		Path workDir = FileSystems.getDefault().getPath(dir);
		try (Stream<Path> stream = Files.list(workDir).filter(p -> p.toFile().isFile())) {
			stream.forEach(p -> {
				try {
					Files.deleteIfExists(p);
				} catch (IOException e) {
					// TODO 自動生成された catch ブロック
					e.printStackTrace();
				}
			});
		}
	}

	@Test
	public void エクセルが生成されること() throws FileNotFoundException, JRException, IOException {
		Path expected = FileSystems.getDefault().getPath(WORK_DIR + "sample.xls").toAbsolutePath();
		ReportSample report = new ReportSample();
		report.generateExcelReport();
		Path actual = report.getPath().toAbsolutePath();
		assertEquals(expected, actual);
	}
}

```

# 結果

- 以下のように出力される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384027/705553f3-e3c8-3b3d-42e4-2c5f7542a7bc.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384027/57aee4ed-99dc-29e2-abe9-c77f9a01fb8f.png)

# 感想

- PDFとEXCELを1フォーマットで両方作成できるのはやっぱり便利

# 参考

- [Jaspersoft Community](https://community.jaspersoft.com/)
- [vakho10/Main.java](https://gist.github.com/vakho10/240148736fc8ecc5b73bbf0cff0628d8)
- [JasperReport Excel出力プロパティまとめ(日本語版)](https://qiita.com/JINr/items/07f4214760cae8f500c0)
- [TIBCO Jaspersoft](https://www.jaspersoft.com/)