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.util;
21 import java.lang.reflect.Field;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26 import java.util.regex.PatternSyntaxException;
29 import org.apache.poi.POIXMLDocumentPart;
30 import org.apache.poi.hssf.record.cf.CellRangeUtil;
31 import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
32 import org.apache.poi.hssf.usermodel.HSSFSheet;
33 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
34 import org.apache.poi.ss.SpreadsheetVersion;
35 import org.apache.poi.ss.formula.FormulaParser;
36 import org.apache.poi.ss.formula.FormulaParsingWorkbook;
37 import org.apache.poi.ss.formula.FormulaRenderer;
38 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
39 import org.apache.poi.ss.formula.FormulaType;
40 import org.apache.poi.ss.formula.ptg.AreaPtg;
41 import org.apache.poi.ss.formula.ptg.Ptg;
42 import org.apache.poi.ss.formula.ptg.RefPtg;
43 import org.apache.poi.ss.usermodel.Cell;
44 import org.apache.poi.ss.usermodel.Row;
45 import org.apache.poi.ss.usermodel.Sheet;
46 import org.apache.poi.ss.util.CellRangeAddress;
47 import org.apache.poi.ss.util.CellReference;
48 import org.apache.poi.xssf.streaming.SXSSFSheet;
49 import org.apache.poi.xssf.usermodel.XSSFDrawing;
50 import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
51 import org.apache.poi.xssf.usermodel.XSSFHyperlink;
52 import org.apache.poi.xssf.usermodel.XSSFSheet;
53 import org.apache.poi.xssf.usermodel.XSSFTable;
54 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
55 import org.hanei.jaxcel.exception.JaxcelInputException;
56 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
57 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
58 import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
59 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter;
60 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
61 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTablePart;
62 import org.slf4j.LoggerFactory;
63 import org.slf4j.Logger;
69 * @author Noboru Saito
71 public class ExcelUtil {
73 private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
76 public static final String INSIDE = "inside";
77 public static final String OVERLAP = "overlap";
78 public static final String ENCLOSES = "encloses";
79 public static final String NO_INTERSECTION = "no_intersection";
84 * @param sheet Worksheetオブジェクト
88 * @throws JaxcelInputException 入力例外発生時
90 public static int getMaxRowIndex(Sheet sheet) {
91 log.trace("getMaxRowIndex start");
94 log.error("sheet is null");
95 throw new JaxcelInputException("sheet is null");
98 if(sheet instanceof HSSFSheet) {
99 max = SpreadsheetVersion.EXCEL97.getLastRowIndex();
101 else if(sheet instanceof XSSFSheet || sheet instanceof SXSSFSheet){
102 max = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
105 log.error("sheet is unsupported type");
106 throw new JaxcelInputException("sheet is unsupported type");
109 log.trace("getMaxRowIndex end: {}", max);
116 * @param sheet Worksheetオブジェクト
120 * @throws JaxcelInputException 入力例外発生時
122 public static int getMaxColumnIndex(Sheet sheet) {
123 log.trace("getMaxColumnIndex start");
126 log.error("sheet is null");
127 throw new JaxcelInputException("sheet is null");
130 if(sheet instanceof HSSFSheet) {
131 max = SpreadsheetVersion.EXCEL97.getLastColumnIndex();
133 else if(sheet instanceof XSSFSheet || sheet instanceof SXSSFSheet){
134 max = SpreadsheetVersion.EXCEL2007.getLastColumnIndex();
137 log.error("sheet is unsupported type");
138 throw new JaxcelInputException("sheet is null");
141 log.trace("getMaxColumnIndex end: {}", max);
148 * @param sheet Worksheetオブジェクト
153 * @throws JaxcelInputException 入力例外発生時
155 public static boolean validateRange(Sheet sheet, CellRangeAddress range) {
156 log.trace("validateRange start");
158 boolean ret = validateColumn(sheet, range) && validateRow(sheet, range);
160 log.trace("validateRange end: {}", ret);
167 * @param sheet Worksheetオブジェクト
172 * @throws JaxcelInputException 入力例外発生時
174 public static boolean validateColumn(Sheet sheet, CellRangeAddress range) {
175 log.trace("validateColumn start");
178 log.error("range is null");
179 throw new JaxcelInputException("range is null");
182 int max = getMaxColumnIndex(sheet);
183 int firstColumn = range.getFirstColumn();
184 int lastColumn = range.getLastColumn();
186 if((isFullRowRange(sheet, range)) || (firstColumn <= lastColumn && firstColumn >= 0 && firstColumn <= max && lastColumn >= 0 && lastColumn <= max)){
190 log.trace("validateColumn end: {}", ret);
197 * @param sheet Worksheetオブジェクト
202 * @throws JaxcelInputException 入力例外発生時
204 public static boolean validateRow(Sheet sheet, CellRangeAddress range) {
205 log.trace("validateRow start");
208 log.error("range is null");
209 throw new JaxcelInputException("range is null");
212 int max = getMaxRowIndex(sheet);
213 int firstRow = range.getFirstRow();
214 int lastRow = range.getLastRow();
216 if((isFullColumnRange(sheet, range)) || (firstRow <= lastRow && firstRow >= 0 && firstRow <= max && lastRow >= 0 && lastRow <= max)){
220 log.trace("validateRow end: {}", ret);
225 * 行全体を指定している範囲であるかの判定
227 * @param sheet Worksheetオブジェクト
232 * @throws JaxcelInputException 入力例外発生時
234 public static boolean isFullRowRange(Sheet sheet, CellRangeAddress range) {
235 log.trace("isFullRowRange start");
238 log.error("range is null");
239 throw new JaxcelInputException("range is null");
242 int max = getMaxColumnIndex(sheet);
243 int firstColumn = range.getFirstColumn();
244 int lastColumn = range.getLastColumn();
246 if(log.isDebugEnabled()) {
247 log.debug("sheet max column: {}", max);
248 log.debug("range first column: {}", firstColumn);
249 log.debug("range last column: {}", lastColumn);
252 if((firstColumn == 0 && lastColumn == max) || (firstColumn == -1 && lastColumn == -1)) {
256 log.trace("isFullRowRange end: {}", ret);
261 * 列全体を指定している範囲であるかの判定
263 * @param sheet Worksheetオブジェクト
268 public static boolean isFullColumnRange(Sheet sheet, CellRangeAddress range) {
269 log.trace("isFullColumnRange start");
272 log.error("range is null");
273 throw new JaxcelInputException("range is null");
276 int max = getMaxRowIndex(sheet);
277 int firstRow = range.getFirstRow();
278 int lastRow = range.getLastRow();
280 if(log.isDebugEnabled()) {
281 log.debug("sheet max row: {}", max);
282 log.debug("range first row: {}", firstRow);
283 log.debug("range last row: {}", lastRow);
286 if((firstRow == 0 && lastRow == max) || (firstRow == -1 && lastRow == -1)) {
290 log.trace("isFullColumnRange end: {}", ret);
297 * @param sheet Worksheetオブジェクト
299 * @return シートの最大列数。<br>
300 * sheetに行データが存在しない場合、または、すべての行にセルが存在しない場合は -1
302 * @throws JaxcelInputException 入力例外発生時
304 public static int getLastColNum(Sheet sheet) {
305 log.trace("getLastColNum start");
308 log.error("sheet is null");
309 throw new JaxcelInputException("sheet is null");
311 int maxColNum = getLastColNum(sheet, sheet.getFirstRowNum(), sheet.getLastRowNum());
313 log.trace("getLastColNum end: {}", maxColNum);
320 * @param sheet Worksheetオブジェクト
321 * @param firstRow 範囲始点行番号(0起点)
322 * @param lastRow 範囲終点行番号(0起点)
324 * @return シート指定列範囲の最大列数。<br>
325 * sheetの指定範囲に行データが存在しない場合は -1
326 * sheetの指定範囲に行データが存在しない場合、または、指定範囲のすべての行にセルが存在しない場合は -1
328 * @throws JaxcelInputException 入力例外発生時
330 public static int getLastColNum(Sheet sheet, int firstRow, int lastRow) {
331 log.trace("getLastColNum start");
334 log.error("sheet is null");
335 throw new JaxcelInputException("sheet is null");
341 if(fRow == -1 && lRow == -1) {
342 log.info("firstRow and lastRow is -1. set full row range");
344 lRow = sheet.getLastRowNum();
347 log.error("firstRow is lt 0: {}", fRow);
348 throw new JaxcelInputException("firstRow is lt 0");
351 log.error("lastRow is lt 0: {}", lRow);
352 throw new JaxcelInputException("lastRow is lt 0");
355 log.warn("firstRow gt lastRow: {}, {}. swap", fRow, lRow);
363 for(int i = fRow; i <= lRow; i++) {
364 row = sheet.getRow(i);
365 if(row == null) continue;
366 maxColNum = (row.getLastCellNum() > maxColNum ? row.getLastCellNum() : maxColNum);
369 log.trace("getLastColNum end: {}", maxColNum);
376 * @param sheet Worksheetオブジェクト
377 * @param range シフト対象範囲
378 * @param direction シフト方向。row:行方向(デフォルト) col:列方向
379 * @param distance シフト距離。正数:右・下 負数:左・上
381 * @throws JaxcelInputException 入力例外発生時
383 public static void shift(Sheet sheet, CellRangeAddress range, String direction, int distance) {
384 shift(sheet, range, direction, distance, true);
390 * @param sheet Worksheetオブジェクト
391 * @param range シフト対象範囲
392 * @param direction シフト方向。row:行方向(デフォルト) col:列方向
393 * @param distance シフト距離。正数:右・下 負数:左・上
394 * @param block ブロック指定。true:ブロック指定 false:列・行単位指定
396 * @throws JaxcelInputException 入力例外発生時
398 public static void shift(Sheet sheet, CellRangeAddress range, String direction, int distance, boolean block) {
399 log.trace("shift start");
401 final String ROW = "row";
402 final String COL = "col";
404 String _direction; // シフト方向
405 CellRangeAddress _range;
409 log.error("sheet is null");
410 throw new JaxcelInputException("sheet is null");
413 log.error("range is null");
414 throw new JaxcelInputException("range is null");
417 if(!validateRange(sheet, range)) {
418 _range = getIntersectRange(sheet, range);
420 log.warn("range is illegal: [{}]", range.formatAsString());
421 log.trace("shift end");
425 log.info("resize range: [{}]", _range.formatAsString());
429 log.debug("distance is 0");
430 log.trace("shift end");
433 if(direction == null) {
434 log.debug("direction is null. set default: {}", ROW);
437 else if(!COL.equalsIgnoreCase(direction) && !ROW.equalsIgnoreCase(direction)) {
438 log.debug("direction is illegal argument. set default: {}", ROW);
442 _direction = direction.toLowerCase();
445 if(log.isDebugEnabled()) {
446 log.debug("sheet: {}", sheet.getSheetName());
447 log.debug("range: [{}]", _range.formatAsString());
448 log.debug("distance: {}", distance);
449 log.debug("direction: {}", _direction);
450 log.debug("block: {}", block);
453 // シフト範囲の特定 引数の範囲以降を移動対象に含める
454 CellRangeAddress fromRange;
455 int rowDistance, colDistance;
456 int firstIdx, lastIdx;
459 if(COL.equals(_direction)) {
461 firstIdx = _range.getFirstColumn();
464 lastIdx = block ? getLastColNum(sheet, _range.getFirstRow(), _range.getLastRow()) : getLastColNum(sheet);
465 lastIdx = lastIdx > firstIdx ? lastIdx : firstIdx;
468 // block範囲指定でなければ行範囲拡大
470 fromRange = new CellRangeAddress(
471 _range.getFirstRow(), _range.getLastRow(),
475 fromRange = new CellRangeAddress(
476 sheet.getFirstRowNum(), sheet.getLastRowNum(),
482 colDistance = distance;
487 firstIdx = _range.getFirstRow();
489 lastIdx = sheet.getLastRowNum() > firstIdx ? sheet.getLastRowNum() : firstIdx;
494 fromRange = new CellRangeAddress(
496 _range.getFirstColumn(), _range.getLastColumn());
499 fromRange = new CellRangeAddress(
501 0, getLastColNum(sheet));
505 rowDistance = distance;
510 moveRange(sheet, fromRange, rowDistance, colDistance, block);
512 log.trace("shift end");
516 * 数式に使用されている相対参照を指定距離で移動した結果を返却<br>
517 * 配列数式はPOIの制約上使用できません
519 * @param sheet Worksheetオブジェクト
521 * @param rowDistance 行方向移動距離。正数:下 負数:上
522 * @param colDistance 列方向移動距離。正数:右 負数:左
524 * @return 相対参照を指定距離で移動した結果の数式<br>
525 * 移動した結果、数式内の参照がシート範囲外を指定したとしてもワークシートとしては異常とならない為、移動距離の妥当性チェックは行わない<br>
526 * 配列数式等、POIの制約上解析不可能な数式の場合は引数のまま返却する
528 * @throws JaxcelInputException 入力例外発生時
530 public static String getMoveFormula(Sheet sheet, String formula, int rowDistance, int colDistance) {
531 log.trace("getMoveFormula start");
535 log.warn("sheet is null");
538 if(formula == null) {
539 log.warn("formula is null");
542 if(rowDistance == 0 && colDistance == 0) {
543 log.debug("rowDistance and colDistance is 0");
544 log.trace("getMoveFormula end");
548 if(log.isDebugEnabled()) {
549 log.debug("sheet: {}", sheet.getSheetName());
550 log.debug("formula: {}", formula);
551 log.debug("rowDistance: {}", rowDistance);
552 log.debug("colDistance: {}", colDistance);
556 FormulaParsingWorkbook fpBook;
558 if(sheet instanceof HSSFSheet) {
559 fpBook = HSSFEvaluationWorkbook.create((HSSFWorkbook) sheet.getWorkbook());
562 else if(sheet instanceof XSSFSheet) {
563 fpBook = XSSFEvaluationWorkbook.create((XSSFWorkbook) sheet.getWorkbook());
566 log.warn("sheet is unsupported type");
567 log.trace("getMoveFormula end");
572 String returnFormula;
573 boolean parseFlg = false;
581 ptg = FormulaParser.parse(formula, fpBook, FormulaType.CELL, sheet.getWorkbook().getSheetIndex(sheet)); // TODO: FormulaType.CELL とは?
582 for (int i = 0; i < ptg.length; i++){
583 log.trace("from ptg: {}", ptg[i]);
585 if(ptg[i] instanceof AreaPtg) {
586 aPtg = (AreaPtg) ptg[i];
587 // 各行・列が相対参照であれば移動量を加算し書き換え
588 if(aPtg.isFirstRowRelative()) {
589 aPtg.setFirstRow(aPtg.getFirstRow() + rowDistance);
592 if(aPtg.isFirstColRelative()) {
593 aPtg.setFirstColumn(aPtg.getFirstColumn() + colDistance);
596 if(aPtg.isLastRowRelative()) {
597 aPtg.setLastRow(aPtg.getLastRow() + rowDistance);
600 if(aPtg.isLastColRelative()) {
601 aPtg.setLastColumn(aPtg.getLastColumn() + colDistance);
606 else if(ptg[i] instanceof RefPtg) {
607 rPtg = (RefPtg) ptg[i];
608 // 行・列が相対参照であれば移動量を加算し書き換え
609 if(rPtg.isRowRelative()) {
610 rPtg.setRow(rPtg.getRow() + rowDistance);
613 if(rPtg.isColRelative()) {
614 rPtg.setColumn(rPtg.getColumn() + colDistance);
618 log.trace("to ptg: {}", ptg[i]);
620 // 相対参照が存在し移動させた場合は数式の再構築
621 returnFormula = parseFlg ? FormulaRenderer.toFormulaString((FormulaRenderingWorkbook) fpBook, ptg) : formula;
624 log.error("formula parse error: {}", e.getMessage(), e);
625 returnFormula = formula;
628 log.trace("getMoveFormula end: {}", returnFormula);
629 return returnFormula;
635 * @param sheet Worksheetオブジェクト
636 * @param range 移動対象範囲
637 * @param clearStyle セルスタイルのクリア指定。trueでスタイルをクリアする
638 * @param clearMerge セル結合のクリア指定。trueで結合をクリアする
640 * @throws JaxcelInputException 入力例外発生時
642 public static void clearRange(Sheet sheet, CellRangeAddress range, boolean clearStyle, boolean clearMerge) {
643 log.trace("clearRange start");
645 CellRangeAddress tmpRange;
646 CellRangeAddress _range;
650 log.error("sheet is null");
651 throw new JaxcelInputException("sheet is null");
654 log.error("range is null");
655 throw new JaxcelInputException("range is null");
658 if(!validateRange(sheet, range)) {
659 _range = getIntersectRange(sheet, range);
661 log.warn("range is illegal: [{}]", range.formatAsString());
662 log.trace("clearRange end");
666 log.info("resize range: [{}]", _range.formatAsString());
669 if(log.isDebugEnabled()) {
670 log.debug("sheet: {}", sheet.getSheetName());
671 log.debug("range: [{}]", _range.formatAsString());
672 log.debug("clearStyle: {}", clearStyle);
673 log.debug("clearMerge: {}", clearMerge);
677 if(clearMerge && sheet.getNumMergedRegions() > 0) {
678 for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
680 tmpRange = sheet.getMergedRegion(i);
681 log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());
683 // 範囲内・範囲に掛かっている結合の解除
684 if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(_range, tmpRange)) {
685 log.debug("mergedRegion {}. clear: [{}]", getIntersectString(_range, tmpRange), tmpRange.formatAsString());
686 sheet.removeMergedRegion(i);
692 // オブジェクトの削除・クリア Excel2007以降 ooxml形式のみ対応
693 if(sheet instanceof XSSFSheet) {
694 XSSFSheet xSheet = (XSSFSheet) sheet;
698 CTAutoFilter af = xSheet.getCTWorksheet().getAutoFilter();
701 log.trace("auto filter renge: [{}]", ref);
703 // CellRangeAddressに変換し位置情報設定
704 tmpRange = CellRangeAddress.valueOf(ref);
705 // 範囲内に含まれている or 掛かっている オートフィルタクリア
706 switch(CellRangeUtil.intersect(_range, tmpRange)) {
707 case CellRangeUtil.INSIDE:
708 case CellRangeUtil.OVERLAP:
709 xSheet.getCTWorksheet().unsetAutoFilter();
710 log.debug("auto filter {}. clear: [{}]", getIntersectString(_range, tmpRange), ref);
717 CTTwoCellAnchor anchor;
718 List<CTTablePart> tableList;
720 for(POIXMLDocumentPart part : xSheet.getRelations()) {
721 if(part == null) continue;
722 log.debug("documentPart class: {}", part.getClass().getName());
724 // DocumentPartがテーブルの場合
725 if(part instanceof XSSFTable) {
727 table = ((XSSFTable) part).getCTTable();
729 ref = table.getRef();
730 log.trace("table range: [{}]", ref);
731 tmpRange = CellRangeAddress.valueOf(ref);
733 // 範囲内に含まれている or 掛かっている テーブルクリア
734 switch(CellRangeUtil.intersect(_range, tmpRange)) {
735 case CellRangeUtil.INSIDE:
736 case CellRangeUtil.OVERLAP:
737 // シート上に存在するテーブルと対象テーブルのIDが一致するものを検索しクリアする
738 tableList = xSheet.getCTWorksheet().getTableParts().getTablePartList();
739 for(int i = 0; i < tableList.size(); i++) {
740 if(tableList.get(i).getId() != null && tableList.get(i).getId().equalsIgnoreCase(part.getPackageRelationship().getId())) {
741 xSheet.getCTWorksheet().getTableParts().removeTablePart(i);
742 log.debug("table {}. clear: [{}]", getIntersectString(_range, tmpRange), ref);
748 // DocumentPartがDrawingオブジェクトの場合
749 else if(part instanceof XSSFDrawing) {
751 drawing = ((XSSFDrawing) part).getCTDrawing();
753 for(int i = 0; i < drawing.getTwoCellAnchorList().size(); i++) {
755 anchor = drawing.getTwoCellAnchorList().get(i);
756 tmpRange = new CellRangeAddress(anchor.getFrom().getRow(), anchor.getTo().getRow(), anchor.getFrom().getCol(), anchor.getTo().getCol());
757 log.debug("object range: [{}]", tmpRange.formatAsString());
759 // 範囲内に掛かっていない以外 オブジェクトの削除
760 if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(_range, tmpRange)) {
761 drawing.removeTwoCellAnchor(i);
762 log.debug("object {}. delete: [{}]", getIntersectString(_range, tmpRange), tmpRange.formatAsString());
773 for(int i = range.getFirstRow() == -1 ? 0 : range.getFirstRow(), ii = range.getLastRow() == - 1 ? getMaxRowIndex(sheet) : range.getLastRow(); i <= ii; i++) {
774 row = sheet.getRow(i);
775 if(row == null) continue;
777 for(int c = range.getFirstColumn() == -1 ? 0 : range.getFirstColumn(), cc = range.getLastColumn() == -1 ? getMaxColumnIndex(sheet) : range.getLastColumn(); c <= cc; c++) {
778 cell = row.getCell(c);
779 if(cell == null) continue;
781 clearHyperlink(sheet, cell);
783 cell.removeCellComment();
786 row.removeCell(cell);
788 // スタイルクリアでないならブランクセル
790 cell.setCellType(Cell.CELL_TYPE_BLANK);
792 log.debug("cell clear: [{}]", (new CellReference(cell).formatAsString()));
795 log.trace("clearRange end");
799 * 指定された範囲の交差状態を定数文字列で返却する<br>
802 * @param rangeA 対象範囲A
803 * @param rangeB 対象範囲B
805 * @return ExcelUtil.INSIDE:対象範囲Aに対象範囲Bが含まれている<br>
806 * ExcelUtil.OVERLAP:対象範囲Aに対象範囲Bの一部が交差している<br>
807 * ExcelUtil.ENCLOSES:対象範囲Bに対象範囲Aが含まれている<br>
808 * ExcelUtil.NO_INTERSECTION:対象範囲Aに対象範囲Bは一部の含まれていない
810 public static String getIntersectString(CellRangeAddress rangeA, CellRangeAddress rangeB) {
811 // 範囲内に含まれている or 掛かっている テーブルクリア
812 switch(CellRangeUtil.intersect(rangeA, rangeB)) {
813 case CellRangeUtil.NO_INTERSECTION:
814 return NO_INTERSECTION;
815 case CellRangeUtil.INSIDE:
817 case CellRangeUtil.ENCLOSES:
825 * 指定された範囲がシートに収まらない場合、収まるサイズにリサイズした範囲を返却する
827 * @param sheet Worksheetオブジェクト
830 * @return シートに収まる範囲<br>
831 * シートに収まる範囲が存在しない場合はnull
833 * @throws JaxcelInputException 入力例外発生時
835 public static CellRangeAddress getIntersectRange(Sheet sheet, CellRangeAddress range) {
836 log.trace("getIntersectRange start");
840 log.error("sheet is null");
841 throw new JaxcelInputException("sheet is null");
843 CellRangeAddress tmpRange = getIntersectRange(sheet, (new CellRangeAddress(0, getMaxRowIndex(sheet), 0, getMaxColumnIndex(sheet))), range);
845 log.trace("getIntersectRange end : [{}]", (tmpRange == null ? null : tmpRange.formatAsString()));
850 * 指定された範囲の交差する範囲を返却する<br>
852 * @param sheet Worksheetオブジェクト
853 * @param rangeA 対象範囲A
854 * @param rangeB 対象範囲B
857 * 交差する範囲が存在しない場合はnull
859 * @throws JaxcelInputException 入力例外発生時
861 public static CellRangeAddress getIntersectRange(Sheet sheet, CellRangeAddress rangeA, CellRangeAddress rangeB) {
862 log.trace("getIntersectRange start");
866 log.error("sheet is null");
867 throw new JaxcelInputException("sheet is null");
870 log.error("rangeA is null");
871 throw new JaxcelInputException("rangeA is null");
874 log.error("rangeB is null");
875 throw new JaxcelInputException("rangeA is null");
877 // if(!validateRange(sheet, rangeA)) {
878 // log.error("rangeA is illegal: [{}]", rangeA.formatAsString());
879 // throw new JaxcelInputException("rangeA is illegal");
881 // if(!validateRange(sheet, rangeB)) {
882 // log.error("rangeB is illegal: [{}]", rangeB.formatAsString());
883 // throw new JaxcelInputException("rangeB is illegal");
885 if(log.isDebugEnabled()) {
886 log.debug("sheet: {}", sheet.getSheetName());
887 log.debug("rangeA: [{}]", rangeA.formatAsString());
888 log.debug("rangeB: [{}]", rangeB.formatAsString());
892 CellRangeAddress tmpRange = new CellRangeAddress(
893 (rangeA.getFirstRow() >= rangeB.getFirstRow() ? rangeA.getFirstRow() : rangeB.getFirstRow()),
894 (rangeA.getLastRow() <= rangeB.getLastRow() ? rangeA.getLastRow() : rangeB.getLastRow()),
895 (rangeA.getFirstColumn() >= rangeB.getFirstColumn() ? rangeA.getFirstColumn() : rangeB.getFirstColumn()),
896 (rangeA.getLastColumn() <= rangeB.getLastColumn() ? rangeA.getLastColumn() : rangeB.getLastColumn()));
900 if(!validateRange(sheet, tmpRange)) {
901 log.debug("rangeA rangeB is not intersect");
902 log.trace("getIntersectRange end");
906 log.trace("getIntersectRange end : {}", tmpRange.formatAsString());
914 * @param sheet Worksheetオブジェクト
915 * @param range 移動対象範囲
916 * @param rowDistance 行方向移動距離。正数:下 負数:上
917 * @param colDistance 列方向移動距離。正数:右 負数:左
918 * @param block ブロック指定。true:ブロック指定 false:列・行単位指定
920 * @throws JaxcelInputException 入力例外発生時
922 public static void moveRange(Sheet sheet, CellRangeAddress range, int rowDistance, int colDistance, boolean block) {
923 log.trace("moveRange start");
927 log.error("sheet is null");
928 throw new JaxcelInputException("sheet is null");
931 log.error("range is null");
932 throw new JaxcelInputException("range is null");
934 if(!validateRange(sheet, range)) {
935 log.error("range is illegal: [{}]", range.formatAsString());
936 throw new JaxcelInputException("range is illegal");
938 if(rowDistance == 0 && colDistance == 0) {
939 log.debug("distance is 0");
940 log.trace("moveRange end");
943 // 斜め移動(rowDistance、colDistanceのいずれも0でない)の場合はblock指定はtrueとする
944 else if(!block && rowDistance != 0 && colDistance != 0) {
945 log.info("change block mode");
949 if(log.isDebugEnabled()) {
950 log.debug("sheet: {}", sheet.getSheetName());
951 log.debug("range: {}", range.formatAsString());
952 log.debug("rowDistance: {}", rowDistance);
953 log.debug("colDistance: {}", colDistance);
954 log.debug("block: {}", block);
958 CellRangeAddress toRange = getIntersectRange(sheet, new CellRangeAddress(
959 (range.getFirstRow() == -1 ? 0 : range.getFirstRow()) + rowDistance,
960 (range.getLastRow() == -1 ? sheet.getLastRowNum() : range.getLastRow()) + rowDistance,
961 (range.getFirstColumn() == - 1 ? 0 : range.getFirstColumn()) + colDistance,
962 (range.getLastColumn() == -1 ? getLastColNum(sheet) : range.getLastColumn()) + colDistance));
964 if(toRange == null) {
966 log.debug("toRange outside sheet. fromRange clear");
967 clearRange(sheet, range, true, true);
968 log.trace("moveRange end");
971 log.debug("toRange: {}", toRange.formatAsString());
974 ArrayList<CellRangeAddress> mergedRegionList = new ArrayList<>();
975 CellRangeAddress tmpRange;
976 if(sheet.getNumMergedRegions() > 0) {
977 log.trace("fromRange mergedRegion check");
979 for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
980 tmpRange = sheet.getMergedRegion(i);
981 log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());
983 // 移動元範囲内に含まれている 接合の保持
984 // 移動元範囲に掛かっている or 移動元範囲を囲っている 接合の削除
985 switch(CellRangeUtil.intersect(range, tmpRange)) {
986 case CellRangeUtil.INSIDE:
987 log.debug("mergedRegion {}. save: [{}]", getIntersectString(range, tmpRange), tmpRange.formatAsString());
988 mergedRegionList.add(tmpRange);
989 sheet.removeMergedRegion(i);
992 case CellRangeUtil.ENCLOSES:
993 case CellRangeUtil.OVERLAP:
994 log.debug("mergedRegion {}. remove: [{}]", getIntersectString(range, tmpRange), tmpRange.formatAsString());
995 sheet.removeMergedRegion(i);
1003 if(sheet.getNumMergedRegions() > 0) {
1004 log.trace("toRange mergedRegion check");
1006 for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
1007 tmpRange = sheet.getMergedRegion(i);
1008 log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());
1010 // 移動先範囲内に掛かっていない以外 接合の削除
1011 if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(toRange, tmpRange)) {
1012 log.debug("mergedRegion {}. remove: [{}]", getIntersectString(toRange, tmpRange), tmpRange.formatAsString());
1013 sheet.removeMergedRegion(i);
1020 if(!mergedRegionList.isEmpty()) {
1021 for(CellRangeAddress mergedRegion : mergedRegionList) {
1023 tmpRange = getIntersectRange(sheet, new CellRangeAddress(
1024 mergedRegion.getFirstRow() + rowDistance,
1025 mergedRegion.getLastRow() + rowDistance,
1026 mergedRegion.getFirstColumn() + colDistance,
1027 mergedRegion.getLastColumn() + colDistance));
1029 if(tmpRange != null) {
1031 sheet.addMergedRegion(tmpRange);
1032 log.debug("mergedRegion move. from: [{}] to: [{}]", mergedRegion.formatAsString(), tmpRange.formatAsString());
1035 log.debug("mergedRegion move to outside sheet. clear: [{}]", mergedRegion.formatAsString());
1040 // シェイプ等のオブジェクトの削除・移動 Excel2007以降 ooxml形式のみ対応
1041 if(sheet instanceof XSSFSheet) {
1042 XSSFSheet xSheet = (XSSFSheet) sheet;
1045 CTDrawing ctDrawing;
1047 CTTwoCellAnchor fAnchor;
1052 CTAutoFilter af = xSheet.getCTWorksheet().getAutoFilter();
1056 log.trace("auto filter range: [{}]", ref);
1057 tmpRange = CellRangeAddress.valueOf(ref);
1060 switch(CellRangeUtil.intersect(range, tmpRange)) {
1061 case CellRangeUtil.OVERLAP:
1062 // 移動元レンジに掛かっている オートフィルタクリア
1063 xSheet.getCTWorksheet().unsetAutoFilter();
1064 log.debug("auto filter clear: [{}]", ref);
1067 case CellRangeUtil.INSIDE:
1069 tmpRange.setFirstRow(tmpRange.getFirstRow() + rowDistance);
1070 tmpRange.setLastRow(tmpRange.getLastRow() + rowDistance);
1071 tmpRange.setFirstColumn(tmpRange.getFirstColumn() + colDistance);
1072 tmpRange.setLastColumn(tmpRange.getLastColumn() + colDistance);
1073 // 移動先範囲妥当性チェック(シートに収まっているか)
1074 if(validateRange(xSheet, tmpRange)) {
1076 xSheet.setAutoFilter(tmpRange);
1077 log.debug("auto filter move from: [{}] to: [{}]", ref, tmpRange.formatAsString());
1081 xSheet.getCTWorksheet().unsetAutoFilter();
1082 log.debug("auto filter move to outside sheet. clear: [{}]", ref);
1086 case CellRangeUtil.NO_INTERSECTION:
1087 // 移動元レンジに含まれていない and (移動先レンジに含まれている or 掛かっている) オートフィルタクリア
1088 switch(CellRangeUtil.intersect(toRange, tmpRange)) {
1089 case CellRangeUtil.INSIDE:
1090 case CellRangeUtil.OVERLAP:
1091 xSheet.getCTWorksheet().unsetAutoFilter();
1092 log.debug("auto filter clear: [{}]", ref);
1098 for(POIXMLDocumentPart part : xSheet.getRelations()) {
1099 if(part == null) continue;
1100 log.debug("DocumentPart class: {}", part.getClass().getName());
1102 // DocumentPartがテーブルの場合
1103 if(part instanceof XSSFTable) {
1105 ctTable = ((XSSFTable) part).getCTTable();
1108 ref = ctTable.getRef();
1109 log.debug("table ref: [{}]", ref);
1110 tmpRange = CellRangeAddress.valueOf(ref);
1112 switch(CellRangeUtil.intersect(range, tmpRange)) {
1113 case CellRangeUtil.OVERLAP:
1114 // 移動元レンジに掛かっている テーブルクリア
1115 clearTable(xSheet, part.getPackageRelationship().getId());
1116 log.debug("table clear: [{}]", ref);
1119 case CellRangeUtil.INSIDE:
1121 tmpRange.setFirstRow(tmpRange.getFirstRow() + rowDistance);
1122 tmpRange.setLastRow(tmpRange.getLastRow() + rowDistance);
1123 tmpRange.setFirstColumn(tmpRange.getFirstColumn() + colDistance);
1124 tmpRange.setLastColumn(tmpRange.getLastColumn() + colDistance);
1125 // 移動先範囲妥当性チェック(シートに収まっているか)
1126 if(validateRange(xSheet, tmpRange)) {
1128 ctTable.setRef(tmpRange.formatAsString());
1129 log.debug("table move from: [{}] to: [{}]", ref, tmpRange.formatAsString());
1133 clearTable(xSheet, part.getPackageRelationship().getId());
1134 log.debug("table clear: [{}]", ref);
1138 case CellRangeUtil.NO_INTERSECTION:
1139 // 移動元レンジに含まれていない and
1140 // (移動先レンジに含まれている or 掛かっている) テーブルクリア
1141 switch(CellRangeUtil.intersect(toRange, tmpRange)) {
1142 case CellRangeUtil.INSIDE:
1143 case CellRangeUtil.OVERLAP:
1144 clearTable(xSheet, part.getPackageRelationship().getId());
1145 log.debug("table clear: [{}]", ref);
1149 // DocumentPartがDrawingオブジェクトの場合
1150 else if(part instanceof XSSFDrawing) {
1151 ctDrawing = ((XSSFDrawing) part).getCTDrawing();
1152 if(ctDrawing != null) {
1153 // 画像やシェイプ等のアンカーでループ
1154 for (int i = 0; i < ctDrawing.getTwoCellAnchorList().size(); i++) {
1156 fAnchor = ctDrawing.getTwoCellAnchorList().get(i);
1157 from = fAnchor.getFrom();
1160 to = fAnchor.getTo();
1163 tmpRange = new CellRangeAddress(r1, r2, c1, c2);
1165 // (移動先レンジに含まれている or 掛かっている) and
1166 // (移動元レンジに含まれていない and 掛かっていない) 削除
1167 switch(CellRangeUtil.intersect(toRange, tmpRange)) {
1168 case CellRangeUtil.INSIDE:
1169 case CellRangeUtil.OVERLAP:
1170 if(CellRangeUtil.intersect(range, tmpRange) == CellRangeUtil.NO_INTERSECTION) {
1171 ctDrawing.removeTwoCellAnchor(i);
1172 log.debug("object delete : [{}]", tmpRange.formatAsString());
1178 // 移動元レンジに含まれている、掛かっているなら移動
1179 switch(CellRangeUtil.intersect(range, tmpRange)) {
1180 case CellRangeUtil.INSIDE:
1181 case CellRangeUtil.OVERLAP:
1182 from.setRow(r1 + rowDistance);
1183 to.setRow(r2 + rowDistance);
1184 from.setCol(c1 + colDistance);
1185 to.setCol(c2 + colDistance);
1188 if(!validateRange(xSheet, (new CellRangeAddress(from.getRow(), to.getRow(), from.getCol(), to.getCol())))) {
1190 ctDrawing.removeTwoCellAnchor(i);
1191 log.debug("object delete : [{}]", tmpRange.formatAsString());
1196 log.debug("object move from: [{}] to: [{}]", tmpRange.formatAsString(), (new CellRangeAddress(from.getRow(), to.getRow(), from.getCol(), to.getCol())).formatAsString());
1199 log.debug("object not move: [{}]", tmpRange.formatAsString());
1206 catch(Exception e) {
1207 log.error("object move error: {}", e.getMessage(), e);
1214 // 移動元範囲行でループ 上へ移動なら正順、下へ移動なら逆順
1216 int r = (rowDistance < 0 ? range.getFirstRow() : range.getLastRow());
1217 (rowDistance < 0 && r <= range.getLastRow()) ||
1218 (rowDistance >= 0 && r >= range.getFirstRow());
1219 r = (rowDistance < 0 ? r + 1 : r - 1))
1222 fRow = sheet.getRow(r) == null ? sheet.createRow(r) : sheet.getRow(r);
1225 if(r + rowDistance < 0 || r + rowDistance > getMaxRowIndex(sheet)) {
1231 tRow = sheet.getRow(r + rowDistance) == null ? sheet.createRow(r + rowDistance) : sheet.getRow(r + rowDistance);
1232 // 縦(行)移動でブロック範囲でないなら行情報(高さ)もコピー
1233 if(!block && rowDistance != 0) {
1234 tRow.setHeight(fRow.getHeight());
1238 // 移動元範囲列でループ 左へ移動なら正順、右へ移動なら逆順
1240 int c = (colDistance < 0 ? range.getFirstColumn() : range.getLastColumn());
1241 (colDistance < 0 && c <= range.getLastColumn()) ||
1242 (colDistance >= 0 && c >= range.getFirstColumn());
1243 c = (colDistance < 0 ? c + 1 : c - 1))
1246 fCell = fRow.getCell(c);
1249 if(tRow == null || c + colDistance < 0 || c + colDistance > getMaxColumnIndex(sheet)) {
1254 tCell = tRow.getCell(c + colDistance) == null ? tRow.createCell(c + colDistance) : tRow.getCell(c + colDistance);
1255 // 横(列)移動でブロック範囲でないなら列情報(幅)もコピー(1行目でのみ行う)
1256 if(!block && colDistance != 0 && c == (colDistance < 0 ? range.getFirstColumn() : range.getLastColumn())) {
1257 sheet.setColumnWidth(tCell.getColumnIndex(), sheet.getColumnWidth(c));
1262 // 移動元セルがnull、移動先がシート範囲外ならcontinue
1263 if(fCell == null && tCell == null) {
1266 // 移動元セルがnull、移動先がシート範囲内
1267 if(fCell == null && tCell != null) {
1268 log.debug("clear cell: [{}]", (new CellReference(tCell)).formatAsString());
1269 // 移動先セルのハイパーリンク クリア
1270 clearHyperlink(sheet, tCell);
1272 tRow.removeCell(tCell);
1274 // 移動元セルがnullでない、移動先がシート範囲外
1275 else if(fCell != null && tCell == null) {
1276 log.debug("cell move to outside sheet. clear cell: [{}]", (new CellReference(fCell)).formatAsString());
1278 fCell.removeCellComment();
1279 // 移動元セルのハイパーリンク クリア
1280 clearHyperlink(sheet, fCell);
1282 fRow.removeCell(fCell);
1284 // 移動元セルがnullでない、移動先がシート範囲内 セル移動
1286 log.debug("move cell. from: [{}] to: [{}]", (new CellReference(fCell)).formatAsString(), (new CellReference(tCell)).formatAsString());
1289 tCell.setCellType(Cell.CELL_TYPE_BLANK);
1291 tCell.setCellStyle(fCell.getCellStyle());
1293 tCell.removeCellComment();
1294 if(fCell.getCellComment() != null) {
1295 tCell.setCellComment(fCell.getCellComment());
1298 switch(fCell.getCellType()) {
1299 case Cell.CELL_TYPE_BOOLEAN:
1300 tCell.setCellType(Cell.CELL_TYPE_BOOLEAN);
1301 tCell.setCellValue(fCell.getBooleanCellValue());
1303 case Cell.CELL_TYPE_ERROR:
1304 tCell.setCellType(Cell.CELL_TYPE_ERROR);
1305 tCell.setCellErrorValue(fCell.getErrorCellValue());
1307 case Cell.CELL_TYPE_FORMULA:
1308 tCell.setCellType(Cell.CELL_TYPE_FORMULA);
1310 tCell.setCellFormula(getMoveFormula(sheet, fCell.getCellFormula(), rowDistance, colDistance));
1312 case Cell.CELL_TYPE_NUMERIC:
1313 tCell.setCellType(Cell.CELL_TYPE_NUMERIC);
1314 tCell.setCellValue(fCell.getNumericCellValue());
1316 case Cell.CELL_TYPE_STRING:
1317 tCell.setCellType(Cell.CELL_TYPE_STRING);
1318 tCell.setCellValue(fCell.getRichStringCellValue());
1322 moveHyperlink(sheet, fCell, tCell);
1324 fCell.removeCellComment();
1326 fRow.removeCell(fCell);
1330 log.trace("moveRange end");
1335 * Excel2007以降 ooxml形式のみ対応
1337 * @param sheet Worksheetオブジェクト
1338 * @param tableId テーブルID
1340 private static void clearTable(Sheet sheet, String tableId) {
1341 if(sheet instanceof XSSFSheet) {
1342 XSSFSheet xSheet = (XSSFSheet) sheet;
1343 List<CTTablePart> tableList = xSheet.getCTWorksheet().getTableParts().getTablePartList();
1344 for(int i = 0; i < tableList.size(); i++) {
1345 if(tableList.get(i).getId() != null && tableList.get(i).getId().equalsIgnoreCase(tableId)) {
1346 xSheet.getCTWorksheet().getTableParts().removeTablePart(i);
1355 * Excel2007以降 ooxml形式のみ対応
1357 * @param sheet Worksheetオブジェクト
1358 * @param fromCell 移動元セル
1359 * @param toCell 移動先セル
1361 private static void moveHyperlink(Sheet sheet, Cell fromCell, Cell toCell) {
1362 if(fromCell.getHyperlink() != null && sheet instanceof XSSFSheet) {
1364 toCell.setHyperlink(fromCell.getHyperlink());
1365 log.debug("hyperlink move. from: [{}] to: [{}]", (new CellReference(fromCell)).formatAsString(), (new CellReference(toCell)).formatAsString());
1367 // 移動元セルのハイパーリンクのクリア
1368 clearHyperlink(sheet, fromCell);
1374 * Excel2007以降 ooxml形式のみ対応
1376 * @param sheet Worksheetオブジェクト
1377 * @param cell クリア対象セル
1379 private static void clearHyperlink(Sheet sheet, Cell cell) {
1380 // ハイパーリンクのクリア Excel2007以降 ooxml形式のみ対応
1381 if(sheet instanceof XSSFSheet && cell.getHyperlink() != null) {
1383 Field field = XSSFSheet.class.getDeclaredField("hyperlinks");
1384 field.setAccessible(true);
1385 @SuppressWarnings("unchecked")
1386 List<XSSFHyperlink> hyperlinks = (List<XSSFHyperlink>)field.get(sheet);
1387 hyperlinks.remove(cell.getHyperlink());
1388 log.debug("hyperlink clear: [{}]", (new CellReference(cell)).formatAsString());
1389 } catch (Exception e) {
1390 log.error("hyperlink clear error: {}", e.getMessage(), e);
1398 * @param cellA 対象セルA
1399 * @param cellB 対象セルB
1400 * @param isCheckSheet trueの場合、シート名を含め比較する
1403 * 対象セルのいずれかでもnullの場合はfalse
1407 public static boolean equalsCellAddress(Cell cellA, Cell cellB, boolean isCheckSheet) {
1409 if(cellA == null || cellB == null) return false;
1412 if(!(cellA.getSheet().getSheetName().equals(cellB.getSheet().getSheetName()))) {
1416 return cellA.getColumnIndex() == cellB.getColumnIndex() &&
1417 cellA.getRowIndex() == cellB.getRowIndex();
1422 * セルの文字列・数式から指定された正規表現の最初の部分を指定された文字列に置換した結果を返却<br>
1424 * replacement内でのバックスラッシュ (\) とドル記号 ($) は、String.replaceFirstと同様に作用します。
1427 * @param target 置換される文字列
1428 * @param replacement 置き換える文字列
1429 * @param regex trueの場合、targetを正規表現として扱う
1431 * @return 置換後のセル文字列・計算式<br>
1432 * 対象セルがnullの場合はnull<br>
1433 * 対象セルのセルタイプが文字列・数式以外の場合はnull
1437 public static String replaceFirstCellValue(Cell cell, String target, String replacement, boolean regex) {
1439 if(cell == null) return null;
1440 String _replacement = replacement == null ? "" : replacement;
1441 String _return = null;
1443 switch(cell.getCellType()) {
1444 case Cell.CELL_TYPE_STRING:
1445 if(target == null) _return = cell.getStringCellValue();
1447 _return = cell.getStringCellValue().replaceFirst(regex ? target : Pattern.quote(target), _replacement);
1449 catch(PatternSyntaxException e) {
1450 log.warn("replaceFirst error. {}", e.getMessage());
1453 case Cell.CELL_TYPE_FORMULA:
1454 if(target == null) _return = cell.getCellFormula();
1456 _return = cell.getCellFormula().replaceFirst(regex ? target : Pattern.quote(target), _replacement);
1458 catch(PatternSyntaxException e) {
1459 log.warn("replaceFirst error. {}", e.getMessage());
1463 // セルタイプが上記以外の場合はnull返却
1464 log.warn("cell type is not strng or formula");
1467 if(_return == null) _return = "";
1472 * セルの文字列・数式から指定された文字列の最初の部分を指定された文字列に置換した結果を返却<br>
1473 * 引数のtarget, replacementともに正規表現として扱いません。
1476 * @param target 置換される文字列
1477 * @param replacement 置き換える文字列
1479 * @return 置換後のセル文字列・計算式<br>
1480 * 対象セルがnullの場合はnull<br>
1481 * 対象セルのセルタイプが文字列・数式以外の場合はnull
1485 public static String replaceFirstCellValue(Cell cell, String target, String replacement) {
1486 return replaceFirstCellValue(cell, target, Matcher.quoteReplacement(replacement), false);