2 * Copyright 2014 Hanei Management Co.,Ltd.
4 * This file is part of Jaxcel
6 * Jaxcel is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Jaxcel is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package org.hanei.jaxcel.parser;
21 import java.util.ArrayList;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
26 import org.apache.commons.lang3.BooleanUtils;
27 import org.apache.commons.lang3.math.NumberUtils;
28 import org.apache.poi.POIXMLDocumentPart;
29 import org.apache.poi.hssf.record.cf.CellRangeUtil;
30 import org.apache.poi.ss.usermodel.Cell;
31 import org.apache.poi.ss.usermodel.Row;
32 import org.apache.poi.ss.usermodel.Sheet;
33 import org.apache.poi.ss.util.CellRangeAddress;
34 import org.apache.poi.ss.util.CellReference;
35 import org.apache.poi.xssf.usermodel.XSSFDrawing;
36 import org.apache.poi.xssf.usermodel.XSSFSheet;
37 import org.hanei.jaxcel.report.ELManager;
38 import org.hanei.jaxcel.report.JaxcelContext;
39 import org.hanei.jaxcel.util.ExcelUtil;
40 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
41 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
42 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
48 * @author Noboru Saito
50 public class ForeachParser extends AbstractTLParser {
52 private static final String ROWS = "rows";
53 private static final String COLS = "cols";
54 private static final String DIRECTION = "direction";
55 private static final String STYLE = "style";
56 private static final String SHIFT = "shift";
57 private static final String BLOCK = "block";
58 private static final String START = "start";
59 private static final String END = "end";
61 private static final String IN_PHRASE = " in ";
63 private static final String ROW = "row";
64 private static final String COL = "col";
65 private static final String COPY = "copy";
66 private static final int IDX_BASE = 1;
67 private static final int SPAN_DEF = 1;
69 public void parse(JaxcelContext context) {
70 log.trace("parse start");
72 Sheet sheet = context.getCurrentSheet();
73 Cell cell = context.getCurrentCell();
74 ELManager elMgr = context.getElManager();
75 Map<String, Object> attributeMap = context.getAttributeMap();
76 String expression = null;
79 String direction = ROW;
80 boolean shift = false;
83 String start = null; // start
84 String end = null; // end
87 int rowSpan = SPAN_DEF; // row
88 int colSpan = SPAN_DEF; // column
91 Cell fromCell, toCell;
93 Pattern pattern = null;
94 Matcher matcher = null;
97 Object[] mapKeys = null;
99 ArrayList<CellRangeAddress> rangeList = new ArrayList<>();
100 CellRangeAddress fromRange, toRange, tmpBfRange, tmpAfRange;
102 // セルタイプのチェック、指示子をクリア
103 switch(cell.getCellType()) {
104 case Cell.CELL_TYPE_STRING:
105 cell.setCellValue(ExcelUtil.replaceFirstCellValue(cell, context.getTemplateString(), ""));
108 // セルタイプが文字列でない場合パース終了
109 log.error("cell type is not strng");
110 log.trace("parse end");
115 if(attributeMap.containsKey(Constants.EXPRESSION)) {
116 expression = (String) attributeMap.get(Constants.EXPRESSION);
117 log.debug("expression: {}", expression);
118 String[] tmp = expression.split(IN_PHRASE, 2);
120 object = tmp[0].trim();
121 list = tmp[1].trim();
124 log.error("format error: ex) object in list");
125 log.trace("parse end");
131 log.error("expression is null");
132 log.trace("parse end");
136 listObject = elMgr.evaluate(list);
137 if(listObject == null) {
138 log.error("list is null eval response.");
139 log.trace("parse end");
142 log.debug("list class: {}", listObject.getClass().getName());
144 // listObject Mapならキー取得
145 if(listObject instanceof Map) {
146 mapKeys = ((Map<?, ?>)listObject).keySet().toArray();
147 log.debug("mapKeys: {}", mapKeys);
151 evalObject = elMgr.evaluate("size(" + list + ")");
152 // evalした結果がintでなければリスト不可なオブジェクトと判断
153 if(evalObject == null) {
154 log.error("list size unknown");
155 log.trace("parse end");
158 else if(!(evalObject instanceof Integer)) {
159 log.error("list size is not Integer instance");
160 log.trace("parse end");
163 listSize = (int) evalObject;
164 log.debug("list size: {}", listSize);
167 // direction:"row|col" 繰返し方向 デフォルト row
168 if(attributeMap.containsKey(DIRECTION)) {
169 direction = (String) attributeMap.get(DIRECTION);
170 if(!ROW.equalsIgnoreCase(direction) && !COL.equalsIgnoreCase(direction)) {
171 log.warn("{} is illegal argument. set default: {}", DIRECTION, ROW);
175 // shift:"true|false" foreachレンジより後のセルに対するシフト指定 デフォルト false
176 if(attributeMap.containsKey(SHIFT)) {
177 shift = BooleanUtils.toBoolean((String) attributeMap.get(SHIFT));
179 // blick:"true|false" foreachレンジをブロック範囲で繰り返すかの指定 デフォルト true
180 if(attributeMap.containsKey(BLOCK)) {
181 block = BooleanUtils.toBoolean((String) attributeMap.get(BLOCK));
183 // style:"copy|^copy" foreachレンジのセルスタイルのコピー指定 デフォルト copy
184 if(attributeMap.containsKey(STYLE)) {
185 style = (String) attributeMap.get(STYLE);
187 // start:"numberExpression"
188 if(attributeMap.containsKey(START)) {
189 start = (String) attributeMap.get(START);
191 // end:"numberExpression"
192 if(attributeMap.containsKey(END)) {
193 end = (String) attributeMap.get(END);
195 // rows:"number" 範囲 デフォルト 1
196 if(attributeMap.containsKey(ROWS)) {
197 rowSpan = NumberUtils.toInt((String) attributeMap.get(ROWS));
199 log.warn("{} is illegal argument. set default: {}", ROWS, SPAN_DEF);
203 // cols:"number" 範囲 デフォルト 1
204 if(attributeMap.containsKey(COLS)) {
205 colSpan = NumberUtils.toInt((String) attributeMap.get(COLS));
207 log.warn("{} is illegal argument. set default: {}", COLS, SPAN_DEF);
214 // size(list) 等 式の可能性もあるのでevalする
215 evalObject = elMgr.evaluate(start);
216 if(evalObject != null) {
217 // evalした結果がintでなければデフォルト
218 if(!(evalObject instanceof Integer)) {
219 log.warn("start is not Integer instance. start set default: {}", IDX_BASE);
223 startIdx = (int) evalObject;
226 // startIdxが0の場合、1に補正
229 else if(startIdx < 0) {
230 // startIdxがマイナス値の場合、listのサイズからの逆順とする
232 startIdx = listSize + startIdx + IDX_BASE;
234 else if(startIdx > listSize) {
235 // startIdxがリストのサイズより大きい場合、リストのサイズに切り詰める
240 // evalした結果がnullの場合はデフォルト
242 log.warn("start is null eval response. start set default: {}", IDX_BASE);
247 log.debug("start is null. start set default: {}", IDX_BASE);
253 // size(list) 等 式の可能性もあるのでevalする
254 evalObject = elMgr.evaluate(end);
255 if(evalObject != null) {
256 // evalした結果がintでなければリストサイズ
257 if(!(evalObject instanceof Integer)) {
258 log.warn("end is not Integer instance. end set list size: {}", listSize);
262 endIdx = (int) evalObject;
265 // endIdxが0の場合、リストサイズ
268 else if(endIdx < 0) {
269 // endIdxがマイナス値の場合、listのサイズからの逆順とする
271 endIdx = listSize + endIdx + IDX_BASE;
273 else if(endIdx > listSize) {
274 // endIdxがリストのサイズより大きい場合、リストのサイズに切り詰める
279 // evalした結果がnullの場合はリストサイズ
281 log.warn("end is null eval response. end set list size: {}", listSize);
286 log.debug("end is null. end set list size: {}", listSize);
290 if(log.isDebugEnabled()) {
291 log.debug("object: {}", object);
292 log.debug("list: {}", list);
293 log.debug("start: {}", startIdx);
294 log.debug("end: {}", endIdx);
295 log.debug("rows: {}", rowSpan);
296 log.debug("cols: {}", colSpan);
297 log.debug("direction: {}", direction);
298 log.debug("shift: {}", shift);
299 log.debug("block: {}", block);
300 log.debug("style: {}", style);
303 // コピー元範囲 block:falseの場合、行・列全体に範囲を広げる
305 // 繰返し方向横(列)(行がデフォルト)
306 if(COL.equalsIgnoreCase(direction)) {
307 fromRange = new CellRangeAddress(sheet.getFirstRowNum(), sheet.getLastRowNum(), cell.getColumnIndex(), cell.getColumnIndex() + colSpan - 1);
311 fromRange = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + rowSpan - 1, 0, ExcelUtil.getLastColNum(sheet));
315 fromRange = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + rowSpan - 1, cell.getColumnIndex(), cell.getColumnIndex() + colSpan - 1);
318 fromRange = ExcelUtil.getIntersectRange(sheet, fromRange);
320 // 繰返しコピー回数1以上なら、コピー先範囲シフト・クリア等実施
321 if(startIdx != endIdx) {
323 int copyCount = startIdx < endIdx ? endIdx - startIdx : startIdx - endIdx;
324 // 繰返し方向横(列)(行がデフォルト)
325 if(COL.equalsIgnoreCase(direction)) {
326 toRange = new CellRangeAddress(
327 fromRange.getFirstRow(),
328 fromRange.getLastRow(),
329 fromRange.getLastColumn() + 1,
330 fromRange.getLastColumn() + (fromRange.getLastColumn() - fromRange.getFirstColumn() + 1) * copyCount);
334 toRange = new CellRangeAddress(
335 fromRange.getLastRow() + 1,
336 fromRange.getLastRow() + (fromRange.getLastRow() - fromRange.getFirstRow() + 1) * copyCount,
337 fromRange.getFirstColumn(),
338 fromRange.getLastColumn());
341 // shift:trueの場合、コピー先範囲の後続範囲をシフト
344 int distance = (startIdx > endIdx ? startIdx - endIdx : endIdx - startIdx);
346 // 繰返し方向横(列)(行がデフォルト)
347 if(COL.equalsIgnoreCase(direction)) {
350 ExcelUtil.shift(sheet, new CellRangeAddress(fromRange.getFirstRow(), fromRange.getLastRow(), fromRange.getLastColumn() + 1, fromRange.getLastColumn() + 1), COL, distance, block);
356 ExcelUtil.shift(sheet, new CellRangeAddress(fromRange.getLastRow() + 1, fromRange.getLastRow() + 1, fromRange.getFirstColumn(), fromRange.getLastColumn()), ROW, distance, block);
359 // shift:falseの場合、コピー先範囲をクリア
361 ExcelUtil.clearRange(sheet, toRange, COPY.equalsIgnoreCase(style), COPY.equalsIgnoreCase(style));
363 // style:copyの場合、コピー元範囲の結合保持・解除
364 if(COPY.equalsIgnoreCase(style)) {
365 // コピー元範囲の結合状態チェック・保持。コピー先範囲の結合解除
366 CellRangeAddress tmpRange;
367 for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
368 tmpRange = sheet.getMergedRegion(i);
370 switch(CellRangeUtil.intersect(fromRange, tmpRange)) {
371 case CellRangeUtil.INSIDE:
372 rangeList.add(tmpRange);
373 log.debug("from range inside mergedRegion. save: {}", tmpRange.formatAsString());
375 case CellRangeUtil.ENCLOSES:
376 log.warn("from range encloses mergedRegion. remove: {}", tmpRange.formatAsString());
377 sheet.removeMergedRegion(i);
379 case CellRangeUtil.OVERLAP:
380 log.warn("from range overlap mergedRegion. remove: {}", tmpRange.formatAsString());
381 sheet.removeMergedRegion(i);
389 for(int r = fromRange.getFirstRow(); r <= fromRange.getLastRow(); r++) {
391 fromRow = sheet.getRow(r);
396 for(int c = fromRange.getFirstColumn(); c <= fromRange.getLastColumn(); c++) {
398 fromCell = fromRow == null ? null : fromRow.getCell(c);
399 // コピー元セルが数式か文字列の場合バリュー保持
400 fromCellValue = null;
401 if(fromCell != null && (fromCell.getCellType() == Cell.CELL_TYPE_FORMULA || fromCell.getCellType() == Cell.CELL_TYPE_STRING)) {
402 switch(fromCell.getCellType()) {
403 case Cell.CELL_TYPE_FORMULA:
404 fromCellValue = fromCell.getCellFormula();
406 case Cell.CELL_TYPE_STRING:
407 fromCellValue = fromCell.getStringCellValue();
410 pattern = Pattern.compile("(" + Pattern.quote("${") + "[^\\$\\{\\}]*?\\b+)(" + Pattern.quote(object) + ")([^a-zA-Z_0-9\\$]+?)");
411 matcher = pattern.matcher(fromCellValue);
415 for(int i = startIdx, cpCount = 0; (startIdx <= endIdx && i <= endIdx || startIdx > endIdx && i >= endIdx); i = (startIdx <= endIdx ? i + 1 : i - 1), cpCount++) {
416 // 繰返し方向横(列)(行がデフォルト)
417 if(COL.equalsIgnoreCase(direction)) {
419 if(c + colSpan * cpCount > ExcelUtil.getMaxColumnIndex(sheet)) {
421 log.debug("break col loop. to cell [{}] is outside sheet", c + colSpan * cpCount + 1);
424 // コピー元行がnullならコピー先行もnull
425 if(fromRow == null) {
426 log.debug("break col loop. from row [{}] and to row [{}] is null", (r + 1), (r + 1));
433 if(r + rowSpan * cpCount > ExcelUtil.getMaxRowIndex(sheet)) {
435 log.debug("break copy loop. to row [{}] is outside sheet", r + rowSpan * cpCount + 1);
439 toRow = sheet.getRow(r + rowSpan * cpCount);
440 // コピー元行がnullならコピー先行もnull。continue
441 if(fromRow == null) {
443 log.debug("continue. from row [{}] is null. to row [{}] remove", (r + 1), (toRow.getRowNum() + 1));
444 sheet.removeRow(toRow);
447 log.debug("continue. from row [{}] and to row [{}] is null", (r + 1), ((ROW.equalsIgnoreCase(direction) ? (r + rowSpan * cpCount) : r) + 1));
454 if(toRow == null) toRow = sheet.createRow(r + rowSpan * cpCount);
456 // コピー元行・コピー先行 行番号が異なり、block false、繰返し方向縦(行)なら行情報(高さ)もコピー(範囲列先頭のみ)
457 if(cpCount > 0 && !block && c == fromRange.getFirstColumn() && ROW.equalsIgnoreCase(direction))
458 toRow.setHeight(fromRow.getHeight());
461 toCell = toRow.getCell(COL.equalsIgnoreCase(direction) ? c + colSpan * cpCount : c);
462 // コピー元セルがnullならコピー先セルもnull
463 if(fromCell == null) {
465 log.debug("continue. from cell [{}] is null. to cell [{}] remove", (new CellReference(fromRow.getRowNum(), c)).formatAsString(), (new CellReference(toCell.getRowIndex(), toCell.getColumnIndex())).formatAsString());
466 toRow.removeCell(toCell);
469 log.debug("continue. from cell [{}] and to cell [{}] is null", (new CellReference(fromRow.getRowNum(), c)).formatAsString(), (new CellReference(toRow.getRowNum(), COL.equalsIgnoreCase(direction) ? c + colSpan * cpCount : c)).formatAsString());
474 if(toCell == null) toCell = toRow.createCell(COL.equalsIgnoreCase(direction) ? c + colSpan * cpCount : c);
476 // コピー元列・コピー先列 列番号が異なり、block false、繰返し方向横(列)なら列情報(幅)もコピー(範囲行先頭のみ)
477 if(cpCount > 0 && !block && r == fromRange.getFirstRow() && COL.equalsIgnoreCase(direction))
478 sheet.setColumnWidth(toCell.getColumnIndex(), sheet.getColumnWidth(fromCell.getColumnIndex()));
481 if(cpCount > 0 && COPY.equalsIgnoreCase(style)) {
483 toCell.setCellStyle(fromCell.getCellStyle());
486 switch(fromCell.getCellType()) {
487 case Cell.CELL_TYPE_BLANK:
488 toCell.setCellType(Cell.CELL_TYPE_BLANK);
490 case Cell.CELL_TYPE_BOOLEAN:
491 toCell.setCellType(Cell.CELL_TYPE_BOOLEAN);
492 toCell.setCellValue(fromCell.getBooleanCellValue());
494 case Cell.CELL_TYPE_ERROR:
495 toCell.setCellType(Cell.CELL_TYPE_ERROR);
496 toCell.setCellErrorValue(fromCell.getErrorCellValue());
498 case Cell.CELL_TYPE_NUMERIC:
499 toCell.setCellType(Cell.CELL_TYPE_NUMERIC);
500 toCell.setCellValue(fromCell.getNumericCellValue());
502 case Cell.CELL_TYPE_FORMULA:
503 toCell.setCellType(Cell.CELL_TYPE_FORMULA);
505 toCell.setCellFormula(
506 ExcelUtil.getMoveFormula(
508 matcher.replaceAll("$1" + Matcher.quoteReplacement("(" + list + ")[" + (mapKeys == null ? i - 1 : "'" + mapKeys[i - 1] + "'") + "]") + "$3"),
509 toCell.getRowIndex() - fromCell.getRowIndex(),
510 toCell.getColumnIndex() - fromCell.getColumnIndex()
513 log.debug("to cell Formula: {}", toCell.getCellFormula());
515 case Cell.CELL_TYPE_STRING:
516 toCell.setCellType(Cell.CELL_TYPE_STRING);
517 // log.debug("fromCell : {}", fromCell.getStringCellValue());
518 // log.debug("toCell : {}", toCell.getStringCellValue());
519 // log.debug("i : {}", i);
520 // log.debug("cpCount : {}", cpCount);
521 // log.debug("regex : {}", "(" + Pattern.quote("${") + "[^\\$\\{\\}]*?\\b+)(" + Pattern.quote(object) + ")([^a-zA-Z_0-9\\$]+?)");
522 // log.debug("replacement: {}", "$1" + Matcher.quoteReplacement("(" + list + ")[" + (mapKeys == null ? i - 1 : "'" + mapKeys[i - 1] + "'") + "]") + "$3");
523 // log.debug("replaceAll: {}", matcher.replaceAll("$1" + Matcher.quoteReplacement("(" + list + ")[" + (mapKeys == null ? i - 1 : "'" + mapKeys[i - 1] + "'") + "]") + "$3"));
524 toCell.setCellValue(matcher.replaceAll("$1" + Matcher.quoteReplacement("(" + list + ")[" + (mapKeys == null ? i - 1 : "'" + mapKeys[i - 1] + "'") + "]") + "$3"));
525 log.debug("to cell string: {}", toCell.getStringCellValue());
533 if(COPY.equalsIgnoreCase(style)) {
537 if(COL.equalsIgnoreCase(direction)) {
545 if(!rangeList.isEmpty()) {
548 for(int c = 0; c < rangeList.size(); c++) {
549 tmpBfRange = rangeList.get(c);
551 for(int i = startIdx, cpCount = 0; (startIdx <= endIdx && i <= endIdx || startIdx > endIdx && i >= endIdx); i = (startIdx <= endIdx ? i + 1 : i - 1), cpCount++) {
552 // 初回はコピーしない(コピー元だから)
553 if(i == startIdx) continue;
555 tmpAfRange = new CellRangeAddress(
556 tmpBfRange.getFirstRow() + (addRow * cpCount),
557 tmpBfRange.getLastRow() + (addRow * cpCount),
558 tmpBfRange.getFirstColumn() + (addCol * cpCount),
559 tmpBfRange.getLastColumn() + (addCol * cpCount));
560 if(ExcelUtil.validateRange(sheet, tmpAfRange)) {
561 sheet.addMergedRegion(tmpAfRange);
562 log.debug("mergedRegion copy. from: [{}] to: [{}]", tmpBfRange.formatAsString(), tmpAfRange.formatAsString());
568 // オブジェクトのコピー Excel2007以降 ooxml形式のみ対応
569 if(sheet instanceof XSSFSheet) {
572 CTTwoCellAnchor fAnchor, cpAnchor;
576 for(POIXMLDocumentPart part : ((XSSFSheet)sheet).getRelations()) {
577 if(part == null) continue;
578 log.debug("DocumentPart class: {}", part.getClass().getName());
579 // DocumentPartがDrawingオブジェクトの場合
580 if(part instanceof XSSFDrawing) {
581 ctDrawing = ((XSSFDrawing) part).getCTDrawing();
582 if(ctDrawing != null) {
584 int alSize = ctDrawing.getTwoCellAnchorList().size();
585 for (int i = 0; i < alSize; i++) {
586 fAnchor = ctDrawing.getTwoCellAnchorList().get(i);
587 // GraphicFrameをもつグラフ、スマートアートは現状非対応
588 if(fAnchor.isSetGraphicFrame()) continue;
591 from = fAnchor.getFrom();
594 to = fAnchor.getTo();
597 tmpBfRange = new CellRangeAddress(r1, r2, c1, c2);
599 // コピー元レンジに含まれている、掛かっているならコピー
600 switch(CellRangeUtil.intersect(fromRange, tmpBfRange)) {
601 case CellRangeUtil.INSIDE:
602 case CellRangeUtil.OVERLAP:
604 for(int j = startIdx, cpCount = 0; (startIdx <= endIdx && j <= endIdx || startIdx > endIdx && j >= endIdx); j = (startIdx <= endIdx ? j + 1 : j - 1), cpCount++) {
605 // 初回はコピーしない(コピー元だから)
606 if(j == startIdx) continue;
608 cpAnchor = ctDrawing.addNewTwoCellAnchor();
609 cpAnchor.set(fAnchor.copy());
610 from = cpAnchor.getFrom();
611 from.setRow(from.getRow() + (addRow * cpCount));
612 from.setCol(from.getCol() + (addCol * cpCount));
613 to = cpAnchor.getTo();
614 to.setRow(to.getRow() + (addRow * cpCount));
615 to.setCol(to.getCol() + (addCol * cpCount));
616 if(log.isDebugEnabled()) {
617 tmpAfRange = new CellRangeAddress(from.getRow(), to.getRow(), from.getCol(), to.getCol());
618 log.debug("object copy from: [{}] to: [{}]", tmpBfRange.formatAsString(), tmpAfRange.formatAsString());
628 log.error("object copy error: " + e.getMessage(), e);
633 log.trace("parse end");