View Javadoc
1   package com.gh.mygreen.xlsmapper.util;
2   
3   import java.awt.Point;
4   import java.lang.reflect.Field;
5   import java.lang.reflect.InvocationTargetException;
6   import java.lang.reflect.Method;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.Collections;
10  import java.util.Date;
11  import java.util.List;
12  import java.util.TimeZone;
13  import java.util.concurrent.atomic.AtomicBoolean;
14  
15  import org.apache.poi.common.usermodel.HyperlinkType;
16  import org.apache.poi.hssf.model.InternalSheet;
17  import org.apache.poi.hssf.model.InternalWorkbook;
18  import org.apache.poi.hssf.record.DVRecord;
19  import org.apache.poi.hssf.record.Record;
20  import org.apache.poi.hssf.record.aggregates.DataValidityTable;
21  import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
22  import org.apache.poi.hssf.usermodel.HSSFSheet;
23  import org.apache.poi.hssf.usermodel.HSSFWorkbook;
24  import org.apache.poi.ss.SpreadsheetVersion;
25  import org.apache.poi.ss.usermodel.BorderStyle;
26  import org.apache.poi.ss.usermodel.Cell;
27  import org.apache.poi.ss.usermodel.CellStyle;
28  import org.apache.poi.ss.usermodel.CellType;
29  import org.apache.poi.ss.usermodel.DataValidation;
30  import org.apache.poi.ss.usermodel.DataValidationConstraint;
31  import org.apache.poi.ss.usermodel.DataValidationHelper;
32  import org.apache.poi.ss.usermodel.Hyperlink;
33  import org.apache.poi.ss.usermodel.Name;
34  import org.apache.poi.ss.usermodel.Row;
35  import org.apache.poi.ss.usermodel.Sheet;
36  import org.apache.poi.ss.usermodel.Workbook;
37  import org.apache.poi.ss.util.AreaReference;
38  import org.apache.poi.ss.util.CellRangeAddress;
39  import org.apache.poi.ss.util.CellRangeAddressBase;
40  import org.apache.poi.ss.util.CellRangeAddressList;
41  import org.apache.poi.ss.util.CellReference;
42  import org.apache.poi.xssf.usermodel.XSSFSheet;
43  import org.apache.poi.xssf.usermodel.XSSFWorkbook;
44  import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDataValidation;
45  import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDataValidations;
46  import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import com.gh.mygreen.xlsmapper.CellFormatter;
51  import com.gh.mygreen.xlsmapper.DefaultCellFormatter;
52  import com.github.mygreen.cellformatter.lang.ExcelDateUtils;
53  
54  /**
55   * Apache POIとJExcel APIの差を埋めるユーティリティクラス。
56   *
57   * @version 2.0
58   * @author T.TSUCHIE
59   *
60   */
61  public class POIUtils {
62  
63      private static final Logger logger = LoggerFactory.getLogger(POIUtils.class);
64  
65      /** 標準のセルフォーマッター */
66      private static CellFormatter defaultCellFormatter = new DefaultCellFormatter();
67  
68      /**
69       * シートの種類を判定する。
70       *
71       * @since 2.0
72       * @param sheet 判定対象のオブジェクト
73       * @return シートの種類。不明な場合はnullを返す。
74       * @throws IllegalArgumentException {@literal sheet == null}
75       */
76      public static SpreadsheetVersion getVersion(final Sheet sheet) {
77          ArgUtils.notNull(sheet, "sheet");
78  
79          if(sheet instanceof HSSFSheet) {
80              return SpreadsheetVersion.EXCEL97;
81  
82          } else if(sheet instanceof XSSFSheet) {
83              return SpreadsheetVersion.EXCEL2007;
84          }
85  
86          return null;
87      }
88  
89      /**
90       * シートの最大列数を取得する。
91       * <p>{@literal jxl.Sheet.getColumns()}</p>
92       * @param sheet シートオブジェクト
93       * @return 最大列数
94       * @throws IllegalArgumentException {@literal sheet == null.}
95       */
96      public static int getColumns(final Sheet sheet) {
97          ArgUtils.notNull(sheet, "sheet");
98  
99          int minRowIndex = sheet.getFirstRowNum();
100         int maxRowIndex = sheet.getLastRowNum();
101         int maxColumnsIndex = 0;
102         for(int i = minRowIndex; i <= maxRowIndex; i++) {
103             final Row row = sheet.getRow(i);
104             if(row == null) {
105                 continue;
106             }
107 
108             final int column = row.getLastCellNum();
109             if(column > maxColumnsIndex) {
110                 maxColumnsIndex = column;
111             }
112         }
113 
114         return maxColumnsIndex;
115     }
116 
117     /**
118      * シートの最大行数を取得する
119      *
120      * <p>{@literal jxl.Sheet.getRows()}</p>
121      * @param sheet シートオブジェクト
122      * @return 最大行数
123      * @throws IllegalArgumentException {@literal sheet == null.}
124      */
125     public static int getRows(final Sheet sheet) {
126         ArgUtils.notNull(sheet, "sheet");
127         return sheet.getLastRowNum() + 1;
128     }
129 
130     /**
131      * シートから任意アドレスのセルを取得する。
132      * @since 0.5
133      * @param sheet シートオブジェクト
134      * @param address アドレス(Point.x=column, Point.y=row)
135      * @return セル
136      * @throws IllegalArgumentException {@literal sheet == null or address == null.}
137      */
138     public static Cell getCell(final Sheet sheet, final Point address) {
139         ArgUtils.notNull(sheet, "sheet");
140         ArgUtils.notNull(address, "address");
141         return getCell(sheet, address.x, address.y);
142     }
143 
144     /**
145      * シートから任意アドレスのセルを取得する。
146      * @since 1.4
147      * @param sheet シートオブジェクト
148      * @param address セルのアドレス
149      * @return セル
150      * @throws IllegalArgumentException {@literal sheet == null or address == null.}
151      */
152     public static Cell getCell(final Sheet sheet, final CellPosition address) {
153         ArgUtils.notNull(sheet, "sheet");
154         ArgUtils.notNull(address, "address");
155         return getCell(sheet, address.getColumn(), address.getRow());
156     }
157 
158     /**
159      * シートから任意アドレスのセルを取得する。
160      *
161      * <p>{@literal jxl.Sheet.getCell(int column, int row)}</p>
162      * @param sheet シートオブジェクト
163      * @param column 列番号(0から始まる)
164      * @param row 行番号(0から始まる)
165      * @return セル
166      * @throws IllegalArgumentException {@literal sheet == null}
167      */
168     public static Cell getCell(final Sheet sheet, final int column, final int row) {
169         ArgUtils.notNull(sheet, "sheet");
170 
171         Row rows = sheet.getRow(row);
172         if(rows == null) {
173             rows = sheet.createRow(row);
174         }
175 
176         Cell cell = rows.getCell(column);
177         if(cell == null) {
178             cell = rows.createCell(column, CellType.BLANK);
179         }
180 
181         return cell;
182     }
183 
184     /**
185      * 任意の行のセルを全て取得する。
186      * <p> {@literal jxl.Seet.getRow(int row)}</p>
187      * @param sheet シートオブジェクト
188      * @param row 行番号(0から始まる)
189      * @return 行レコード(カラムの集合)。
190      *         ただし、シートの最大列数以下の場合、空のセルを補完する。
191      * @throws IllegalArgumentException {@literal sheet == null}
192      */
193     public static Cell[] getRow(final Sheet sheet, final int row) {
194         ArgUtils.notNull(sheet, "sheet");
195 
196         Row rows = sheet.getRow(row);
197         if(rows == null) {
198             rows = sheet.createRow(row);
199         }
200         int maxColumn = getColumns(sheet);
201         Cell[] cells = new Cell[maxColumn];
202         for(int i=0; i < maxColumn; i++) {
203             Cell cell = rows.getCell(i);
204             if(cell == null) {
205                 cell = rows.createCell(i, CellType.BLANK);
206             }
207             cells[i] = cell;
208         }
209 
210         return cells;
211     }
212 
213     /**
214      * 任意の列のセルを全て取得する。
215      * <p> {@literal jxl.Seet.getColumn(int col)}</p>
216      * @param sheet
217      * @param col 列番号(0から始まる)
218      * @return 列レコード(行の集合)。
219      *         ただし、シートの最大行数以下の場合、空のセルを補完する。
220      * @throws IllegalArgumentException {@literal sheet == null}
221      */
222     public static Cell[] getColumn(final Sheet sheet, final int col) {
223         ArgUtils.notNull(sheet, "sheet");
224 
225         int maxRow = getRows(sheet);
226         Cell[] cells = new Cell[maxRow];
227         for(int i=0; i < maxRow; i++) {
228             Row rows = sheet.getRow(i);
229             if(rows == null) {
230                 rows = sheet.createRow(i);
231 
232             }
233 
234             Cell cell = rows.getCell(col);
235             if(cell == null) {
236                 cell = rows.createCell(col, CellType.BLANK);
237             }
238 
239             cells[i] = cell;
240         }
241 
242         return cells;
243     }
244 
245     /**
246      * フォーマッターを指定してセルの値を取得する
247      *
248      * @param cell
249      * @param cellFormatter
250      * @return フォーマットした文字列
251      * @throws IllegalArgumentException {@literal cell or cellFormatter is null.}
252      */
253     public static String getCellContents(final Cell cell, final CellFormatter cellFormatter) {
254         ArgUtils.notNull(cell, "cell");
255         ArgUtils.notNull(cellFormatter, "cellFormatter");
256 
257         return cellFormatter.format(cell);
258 
259     }
260 
261     /**
262      * 指定してセルの値が空かどうか判定する。
263      * <p>ブランクセルなどの判定は優先的に行う。</p>
264      * @param cell
265      * @return
266      */
267     public static boolean isEmptyCellContents(final Cell cell) {
268         return isEmptyCellContents(cell, defaultCellFormatter);
269     }
270 
271     /**
272      * フォーマッターを指定してセルの値が空かどうか判定する。
273      * <p>ブランクセルなどの判定は優先的に行う。</p>
274      * @param cell セル
275      * @param cellFormatter セルのフォーマッタ
276      * @throws IllegalArgumentException {@literal  sheet == null or cellFormatter == null.}
277      * @return
278      */
279     public static boolean isEmptyCellContents(final Cell cell, final CellFormatter cellFormatter) {
280         ArgUtils.notNull(cell, "cell");
281         ArgUtils.notNull(cellFormatter, "cellFormatter");
282 
283         return getCellContents(cell, cellFormatter).isEmpty();
284     }
285 
286     /**
287      * 指定した書式のインデックス番号を取得する。シートに存在しない場合は、新しく作成する。
288      * @param sheet シート
289      * @param pattern 作成する書式のパターン
290      * @return 書式のインデックス番号。
291      * @throws IllegalArgumentException {@literal sheet == null.}
292      * @throws IllegalArgumentException {@literal pattern == null || pattern.isEmpty().}
293      */
294     public static short getDataFormatIndex(final Sheet sheet, final String pattern) {
295         ArgUtils.notNull(sheet, "sheet");
296         ArgUtils.notEmpty(pattern, "pattern");
297 
298         return sheet.getWorkbook().getCreationHelper().createDataFormat().getFormat(pattern);
299 
300     }
301 
302     /**
303      * セルに設定されている書式を取得する。
304      * @since 1.1
305      * @param cell セルのインスタンス。
306      * @param cellFormatter セルのフォーマッタ
307      * @return 書式が設定されていない場合は、空文字を返す。
308      *         cellがnullの場合も空文字を返す。
309      *         標準の書式の場合も空文字を返す。
310      */
311     public static String getCellFormatPattern(final Cell cell, final CellFormatter cellFormatter) {
312         if(cell == null) {
313             return "";
314         }
315 
316         String pattern = cellFormatter.getPattern(cell);
317         if(pattern.equalsIgnoreCase("general")) {
318             return "";
319         }
320 
321         return pattern;
322 
323     }
324 
325     /**
326      * 指定した範囲のセルを結合する。
327      * @param sheet
328      * @param startCol
329      * @param startRow
330      * @param endCol
331      * @param endRow
332      * @return 結合した範囲のアドレス情報
333      * @throws IllegalArgumentException {@literal sheet == null}
334      */
335     public static CellRangeAddress mergeCells(final Sheet sheet, int startCol, int startRow, int endCol, int endRow) {
336         ArgUtils.notNull(sheet, "sheet");
337 
338         // 結合先のセルの値を空に設定する
339         for(int r=startRow; r <= endRow; r++) {
340             for(int c=startCol; c <= endCol; c++) {
341 
342                 if(r == startRow && c == startCol) {
343                     continue;
344                 }
345 
346                 Cell cell = getCell(sheet, c, r);
347                 cell.setBlank();
348             }
349         }
350 
351         final CellRangeAddress range = new CellRangeAddress(startRow, endRow, startCol, endCol);
352         sheet.addMergedRegion(range);
353         return range;
354     }
355 
356     /**
357      * 指定したセルのアドレスの結合情報を取得する。
358      * @since 0.5
359      * @param sheet シート情報
360      * @param rowIdx 行番号
361      * @param colIdx 列番号
362      * @return 結合していない場合nullを返す。
363      */
364     public static CellRangeAddress getMergedRegion(final Sheet sheet, final int rowIdx, final int colIdx) {
365         ArgUtils.notNull(sheet, "sheet");
366 
367         final int num = sheet.getNumMergedRegions();
368         for(int i=0; i < num; i ++) {
369             final CellRangeAddress range = sheet.getMergedRegion(i);
370             if(range.isInRange(rowIdx, colIdx)) {
371                 return range;
372             }
373         }
374 
375         return null;
376     }
377 
378     /**
379      * 指定した範囲の結合を解除する。
380      * @param sheet
381      * @param mergedRange
382      * @return 引数で指定した結合が見つからない場合。
383      */
384     public static boolean removeMergedRange(final Sheet sheet, final CellRangeAddress mergedRange) {
385         ArgUtils.notNull(sheet, "sheet");
386         ArgUtils.notNull(mergedRange, "mergedRange");
387 
388         final String mergedAddress = mergedRange.formatAsString(sheet.getSheetName(), true);
389 
390         final int num = sheet.getNumMergedRegions();
391         for(int i=0; i < num; i ++) {
392             final CellRangeAddress range = sheet.getMergedRegion(i);
393             final String rangeAddress = range.formatAsString(sheet.getSheetName(), true);
394             if(rangeAddress.equals(mergedAddress)) {
395                 sheet.removeMergedRegion(i);
396                 return true;
397             }
398         }
399 
400         return false;
401     }
402 
403     /**
404      * 領域の列サイズ(横セル数)を計算します。
405      *
406      * @since 2.0
407      * @param region 領域
408      * @return 列サイズ(横セル数)。引数がnullの時は、0を返します。
409      */
410     public static int getColumnSize(final CellRangeAddress region) {
411         if(region == null) {
412             return 0;
413         }
414         return region.getLastColumn() - region.getFirstColumn() + 1;
415     }
416 
417     /**
418      * 領域の行サイズ(行セル数)を計算します。
419      *
420      * @since 2.0
421      * @param region 領域
422      * @return 行サイズ(行セル数)。引数がnullの時は、0を返します。
423      */
424     public static int getRowSize(final CellRangeAddress region) {
425         if(region == null) {
426             return 0;
427         }
428         return region.getLastRow() - region.getFirstRow() + 1;
429     }
430 
431     /**
432      * 指定した行の下に行を1行追加する
433      * @param sheet
434      * @param rowIndex 追加する行数
435      * @return 追加した行を返す。
436      */
437     public static Row insertRow(final Sheet sheet, final int rowIndex) {
438 
439         ArgUtils.notNull(sheet, "sheet");
440         ArgUtils.notMin(rowIndex, 0, "rowIndex");
441 
442         // 最終行を取得する
443         int lastRow = sheet.getLastRowNum();
444         if(lastRow < rowIndex) {
445             // データが定義されている範囲害の場合は、行を新たに作成して返す。
446             return sheet.createRow(rowIndex);
447         }
448 
449         sheet.shiftRows(rowIndex, lastRow+1, 1);
450         return sheet.createRow(rowIndex);
451     }
452 
453     /**
454      * 指定した行を削除する。
455      * <p>削除した行は上に詰める。
456      * @since 0.5
457      * @param sheet
458      * @param rowIndex 削除する行数
459      * @return 削除した行
460      */
461     public static Row removeRow(final Sheet sheet, final int rowIndex) {
462 
463         ArgUtils.notNull(sheet, "cell");
464         ArgUtils.notMin(rowIndex, 0, "rowIndex");
465 
466         final Row row = sheet.getRow(rowIndex);
467         if(row == null) {
468             // 削除対象の行にデータが何もない場合
469             return row;
470         }
471 
472         sheet.removeRow(row);
473 
474         // 上に1つ行をずらす
475         int lastRow = sheet.getLastRowNum();
476         if(rowIndex +1 > lastRow) {
477             return row;
478         }
479 
480         sheet.shiftRows(rowIndex+1, lastRow, -1);
481 
482         return row;
483     }
484 
485 
486     /**
487      * 座標をExcelのアドレス形式'A1'などに変換する
488      * @param rowIndex 行インデックス
489      * @param colIndex 列インデックス
490      * @return
491      */
492     public static String formatCellAddress(final int rowIndex, final int colIndex) {
493         return CellReference.convertNumToColString(colIndex) + String.valueOf(rowIndex+1);
494     }
495 
496     /**
497      * 座標をExcelのアドレス形式'A1'になどに変換する。
498      * @param cellAddress セルの位置情報
499      * @return
500      * @throws IllegalArgumentException address == null.
501      */
502     public static String formatCellAddress(final Point cellAddress) {
503         ArgUtils.notNull(cellAddress, "cellAddress");
504         return formatCellAddress(cellAddress.y, cellAddress.x);
505     }
506 
507     /**
508      * セルのアドレス'A1'を取得する。
509      * @param cell セル情報
510      * @return IllegalArgumentException cell == null.
511      */
512     public static String formatCellAddress(final Cell cell) {
513         ArgUtils.notNull(cell, "cell");
514         return CellReference.convertNumToColString(cell.getColumnIndex()) + String.valueOf(cell.getRowIndex()+1);
515     }
516 
517     /**
518      * リンクのアドレスを判定する。
519      * @param linkAddress リンクのアドレス(URL)
520      * @return 不明な場合は{@link HyperlinkType#NONE}を返す。
521      * @throws IllegalArgumentException linkAddress が空文字の場合。
522      */
523     public static HyperlinkType judgeLinkType(final String linkAddress) {
524 
525         ArgUtils.notEmpty(linkAddress, "linkAddress");
526 
527         if(linkAddress.matches(".*![\\p{Alnum}]+")) {
528             // !A1のアドレスを含むかどうか
529             return HyperlinkType.DOCUMENT;
530 
531         } else if(linkAddress.matches("[\\p{Alpha}]+[0-9]+")) {
532             // A1の通常のアドレスの形式
533             return HyperlinkType.DOCUMENT;
534 
535         } else if(linkAddress.matches(".+@.+")) {
536             // @を含むかどうか
537             return HyperlinkType.EMAIL;
538 
539         } else if(linkAddress.matches("[\\p{Alpha}]+://.+")) {
540             // プロトコル付きかどうか
541             return HyperlinkType.URL;
542 
543         } else if(linkAddress.matches(".+\\.[\\p{Alnum}]+")) {
544             // 拡張子付きかどうか
545             return HyperlinkType.FILE;
546 
547         } else {
548             return HyperlinkType.NONE;
549         }
550 
551     }
552 
553     /**
554      * 入力規則の範囲を更新する。
555      * @since 0.5
556      * @param sheet シート
557      * @param oldRegion 更新対象の範囲。
558      * @param newRegion 新しい範囲。
559      * @return true:更新完了。false:指定した範囲を持つ入力規則が見つからなかった場合。
560      */
561     public static boolean updateDataValidationRegion(final Sheet sheet,
562             final CellRangeAddressList oldRegion, final CellRangeAddressList newRegion) {
563 
564         ArgUtils.notNull(sheet, "sheet");
565         ArgUtils.notNull(oldRegion, "oldRegion");
566         ArgUtils.notNull(newRegion, "newRegion");
567 
568         if(sheet instanceof XSSFSheet) {
569 
570             final List<String> oldSqref = convertSqref(oldRegion);
571 
572             try {
573                 final XSSFSheet xssfSheet = (XSSFSheet) sheet;
574                 Field fWorksheet = XSSFSheet.class.getDeclaredField("worksheet");
575                 fWorksheet.setAccessible(true);
576                 CTWorksheet worksheet = (CTWorksheet) fWorksheet.get(xssfSheet);
577 
578                 CTDataValidations dataValidations = worksheet.getDataValidations();
579                 if(dataValidations == null) {
580                     return false;
581                 }
582 
583                 for(int i=0; i < dataValidations.getCount(); i++) {
584                     CTDataValidation dv = dataValidations.getDataValidationArray(i);
585 
586                     // 規則の範囲を比較し、同じならば範囲を書き換える。
587                     @SuppressWarnings("unchecked")
588                     List<String> sqref = new ArrayList<>(dv.getSqref());
589                     if(equalsSqref(sqref, oldSqref)) {
590                         List<String> newSqref = convertSqref(newRegion);
591                         dv.setSqref(newSqref);
592 
593                         // 設定し直す
594                         dataValidations.setDataValidationArray(i, dv);
595                         return true;
596                     }
597 
598                 }
599 
600                 return false;
601 
602             } catch(Exception e) {
603                 throw new RuntimeException("fail update DataValidation's Regsion.", e);
604             }
605 
606         } else if(sheet instanceof HSSFSheet) {
607 
608             final HSSFSheet hssfSheet = (HSSFSheet) sheet;
609             try {
610                 Field fWorksheet = HSSFSheet.class.getDeclaredField("_sheet");
611                 fWorksheet.setAccessible(true);
612                 InternalSheet worksheet = (InternalSheet) fWorksheet.get(hssfSheet);
613 
614                 DataValidityTable dvt = worksheet.getOrCreateDataValidityTable();
615 
616                 // シート内の入力規則のデータを検索して、一致するものがあれば書き換える。
617                 final AtomicBoolean updated = new AtomicBoolean(false);
618                 RecordVisitor visitor = new RecordVisitor() {
619 
620                     @Override
621                     public void visitRecord(final Record r) {
622                         if (!(r instanceof DVRecord)) {
623                             return;
624                         }
625 
626                         final DVRecord dvRecord = (DVRecord) r;
627                         final CellRangeAddressList region = dvRecord.getCellRangeAddress();
628                         if(equalsRegion(region, oldRegion)) {
629 
630                             // 一旦既存の範囲を削除する。
631                             while(region.countRanges() != 0) {
632                                 region.remove(0);
633                             }
634 
635                             // 新しい範囲を追加する。
636                             for(CellRangeAddress newRange : newRegion.getCellRangeAddresses()) {
637                                 region.addCellRangeAddress(newRange);
638                             }
639 
640                             updated.set(true);
641                             return;
642                         }
643                     }
644                 };
645 
646                 dvt.visitContainedRecords(visitor);
647 
648                 return updated.get();
649 
650             } catch(Exception e) {
651                 throw new RuntimeException("fail update DataValidation's Regsion.", e);
652             }
653         } else {
654             throw new UnsupportedOperationException("not supported update dava validation's region for type " + sheet.getClass().getName());
655         }
656 
657     }
658 
659     /**
660      * CellRangeAddressを文字列形式のリストに変換する。
661      * @since 0.5
662      * @param region
663      * @return
664      */
665     private static List<String> convertSqref(final CellRangeAddressList region) {
666 
667         List<String> sqref = new ArrayList<>();
668         for(CellRangeAddress range : region.getCellRangeAddresses()) {
669             sqref.add(range.formatAsString());
670         }
671 
672         return sqref;
673 
674     }
675 
676     /**
677      * 文字列形式のセルの範囲が同じかどうか比較する。
678      * @since 0.5
679      * @param sqref1
680      * @param sqref2
681      * @return
682      */
683     public static boolean equalsSqref(final List<String> sqref1, final List<String> sqref2) {
684 
685         if(sqref1.size() != sqref2.size()) {
686             return false;
687         }
688 
689         Collections.sort(sqref1);
690         Collections.sort(sqref2);
691 
692         final int size = sqref1.size();
693         for(int i=0; i < size; i++) {
694             if(!sqref1.get(i).equals(sqref2.get(i))) {
695                 return false;
696             }
697         }
698 
699         return true;
700 
701     }
702 
703     /**
704      * 文字列形式のセルの範囲が同じかどうか比較する。
705      * @since 0.5
706      * @param region1
707      * @param region2
708      * @return
709      */
710     public static boolean equalsRegion(final CellRangeAddressList region1, final CellRangeAddressList region2) {
711 
712         return equalsSqref(convertSqref(region1), convertSqref(region2));
713 
714     }
715 
716     /**
717      * テンプレートの入力規則の制約「リスト」を追加する。
718      * <p>POI-3.7以上が必要。
719      * @param sheet シート
720      * @param constraints 制約とするコレクションの中身
721      * @param startPosition 開始位置
722      * @param endPosition 終了位置
723      */
724     public static void setupExplicitListConstaint(final Sheet sheet, final Collection<String> constraints,
725             final Point startPosition, final Point endPosition) {
726 
727         ArgUtils.notNull(sheet, "sheet");
728         ArgUtils.notEmpty(constraints, "constraints");
729         ArgUtils.notNull(startPosition, "startPosition");
730         ArgUtils.notNull(endPosition, "endPosition");
731 
732         setupExplicitListConstaint(sheet, constraints.toArray(new String[constraints.size()]),
733                 startPosition, endPosition);
734     }
735 
736     /**
737      * テンプレートの入力規則の制約「リスト」を追加する。
738      * <p>POI-3.7以上が必要。
739      * @param sheet シート
740      * @param constraints 制約とするリストの中身
741      * @param startPosition 開始位置
742      * @param endPosition 終了位置
743      */
744     public static void setupExplicitListConstaint(final Sheet sheet, final String[] constraints,
745             final Point startPosition, final Point endPosition) {
746 
747         ArgUtils.notNull(sheet, "sheet");
748         ArgUtils.notEmpty(constraints, "constraints");
749         ArgUtils.notNull(startPosition, "startPosition");
750         ArgUtils.notNull(endPosition, "endPosition");
751 
752         final DataValidationHelper helper = sheet.getDataValidationHelper();
753         final DataValidationConstraint constraint = helper.createExplicitListConstraint(constraints);
754         setupConstaint(sheet, constraint, startPosition, endPosition);
755 
756     }
757 
758     /**
759      * テンプレートの入力規則の制約「リスト」を式形式で追加する。
760      * <p>POI-3.7以上が必要。
761      * @param sheet シート
762      * @param listFormula 入力規則の式('='は含まない)
763      * @param startPosition 設定するセルの開始位置
764      * @param endPosition 設定するセルの終了位置
765      */
766     public static void setupFormulaListConstaint(final Sheet sheet, final String listFormula,
767             final Point startPosition, final Point endPosition) {
768 
769         ArgUtils.notNull(sheet, "sheet");
770         ArgUtils.notEmpty(listFormula, "listFormula");
771         ArgUtils.notNull(startPosition, "startPosition");
772         ArgUtils.notNull(endPosition, "endPosition");
773 
774         final DataValidationHelper helper = sheet.getDataValidationHelper();
775         final DataValidationConstraint constraint = helper.createFormulaListConstraint("=" + listFormula);
776         setupConstaint(sheet, constraint, startPosition, endPosition);
777     }
778 
779     /**
780      * 指定した範囲のセルに制約を追加する。
781      * <p>POI-3.7以上が必要。
782      * @param sheet シート
783      * @param constraint 制約
784      * @param startPosition 設定するセルの開始位置
785      * @param endPosition 設定するセルの終了位置
786      */
787     public static void setupConstaint(final Sheet sheet, final DataValidationConstraint constraint,
788             final Point startPosition, final Point endPosition) {
789 
790         ArgUtils.notNull(sheet, "sheet");
791         ArgUtils.notNull(constraint, "constraint");
792         ArgUtils.notNull(startPosition, "startPosition");
793         ArgUtils.notNull(endPosition, "endPosition");
794 
795         final DataValidationHelper helper = sheet.getDataValidationHelper();
796 
797         final CellRangeAddressList region = new CellRangeAddressList(
798                 startPosition.y, endPosition.y,
799                 startPosition.x, endPosition.x
800                 );
801         final DataValidation dataValidation = helper.createValidation(constraint, region);
802         sheet.addValidationData(dataValidation);
803     }
804 
805     /**
806      * 指定した範囲の名前を登録する。
807      * <p>POI-3.7以上が必要。
808      * <p>指定した名前が既に存在する場合は、新しい範囲に書き換える。
809      * @param sheet シート
810      * @param name 名前
811      * @param startPosition 設定するセルの開始位置
812      * @param endPosition 設定するセルの終了位置
813      * @return
814      */
815     public static Name defineName(final Sheet sheet, final String name,
816             final Point startPosition, final Point endPosition) {
817 
818         ArgUtils.notNull(sheet, "sheet");
819         ArgUtils.notEmpty(name, "name");
820         ArgUtils.notNull(startPosition, "startPosition");
821         ArgUtils.notNull(endPosition, "endPosition");
822 
823         final Workbook workbook = sheet.getWorkbook();
824         Name nameObj = workbook.getName(name);
825         if(nameObj == null) {
826             nameObj = workbook.createName();
827             nameObj.setNameName(name);
828         }
829 
830         final AreaReference areaRef = buildNameArea(sheet.getSheetName(), startPosition, endPosition,
831                 sheet.getWorkbook().getSpreadsheetVersion());
832         nameObj.setRefersToFormula(areaRef.formatAsString());
833 
834         return nameObj;
835 
836     }
837 
838     /**
839      * 名前の範囲の形式を組み立てる。
840      * <code>シート名!$A$1:$A:$5</code>
841      * @param sheetName シート名
842      * @param startPosition 設定するセルの開始位置
843      * @param endPosition 設定するセルの終了位置
844      * @param sheetVersion シートの形式
845      * @return
846      */
847     public static AreaReference buildNameArea(final String sheetName,
848             final Point startPosition, final Point endPosition, SpreadsheetVersion sheetVersion) {
849 
850         ArgUtils.notEmpty(sheetName, "sheetName");
851         ArgUtils.notNull(startPosition, "startPosition");
852         ArgUtils.notNull(endPosition, "endPosition");
853 
854         final CellReference firstRefs = new CellReference(sheetName, startPosition.y, startPosition.x, true, true);
855         final CellReference lastRefs = new CellReference(sheetName, endPosition.y, endPosition.x, true, true);
856 
857         return new AreaReference(firstRefs, lastRefs, sheetVersion);
858     }
859 
860     /**
861      * セルの範囲が重複(交錯)しているかどうか判定する。
862      * <p>このメソッドは、POI-3.14で追加されたメソッド{@literal Sheet#intersects(...)}と後方互換性を保つためのもの。</p>
863      *
864      * @param my
865      * @param other
866      * @return trueの場合、1つでもセルの範囲が重複している。
867      */
868     public static boolean intersectsRegion(final CellRangeAddressBase my, final CellRangeAddressBase other) {
869         return my.getFirstRow() <= other.getLastRow() &&
870                 my.getFirstColumn() <= other.getLastColumn() &&
871                 other.getFirstRow() <= my.getLastRow() &&
872                 other.getFirstColumn() <= my.getLastColumn();
873     }
874 
875     /**
876      * 日時の開始日が1904年かどうか。
877      * 通常は、1900年始まり。
878      * @param workbook ワークブック
879      * @return trueの場合は、1904年始まり。falseの場合は、1900年始まり。
880      */
881     public static boolean isDateStart1904(final Workbook workbook) {
882 
883         if(workbook instanceof HSSFWorkbook) {
884             try {
885                 Method method = HSSFWorkbook.class.getDeclaredMethod("getWorkbook");
886                 method.setAccessible(true);
887 
888                 InternalWorkbook iw = (InternalWorkbook) method.invoke(workbook);
889                 return iw.isUsing1904DateWindowing();
890 
891             } catch(NoSuchMethodException | SecurityException e) {
892                 logger.warn("fail access method HSSFWorkbook.getWorkbook.", e);
893                 return false;
894             } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
895                 logger.warn("fail invoke method HSSFWorkbook.getWorkbook.", e);
896                 return false;
897             }
898 
899         } else if(workbook instanceof XSSFWorkbook) {
900             try {
901                 Method method = XSSFWorkbook.class.getDeclaredMethod("isDate1904");
902                 method.setAccessible(true);
903 
904                 boolean value = (boolean) method.invoke(workbook);
905                 return value;
906 
907             } catch(NoSuchMethodException | SecurityException e) {
908                 logger.warn("fail access method XSSFWorkbook.isDate1904.", e);
909                 return false;
910             } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
911                 logger.warn("fail invoke method XSSFWorkbook.isDate1904.", e);
912                 return false;
913             }
914 
915         } else {
916             logger.warn("unknown workbook type.", workbook.getClass().getName());
917         }
918 
919         return false;
920     }
921 
922     /**
923      * 結合を考慮してセルの罫線(上部)を取得する。
924      *
925      * @param cell セル
926      * @return {@literal BorderStyle}
927      * @throws IllegalArgumentException {@literal cell is null.}
928      */
929     public static BorderStyle getBorderTop(final Cell cell) {
930 
931         ArgUtils.notNull(cell, "cell");
932 
933         final Sheet sheet = cell.getSheet();
934         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
935 
936         final Cell target;
937         if(mergedRegion == null) {
938             // 結合されていない場合
939             target = cell;
940 
941         } else {
942             if(mergedRegion.getFirstRow() == cell.getRowIndex()) {
943                 // 引数のCellが上部のセルの場合
944                 target = cell;
945             } else {
946                 target = getCell(sheet, cell.getColumnIndex(), mergedRegion.getFirstRow());
947             }
948 
949         }
950 
951         final CellStyle style = target.getCellStyle();
952         if(style == null) {
953             return BorderStyle.NONE;
954         } else {
955             return style.getBorderTop();
956         }
957 
958     }
959 
960     /**
961      * 結合を考慮してセルの罫線(下部)を取得する。
962      *
963      * @param cell セル
964      * @return {@literal BorderStyle}
965      * @throws IllegalArgumentException {@literal cell is null.}
966      */
967     public static BorderStyle getBorderBottom(final Cell cell) {
968 
969         ArgUtils.notNull(cell, "cell");
970 
971         final Sheet sheet = cell.getSheet();
972         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
973 
974         final Cell target;
975         if(mergedRegion == null) {
976             // 結合されていない場合
977             target = cell;
978 
979         } else {
980             if(mergedRegion.getLastRow() == cell.getRowIndex()) {
981                 // 引数のCellが下部のセルの場合
982                 target = cell;
983             } else {
984                 target = getCell(sheet, cell.getColumnIndex(), mergedRegion.getLastRow());
985             }
986 
987         }
988 
989         final CellStyle style = target.getCellStyle();
990         if(style == null) {
991             return BorderStyle.NONE;
992         } else {
993             return style.getBorderBottom();
994         }
995 
996     }
997 
998     /**
999      * 結合を考慮してセルの罫線(左部)を取得する。
1000      *
1001      * @param cell セル
1002      * @return {@literal BorderStyle}
1003      * @throws IllegalArgumentException {@literal cell is null.}
1004      */
1005     public static BorderStyle getBorderRight(final Cell cell) {
1006 
1007         ArgUtils.notNull(cell, "cell");
1008 
1009         final Sheet sheet = cell.getSheet();
1010         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
1011 
1012         final Cell target;
1013         if(mergedRegion == null) {
1014             // 結合されていない場合
1015             target = cell;
1016 
1017         } else {
1018             if(mergedRegion.getLastColumn() == cell.getColumnIndex()) {
1019                 // 引数のCellが右部のセルの場合
1020                 target = cell;
1021             } else {
1022                 target = getCell(sheet, mergedRegion.getLastColumn(), cell.getRowIndex());
1023             }
1024 
1025         }
1026 
1027         final CellStyle style = target.getCellStyle();
1028         if(style == null) {
1029             return BorderStyle.NONE;
1030         } else {
1031             return style.getBorderRight();
1032         }
1033 
1034     }
1035 
1036     /**
1037      * 結合を考慮してセルの罫線(右部)を取得する。
1038      *
1039      * @param cell セル
1040      * @return {@literal BorderStyle}
1041      * @throws IllegalArgumentException {@literal cell is null.}
1042      */
1043     public static BorderStyle getBorderLeft(final Cell cell) {
1044 
1045         ArgUtils.notNull(cell, "cell");
1046 
1047         final Sheet sheet = cell.getSheet();
1048         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
1049 
1050         final Cell target;
1051         if(mergedRegion == null) {
1052             // 結合されていない場合
1053             target = cell;
1054 
1055         } else {
1056             if(mergedRegion.getFirstColumn() == cell.getColumnIndex()) {
1057                 // 引数のCellが左部のセルの場合
1058                 target = cell;
1059             } else {
1060                 target = getCell(sheet, mergedRegion.getFirstColumn(), cell.getRowIndex());
1061             }
1062 
1063         }
1064 
1065         final CellStyle style = target.getCellStyle();
1066         if(style == null) {
1067             return BorderStyle.NONE;
1068         } else {
1069             return style.getBorderLeft();
1070         }
1071 
1072     }
1073 
1074     /**
1075      * ハイパーリンクを取得する。
1076      * <p>結合されているセルの場合にも対応。
1077      * @param cell
1078      * @return 見つからない場合は、nullを返す。
1079      * @throws IllegalArgumentException {@literal cell is null.}
1080      */
1081     public static Hyperlink getHyperlink(final Cell cell) {
1082 
1083         ArgUtils.notNull(cell, "cell");
1084 
1085         Hyperlink link = cell.getHyperlink();
1086         if(link != null) {
1087             return link;
1088         }
1089 
1090         final Sheet sheet = cell.getSheet();
1091         CellRangeAddress mergedRange = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
1092         if(mergedRange == null) {
1093             return null;
1094         }
1095 
1096         for(Hyperlink item : sheet.getHyperlinkList()) {
1097             if(item.getFirstRow() == mergedRange.getFirstRow()
1098                     && item.getFirstColumn() == mergedRange.getFirstColumn()) {
1099                 return item;
1100             }
1101 
1102         }
1103 
1104         return null;
1105 
1106     }
1107 
1108     /**
1109      * {@literal 1900-01-01 00:00:00.000}の時間(単位はミリ秒)。
1110      * <p>Excelは設定により、1900年始まりか1904年始まりか指定できるため、その基準値として利用する。
1111      */
1112     public static final long MILLISECONDS_19000101_END = ExcelDateUtils.parseDate("1900-01-01 23:59:54.999").getTime();
1113 
1114     /**
1115      * セルに日時を設定する。
1116      * <p>1900年1月0日となる経過時間指定の場合は、POIのバグにより設定できあいため、数値として設定する。</p>
1117      *
1118      * @param cell 設定するセル
1119      * @param date セルに設定する日時
1120      * @param dateStart1904 1904年始まりの設定のシートかどうか
1121      */
1122     public static void setCellValueAsDate(Cell cell, Date date, boolean dateStart1904) {
1123 
1124         ArgUtils.notNull(cell, "cell");
1125         ArgUtils.notNull(date, "date");
1126 
1127         if(dateStart1904) {
1128             // 1904年始まりの場合は、そのまま設定する
1129             cell.setCellValue(date);
1130 
1131         } else {
1132 
1133             long timemills = date.getTime();
1134             if(timemills <= MILLISECONDS_19000101_END) {
1135                 // 1900年1月0日の場合は、数値に変換してから設定する
1136                 // タイムゾーンを除去する
1137                 Date strip = new Date(date.getTime() + TimeZone.getDefault().getRawOffset());
1138                 double num = ExcelDateUtils.convertExcelNumber(strip, dateStart1904);
1139                 cell.setCellValue(num);
1140 
1141             } else {
1142                 cell.setCellValue(date);
1143             }
1144 
1145         }
1146 
1147     }
1148 
1149 }