2 * Copyright 2014 Hanei Management Co.,Ltd.
\r
4 * This file is part of Jaxcel
\r
6 * Jaxcel is free software: you can redistribute it and/or modify
\r
7 * it under the terms of the GNU Lesser General Public License as published by
\r
8 * the Free Software Foundation, either version 3 of the License, or
\r
9 * (at your option) any later version.
\r
11 * Jaxcel is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU Lesser General Public License for more details.
\r
16 * You should have received a copy of the GNU Lesser General Public License
\r
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
\r
19 package org.hanei.jaxcel.report;
\r
21 import java.io.BufferedInputStream;
\r
22 import java.io.File;
\r
23 import java.io.FileOutputStream;
\r
24 import java.io.IOException;
\r
25 import java.io.InputStream;
\r
26 import java.io.OutputStream;
\r
27 import java.util.Map;
\r
30 import org.apache.poi.hssf.usermodel.HSSFSheet;
\r
31 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
\r
32 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
\r
33 import org.apache.poi.openxml4j.opc.OPCPackage;
\r
34 import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
\r
35 import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
\r
36 import org.apache.poi.ss.usermodel.Cell;
\r
37 import org.apache.poi.ss.usermodel.Row;
\r
38 import org.apache.poi.ss.usermodel.Sheet;
\r
39 import org.apache.poi.ss.usermodel.Workbook;
\r
40 import org.apache.poi.ss.usermodel.WorkbookFactory;
\r
41 import org.apache.poi.ss.util.CellReference;
\r
42 import org.apache.poi.xssf.usermodel.XSSFChartSheet;
\r
43 import org.apache.poi.xssf.usermodel.XSSFDialogsheet;
\r
44 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
\r
45 import org.hanei.jaxcel.exception.JaxcelInputException;
\r
46 import org.hanei.jaxcel.exception.JaxcelOutputException;
\r
47 import org.slf4j.LoggerFactory;
\r
48 import org.slf4j.Logger;
\r
52 * テンプレートのExcelファイルにデータを挿入することでExcel帳票を生成する。
\r
54 * <h4>テンプレートの書式について</h4>
\r
55 * <h5>${expression}</h5>
\r
56 * <p>値出力。expressionを解析し値を出力する。</p>
\r
58 * <h5>#foreach(${object in aryObject}[ rows:"number"][ cols:"number"][ direction:"row|col"][ style:"copy|^copy"][ shift:"true|false"][ block:"true|false"][ start:"number"][ end:"number"])</h5>
\r
59 * <p>foreach句。配列やListオブジェクト、Mapオブジェクトの繰返し出力を行う。<br>
\r
60 * プロパティにより、繰返し出力方向の指定や、繰返し回数、出力範囲以降のセルのシフト方法等を指定可能。</p>
\r
61 * <table border="1" cellspacing="1">
\r
62 * <tr><th>属性</th><th>説明</th></tr>
\r
64 * <td>${object in aryObject}</td>
\r
65 * <td>"${" 要素オブジェクト in 配列オブジェクト "}" のフォーマットで指定する。配列オブジェクトには配列、List、Mapが使用可能。<br>
\r
66 * rows・colsオプションで指定した範囲内で、要素オブジェクトで指定した名称で各インデックスの要素が出力可能。</td>
\r
70 * <td>繰返し元の行数を正の整数で指定する。"1"指定で#foreach指示子が記述された行のみとなる。省略すると規定値である "1"となる。</td>
\r
74 * <td>繰返し元の列数を正の整数で指定する。"1"指定で#foreach指示子が記述された列のみとなる。省略すると規定値である "1"となる。</td>
\r
77 * <td>direction</td>
\r
78 * <td>繰返し処理の方向を指定する。省略すると規定値である"row"となる。<br>
\r
80 * <tr><td>row</td><td>行方向に繰返す。</td></tr>
\r
81 * <tr><td>col</td><td>列方向に繰返す。</td></tr>
\r
86 * <td>繰返し元範囲(rows・colsオプションで指定する範囲)のセルスタイルのコピーについて指定する。省略すると規定値である"copy"となる。<br>
\r
88 * <tr><td>copy</td><td>セルスタイルをコピーする。</td></tr>
\r
89 * <tr><td>copy以外</td><td>セルスタイルをコピーしない。</td></tr>
\r
94 * <td>繰返し範囲より後方の範囲のシフト方法について指定する。省略すると規定値である"true"となる。<br>
\r
96 * <tr><td>true</td><td>繰返し範囲より後方の範囲をdirectionオプションで指定した方向にシフトする。</td></tr>
\r
97 * <tr><td>false</td><td>シフトしない。</td></tr>
\r
102 * <td>矩形範囲での繰返し処理を行うかについて指定する。省略すると規定値である"true"となる。<br>
\r
104 * <tr><td>true</td><td>繰返し範囲を矩形範囲(rows・colsオプションで指定する範囲)とする。</td></tr>
\r
105 * <tr><td>false</td><td>繰返し範囲を繰返し元範囲に含まれる行・列全体とする。<br>
\r
106 * directionオプションが"row"の場合、繰返し元範囲に含まれる行全体が繰返し範囲となる。<br>
\r
107 * directionオプションが"col"の場合、繰返し元範囲に含まれる列全体が繰返し範囲となる。</td></tr>
\r
112 * <td>配列オブジェクトの出力開始インデックス(1起点)を正の整数、もしくは、正の整数を返却する関数で指定する。省略すると規定値である"1"となる。</td>
\r
116 * <td>配列オブジェクトの出力終了インデックス(1起点)を正の整数、もしくは、正の整数を返却する関数で指定する。省略すると配列オブジェクトの要素数となる。</td>
\r
119 * <td colspan="2">startオプションよりendオプションの指定値が小さい場合、配列オブジェクトを降順で出力する。<br>
\r
120 * 配列オブジェクトの要素数は"size(配列オブジェクト)"関数で取得可能。</td>
\r
124 * <h5>#if(${expression}[ rows:"number"][ cols:"number"][ delete:"left|up|clear"][ block:"true|false"])</h5>
\r
125 * <p>if句。expression判定式がtrue判定の場合は指定範囲の出力、false判定の場合は指定の方法で範囲を削除する。</p>
\r
126 * <table border="1" cellspacing="1">
\r
127 * <tr><th>属性</th><th>説明</th></tr>
\r
129 * <td>${expression}</td>
\r
130 * <td>判定式。"${" 式 "}" のフォーマットで指定する。Bool値を返す式を指定する。Bool値を返さない式の場合は、値がnullでなければtrueと判断する。</td>
\r
134 * <td>制御範囲の行数を正の整数で指定する。"1"指定で#if指示子が記述された行のみとなる。省略すると規定値である "1"となる。</td>
\r
138 * <td>制御範囲の列数を正の整数で指定する。"1"指定で#if指示子が記述された列のみとなる。省略すると規定値である "1"となる。</td>
\r
142 * <td>判定式がflse判定の場合の制御範囲に対する操作を指定する。省略すると規定値である"left"となる。<br>
\r
144 * <tr><td>left</td><td>制御範囲セルを削除し、左に詰める。</td></tr>
\r
145 * <tr><td>up</td><td>制御範囲セルを削除し、上に詰める。</td></tr>
\r
146 * <tr><td>clear</td><td>制御範囲セルの値・計算式をクリアするのみで詰めない。</td></tr>
\r
151 * <td>矩形範囲での制御を行うかについて指定する。省略すると規定値である"true"となる。<br>
\r
153 * <tr><td>true</td><td>制御範囲を矩形範囲(rows・colsオプションで指定する範囲)とする。</td></tr>
\r
154 * <tr><td>false</td><td>制御範囲を矩形範囲に含まれる行・列全体とする。<br>
\r
155 * deleteオプションが"left"の場合、矩形範囲に含まれる列全体が制御範囲となる。<br>
\r
156 * deleteオプションが"up"の場合、矩形範囲に含まれる行全体が制御範囲となる。<br>
\r
157 * deleteオプションが"clear"の場合は無効。</td></tr>
\r
163 * @author Noboru Saito
\r
165 public class ReportMaker {
\r
167 private static final Logger log = LoggerFactory.getLogger(ReportMaker.class);
\r
171 * Excel 2003 形式(.xls)
\r
173 private NPOIFSFileSystem npoifs = null;
\r
177 * Excel 2007 形式(.xlsx, .xlsm)
\r
179 private OPCPackage pkg = null;
\r
184 private ELManager elMgr = null;
\r
189 private JaxcelContext context = null;
\r
194 private File templateFile = null;
\r
199 public ReportMaker() {}
\r
202 * 入力ストリームのExcelテンプレートファイルにデータを挿入することでExcel帳票を生成、Workbookオブジェクトを返却する。<br>
\r
203 * 返却されたWorkbookオブジェクトはPOIを使用し、加工・出力が可能。<br>
\r
204 * 入力ストリームは別途クローズが必要。<br>
\r
205 * Workbookオブジェクトの使用終了後はclose()メソッドでExcelテンプレートファイルのクローズが必要。
\r
207 * @param template Excelテンプレートファイル入力ストリーム
\r
208 * @param parameter テンプレートに挿入するデータ
\r
210 * @return Workbookオブジェクト
\r
212 * @throws JaxcelInputException 入力例外発生時
\r
214 public Workbook makeReport(InputStream template, Map<String, Object> parameter) {
\r
215 log.trace("makeReport start");
\r
218 if(template == null) {
\r
219 log.error("template is null");
\r
220 throw new JaxcelInputException("template is null");
\r
224 if (templateFile != null && templateFile.exists()) {
\r
225 templateFile.delete();
\r
226 log.debug("template file delete: {}", templateFile.getPath());
\r
227 templateFile = null;
\r
229 templateFile = createTempFile(template);
\r
232 Workbook book = makeReport(templateFile, parameter);
\r
234 log.trace("makeReport end");
\r
239 * 入力ストリームのExcelテンプレートファイルにデータを挿入することでExcel帳票を生成、出力ストリームにExcel帳票を出力する。<br>
\r
240 * 入出力ストリームは別途クローズが必要。
\r
242 * @param template Excelテンプレートファイル入力ストリーム
\r
243 * @param parameter テンプレートに挿入するデータ
\r
244 * @param output Excel帳票出力ストリーム
\r
246 * @throws JaxcelInputException 入力例外発生時
\r
247 * @throws JaxcelOutputException 出力例外発生時
\r
249 public void makeReport(InputStream template, Map<String, Object> parameter, OutputStream output) {
\r
250 log.trace("makeReport start");
\r
253 Workbook book = makeReport(template, parameter);
\r
256 outputReport(book, output);
\r
261 log.trace("makeReport end");
\r
265 * 入力ストリームのExcelテンプレートファイルにデータを挿入することでExcel帳票を生成、Excel帳票ファイルを出力する。<br>
\r
266 * 入力ストリームは別途クローズが必要。
\r
268 * @param template Excelテンプレートファイル入力ストリーム
\r
269 * @param parameter テンプレートに挿入するデータ
\r
270 * @param output Excel帳票出力ファイル
\r
272 * @throws JaxcelInputException 入力例外発生時
\r
273 * @throws JaxcelOutputException 出力例外発生時
\r
275 public void makeReport(InputStream template, Map<String, Object> parameter, File output) {
\r
276 log.trace("makeReport start");
\r
279 Workbook book = makeReport(template, parameter);
\r
282 FileOutputStream _output;
\r
284 _output = new FileOutputStream(output);
\r
286 catch(Exception e) {
\r
287 log.error("output file open error: {}", e.getMessage(), e);
\r
288 throw new JaxcelOutputException("output file open error");
\r
292 outputReport(book, _output);
\r
295 } catch (IOException e) {
\r
296 log.error("output file close error: {}", e.getMessage(), e);
\r
297 throw new JaxcelOutputException("output file close error");
\r
303 log.trace("makeReport end");
\r
307 * Excelテンプレートファイルにデータを挿入することでExcel帳票を生成、Workbookオブジェクトを返却する。<br>
\r
308 * 返却されたWorkbookオブジェクトはPOIを使用し、加工・出力が可能。<br>
\r
309 * Workbookオブジェクトの使用終了後はclose()メソッドでExcelテンプレートファイルのクローズが必要。
\r
311 * @param template Excelテンプレートファイル
\r
312 * @param parameter テンプレートに挿入するデータ
\r
314 * @return Workbookオブジェクト
\r
316 * @throws JaxcelInputException 入力例外発生時
\r
318 public Workbook makeReport(File template, Map<String, Object> parameter) {
\r
319 log.trace("makeReport start");
\r
322 if(template == null) {
\r
323 log.error("template file is null");
\r
324 throw new JaxcelInputException("template file is null");
\r
326 else if(!template.exists()) {
\r
327 log.error("template file does not exist: {}", template.getAbsolutePath());
\r
328 throw new JaxcelInputException("template file does not exist");
\r
330 else if(!template.canRead()) {
\r
331 log.error("template file can not read: {}", template.getAbsolutePath());
\r
332 throw new JaxcelInputException("template file can not read");
\r
334 if(parameter == null) {
\r
335 log.debug("parameter is null");
\r
338 // Excelテンプレートファイルオープン
\r
339 Workbook book = openWorkbook(template);
\r
342 makeReport(book, parameter);
\r
344 log.trace("makeReport end");
\r
349 * Excelテンプレートファイルにデータを挿入することでExcel帳票を生成、出力ストリームにExcel帳票を出力する。<br>
\r
350 * 出力ストリームは別途クローズが必要。
\r
352 * @param template Excelテンプレートファイル
\r
353 * @param parameter テンプレートに挿入するデータ
\r
354 * @param output Excel帳票出力ストリーム
\r
356 * @throws JaxcelInputException 入力例外発生時
\r
357 * @throws JaxcelOutputException 出力例外発生時
\r
359 public void makeReport(File template, Map<String, Object> parameter, OutputStream output) {
\r
360 log.trace("makeReport start");
\r
363 Workbook book = makeReport(template, parameter);
\r
366 outputReport(book, output);
\r
371 log.trace("makeReport end");
\r
375 * Excelテンプレートファイルにデータを挿入することでExcel帳票を生成、Excel帳票ファイルを出力する。<br>
\r
377 * @param template Excelテンプレートファイル
\r
378 * @param parameter テンプレートに挿入するデータ
\r
379 * @param output Excel帳票出力ファイル
\r
381 * @throws JaxcelInputException 入力例外発生時
\r
382 * @throws JaxcelOutputException 出力例外発生時
\r
384 public void makeReport(File template, Map<String, Object> parameter, File output) {
\r
385 log.trace("makeReport start");
\r
388 Workbook book = makeReport(template, parameter);
\r
391 FileOutputStream _output;
\r
393 _output = new FileOutputStream(output);
\r
395 catch(Exception e) {
\r
396 log.error("output file open error: {}", e.getMessage(), e);
\r
397 throw new JaxcelOutputException("output file open error");
\r
401 outputReport(book, _output);
\r
404 } catch (IOException e) {
\r
405 log.error("output file close error: {}", e.getMessage(), e);
\r
406 throw new JaxcelOutputException("output file close error");
\r
412 log.trace("makeReport end");
\r
416 * ExcelテンプレートのWorkbookオブジェクトにデータを挿入することでExcel帳票を生成する。<br>
\r
417 * Workbookオブジェクトの使用終了後はclose()メソッドでExcelテンプレートファイルのクローズが必要。
\r
419 * @param book Workbookオブジェクト
\r
420 * @param parameter テンプレートに挿入するデータ
\r
422 * @throws JaxcelInputException 入力例外発生時
\r
424 public void makeReport(Workbook book, Map<String, Object> parameter) {
\r
425 log.trace("makeReport start");
\r
429 log.error("workbook is null");
\r
430 throw new JaxcelInputException("Workbook is null");
\r
432 else if(!(book instanceof HSSFWorkbook) && !(book instanceof XSSFWorkbook)) {
\r
433 log.error("Workbook is unsupport type: {}", book.getClass().getName());
\r
434 throw new JaxcelInputException("Workbook is unsupported type");
\r
436 if(parameter == null) {
\r
437 log.debug("parameter is null");
\r
441 context = new JaxcelContext();
\r
443 // EL式マネージャ生成。パラメータ設定
\r
444 elMgr = new ELManager();
\r
445 elMgr.setParameter(parameter);
\r
447 // JaxlsコンテキストにEL式マネージャ設定
\r
448 context.setElManager(elMgr);
\r
453 log.trace("makeReport end");
\r
459 * @param template Excelテンプレートファイル
\r
461 * @throws JaxcelInputException Excelテンプレートファイルオープン失敗時
\r
463 private Workbook openWorkbook(File template) {
\r
464 log.trace("openWorkbook start");
\r
467 Workbook book = null;
\r
471 npoifs = new NPOIFSFileSystem(template);
\r
472 book = WorkbookFactory.create(npoifs);
\r
473 } catch (OfficeXmlFileException | IOException e1) {
\r
476 pkg = OPCPackage.open(template);
\r
477 book = WorkbookFactory.create(pkg);
\r
478 } catch (InvalidFormatException | IOException e2) {
\r
479 log.error("template file open error: [{}]. [{}]", e1.getMessage(), e2.getMessage());
\r
485 throw new JaxcelInputException("template file open error");
\r
488 log.trace("openWorkbook end");
\r
494 * 出力ストリームにワークブックを出力する
\r
497 * @param book Workbookオブジェクト
\r
498 * @param output 出力ストリーム
\r
500 * @throws JaxcelOutputException 出力例外発生時
\r
502 private void outputReport(Workbook book, OutputStream output) {
\r
503 log.trace("outputReport start");
\r
506 book.write(output);
\r
507 } catch (Exception e) {
\r
508 log.error("workbook output error: {}", e.getMessage(), e);
\r
509 throw new JaxcelOutputException("workbook output error");
\r
512 log.trace("outputReport end");
\r
516 * Excelテンプレートファイルのクローズ<br>
\r
517 * テンプレートファイルの変更は保存しません。
\r
519 * @throws JaxcelOutputException 出力例外発生時
\r
521 public void close() {
\r
522 log.trace("close start");
\r
525 if (npoifs != null) {
\r
527 log.debug("template file close.");
\r
532 log.debug("template file close.");
\r
535 if (templateFile != null && templateFile.exists()) {
\r
536 templateFile.delete();
\r
537 log.debug("template file delete: {}", templateFile.getPath());
\r
538 templateFile = null;
\r
540 } catch (IOException e) {
\r
541 log.error("template file close error: {}", e.getMessage(), e);
\r
542 throw new JaxcelOutputException("template file close error");
\r
545 log.trace("close end");
\r
549 * InputStreamから一時ファイルを作成
\r
551 * @return Fileオブジェクト
\r
553 * @throws JaxcelInputException 一時ファイル作成エラー発生時
\r
555 private File createTempFile(InputStream template) {
\r
556 log.trace("createTempFile start");
\r
558 final String PREFIX = "org.hanei.jaxcel_";
\r
559 final String SUFFIX = ".tmp";
\r
560 final int BUF_SIZE = 1024;
\r
562 BufferedInputStream inStream = null;
\r
563 File tmpFile = null;
\r
565 if(template instanceof BufferedInputStream) {
\r
566 inStream = (BufferedInputStream) template;
\r
569 inStream = new BufferedInputStream((InputStream) template);
\r
572 tmpFile = File.createTempFile(PREFIX, SUFFIX);
\r
573 FileOutputStream outStream = new FileOutputStream(tmpFile);
\r
575 byte buf[]=new byte[BUF_SIZE];
\r
577 while((len = inStream.read(buf)) != -1){
\r
578 outStream.write(buf, 0, len);
\r
582 log.debug("createTempFile: {}", tmpFile.getPath());
\r
583 } catch (SecurityException | IOException e) {
\r
584 log.error("template file create error: {}", e.getMessage());
\r
585 throw new JaxcelInputException("template file create error");
\r
588 log.trace("createTempFile end");
\r
594 * @param book Workbookオブジェクト
\r
596 private void makeBook(Workbook book) {
\r
597 log.trace("makeBook start");
\r
600 log.debug("sheet count: {}", book.getNumberOfSheets());
\r
601 for(int i = 0; i < book.getNumberOfSheets(); i++) {
\r
603 Sheet sheet = book.getSheetAt(i);
\r
604 if(sheet == null) {
\r
605 log.warn("sheet[{}] is null. skip", i);
\r
608 else if((sheet instanceof HSSFSheet && ((HSSFSheet)sheet).getDialog()) || (sheet instanceof XSSFDialogsheet)) {
\r
609 log.debug("sheet[{}] is dialog sheet. skip", i);
\r
612 else if(sheet instanceof XSSFChartSheet) {
\r
613 log.debug("sheet[{}] is chart sheet. skip", i);
\r
617 log.debug("sheet[{}] name: {}", i, sheet.getSheetName());
\r
624 book.setForceFormulaRecalculation(true);
\r
626 log.trace("makeBook end");
\r
631 * @param sheet ワークシートオブジェクト
\r
633 private void makeSheet(Sheet sheet) {
\r
634 log.trace("makeSheet start");
\r
636 Row row; // 行オブジェクト
\r
637 Cell cell; // セルブジェクト
\r
638 TLParser tlParser; // パーサ
\r
641 context.setCurrentSheet(sheet);
\r
642 tlParser = new TLParser(context);
\r
645 int lastRowNum = sheet.getLastRowNum();
\r
646 log.debug("lastRowNum: {}", lastRowNum);
\r
652 for(int rowIdx = 0; rowIdx <= lastRowNum; rowIdx++) {
\r
654 row = sheet.getRow(rowIdx);
\r
658 log.debug("row[{}] is null", (rowIdx + 1));
\r
663 if(row.getLastCellNum() > maxColNum) {
\r
664 maxColNum = row.getLastCellNum();
\r
665 log.debug("maxColNum: {}", maxColNum);
\r
669 for(int cellIdx = 0; cellIdx <= maxColNum; cellIdx++) {
\r
671 cell = row.getCell(cellIdx);
\r
675 log.debug("cell[{}] is null", (new CellReference(rowIdx, cellIdx)).formatAsString());
\r
680 switch (cell.getCellType()) {
\r
682 case Cell.CELL_TYPE_STRING:
\r
683 case Cell.CELL_TYPE_FORMULA:
\r
685 tlParser.parse(cell);
\r
687 if(tlParser.isReParseCell()) {
\r
694 log.debug("cell type is not string or formula");
\r
699 if(row.getLastCellNum() > maxColNum) {
\r
700 maxColNum = row.getLastCellNum();
\r
701 log.debug("maxColNum update: {}", maxColNum);
\r
705 if(lastRowNum < sheet.getLastRowNum()) {
\r
706 lastRowNum = sheet.getLastRowNum();
\r
707 log.debug("lastRowNum update: {}", lastRowNum);
\r
710 log.trace("transformSheet end");
\r