    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.model;
    017
    018import java.io.File;                                                                    // 6.2.0.0 (2015/02/27)
    019import java.io.IOException;
    020import java.io.OutputStream;
    021import java.io.FileOutputStream;
    022import java.io.BufferedOutputStream;
    023import java.util.Locale;
    024import java.util.Map;                                                                   // 6.0.2.3 (2014/10/10) 画像関連
    025import java.util.HashMap;                                                               // 6.0.2.3 (2014/10/10) 画像関連
    026import java.util.List;                                                                  // 8.0.1.0 (2021/10/29)
    027
    028import org.apache.poi.util.Units;                                               // 7.2.9.0 (2020/10/12)
    029
    030import org.apache.poi.common.usermodel.HyperlinkType;   // 6.5.0.0 (2016/09/30) poi-3.15
    031import org.apache.poi.ss.util.WorkbookUtil;
    032import org.apache.poi.ss.usermodel.Workbook;
    033import org.apache.poi.ss.usermodel.Sheet;
    034import org.apache.poi.ss.usermodel.Row;
    035import org.apache.poi.ss.usermodel.Cell;
    036import org.apache.poi.ss.usermodel.CellType;                    // 6.5.0.0 (2016/09/30) poi-3.15
    037import org.apache.poi.ss.usermodel.CellStyle;
    038import org.apache.poi.ss.usermodel.VerticalAlignment;   // 6.5.0.0 (2016/09/30) poi-3.15
    039import org.apache.poi.ss.usermodel.BorderStyle;                 // 6.5.0.0 (2016/09/30) poi-3.15
    040import org.apache.poi.ss.usermodel.Font;
    041import org.apache.poi.ss.usermodel.IndexedColors;
    042import org.apache.poi.ss.usermodel.RichTextString;
    043import org.apache.poi.ss.usermodel.Hyperlink;
    044import org.apache.poi.ss.usermodel.CreationHelper;
    045import org.apache.poi.ss.usermodel.Drawing;                             // 6.0.2.3 (2014/10/10) 画像関連
    046import org.apache.poi.ss.usermodel.ClientAnchor;                // 6.0.2.3 (2014/10/10) 画像関連
    047import org.apache.poi.ss.usermodel.Picture;                             // 6.0.2.3 (2014/10/10) 画像関連
    048
    049import org.apache.poi.hssf.usermodel.HSSFWorkbook;              // .xls
    050
    051//import org.apache.poi.POIXMLDocumentPart;                             // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    052import org.apache.poi.ooxml.POIXMLDocumentPart;                 // 7.0.0.0 (2018/10/01) poi-ooxml-3.17.jar → poi-ooxml-4.0.0.jar
    053
    054import org.apache.poi.xssf.usermodel.XSSFDrawing;               // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    055import org.apache.poi.xssf.usermodel.XSSFShape;                 // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    056import org.apache.poi.xssf.usermodel.XSSFSimpleShape;   // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    057import org.apache.poi.xssf.usermodel.XSSFTextParagraph; // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    058import org.apache.poi.xssf.usermodel.XSSFTextRun;               // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    059import org.apache.poi.xssf.streaming.SXSSFWorkbook;             // .xlsx 6.3.7.0 (2015/09/04) 制限あり　高速､低ﾒﾓﾘ消費
    060
    061import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29)
    062import org.opengion.fukurou.system.Closer;
    063import org.opengion.fukurou.util.ImageUtil;                             // 6.0.2.3 (2014/10/10) 画像関連
    064
    065import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
    066import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    067
    068/**
    069 * POI による､EXCELﾊﾞｲﾅﾘﾌｧｲﾙに対する､ﾃﾞｰﾀﾓﾃﾞﾙｸﾗｽです｡
    070 *
    071 * 共通的な EXCEL処理 を集約しています｡
    072 * staticﾒｿｯﾄﾞによる簡易的なｱｸｾｽの他に､順次処理も可能なように
    073 * 現在ｱｸｾｽ中の､Workbook､Sheet､Row､Cell ｵﾌﾞｼﾞｪｸﾄを内部で管理しています｡
    074 *
    075 * 入力形式は､openXML形式にも対応しています｡
    076 * ﾌｧｲﾙの内容に応じて､.xlsと.xlsxのどちらで読み取るかは､内部的に
    077 * 自動判定されます｡
    078 *
    079 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    080 * @og.group その他
    081 *
    082 * @version  6.0
    083 * @author   Kazuhiko Hasegawa
    084 * @since    JDK7.0,
    085 */
    086public class ExcelModel {
    087        /** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します｡ {@value} */
    088        private static final String VERSION = "8.0.1.0 (2021/10/29)" ;
    089
    090        private static final String DEF_SHEET_NAME = "Sheet" ;
    091
    092        // 6.0.2.3 (2014/10/10) ImageUtil の Suffix と､Workbook.PICTURE_TYPE_*** の関連付けをしておきます｡
    093        // Suffix 候補は､[bmp, gif, jpeg, jpg, png, wbmp] だが､対応する PICTURE_TYPE は一致しない｡
    094        /** staticｲﾆｼｬﾗｲｻﾞ後､読み取り専用にするので､ConcurrentHashMap を使用しません｡ */
    095        private static final Map<String,Integer> PICTURE_TYPE ;
    096        static {
    097                PICTURE_TYPE = new HashMap<>() ;
    098                PICTURE_TYPE.put( "png"  , Integer.valueOf( Workbook.PICTURE_TYPE_PNG   ) );
    099                PICTURE_TYPE.put( "jpeg" , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG  ) );
    100                PICTURE_TYPE.put( "jpg"  , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG  ) );
    101        }
    102
    103        private final String inFilename ;               // ｴﾗｰ発生時のｷｰとなる､EXCELﾌｧｲﾙ名
    104        private final String sufix              ;               // 6.1.0.0 (2014/12/26) ｵｰﾌﾟﾝしたﾌｧｲﾙ形式を記憶(ﾋﾟﾘｵﾄﾞを含む)
    105
    106        private final Workbook  wkbook  ;               // 現在処理中の Workbook
    107        private Sheet                   sheet   ;               // 現在処理中の Sheet
    108        private Row                             rowObj  ;               // 現在処理中の Row
    109
    110        private int refSheetIdx = -1;                   // 雛形ｼｰﾄのｲﾝﾃﾞｯｸｽ
    111
    112        private final CreationHelper createHelper       ;       // poi.xssf対応
    113
    114        private CellStyle style                 ;               // 共通のｾﾙｽﾀｲﾙ
    115        private CellStyle hLinkStyle    ;               // Hyperlink用のｾﾙｽﾀｲﾙ(青文字＋下線)
    116
    117        private int maxColCount                 = 5 ;   // 標準ｾﾙ幅の５倍を最大幅とする｡
    118        private int dataStartRow                = -1;   // ﾃﾞｰﾀ行の開始位置｡未設定時は､-1
    119        private boolean isAutoCellSize  ;               // ｶﾗﾑ幅の自動調整を行うかどうか(true:行う/false:行わない)
    120
    121        private String addTitleSheet    ;               // Sheet一覧を先頭Sheetに作成する場合のSheet名
    122
    123        private String[] recalcSheetNames       ;       // 6.5.0.0 (2016/09/30) ｾﾙの計算式の再計算をさせるｼｰﾄ名の配列｡
    124
    125        /**
    126         * EXCELﾌｧｲﾙのWookbookのﾃﾞｰﾀ処理ﾓﾃﾞﾙを作成します｡
    127         *
    128         * ここでは､既存のﾌｧｲﾙを読み込んで､ﾃﾞｰﾀ処理ﾓﾃﾞﾙを作成しますので､
    129         * ﾌｧｲﾙがｵｰﾌﾟﾝできなければｴﾗｰになります｡
    130         *
    131         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    132         * @og.rev 6.2.0.0 (2015/02/27) ﾌｧｲﾙ引数を､String → File に変更
    133         *
    134         * @param   file  EXCELﾌｧｲﾙ
    135         * @see         #ExcelModel( File , boolean )
    136         */
    137        public ExcelModel( final File file ) {
    138                this( file,true );
    139        }
    140
    141        /**
    142         * EXCELﾌｧｲﾙのWookbookのﾃﾞｰﾀ処理ﾓﾃﾞﾙを作成します｡
    143         *
    144         * isOpen条件によって､ﾌｧｲﾙｵｰﾌﾟﾝ(true)か､新規作成(false)が分かれます｡
    145         * ﾌｧｲﾙｵｰﾌﾟﾝの場合は､EXCELの読み込み以外に､追記するとか､雛形参照する
    146         * 場合にも､使用します｡
    147         * ﾌｧｲﾙｵｰﾌﾟﾝの場合は､当然､ﾌｧｲﾙがｵｰﾌﾟﾝできなければｴﾗｰになります｡
    148         *
    149         * isOpen=新規作成(false) の場合は､ﾌｧｲﾙ名の拡張子で､XSSFWorkbook か HSSFWorkbook を
    150         * 判定します｡.xlsx の場合⇒XSSFWorkbook ｵﾌﾞｼﾞｪｸﾄを使用します｡
    151         *
    152         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    153         * @og.rev 6.0.2.3 (2014/10/10) POIUtil#createWorkbook( String ) を使用するように変更
    154         * @og.rev 6.1.0.0 (2014/12/26) 入力ﾌｧｲﾙの拡張子判定の対応
    155         * @og.rev 6.2.0.0 (2015/02/27) ﾌｧｲﾙ引数を､String → File に変更
    156         * @og.rev 6.2.2.0 (2015/03/27) ﾏｸﾛ付Excel(.xlsm)対応
    157         * @og.rev 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を､SXSSFWorkbook に切り替えてみる｡
    158         *
    159         * @param   file   EXCELﾌｧｲﾙ
    160         * @param   isOpen true:ﾌｧｲﾙｵｰﾌﾟﾝ/false:新規作成
    161         * @see         #ExcelModel( File )
    162         */
    163        public ExcelModel( final File file , final boolean isOpen ) {
    164                inFilename      = file.getName();
    165
    166                final int idx = inFilename.lastIndexOf( '.' );  // 拡張子の位置
    167                if( idx >= 0 ) {
    168                        sufix = inFilename.substring( idx ).toLowerCase( Locale.JAPAN );                // ﾋﾟﾘｵﾄﾞを含む
    169                }
    170                else {
    171                        final String errMsg = "ﾌｧｲﾙの拡張子が見当たりません｡(.xls か .xlsx/.xlsm を指定下さい)" + CR
    172                                                        + " filename=[" + file + "]"  + CR ;
    173                        throw new IllegalArgumentException( errMsg );
    174                }
    175
    176                if( isOpen ) {
    177                        wkbook = POIUtil.createWorkbook( file );
    178                }
    179                else {
    180                        // 新規の場合､ﾌｧｲﾙ名に.xlsxで終了した場合⇒.xlsx形式ﾌｧｲﾙ作成､その他⇒.xls形式ﾌｧｲﾙ作成
    181                        if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) {              // 6.2.2.0 (2015/03/27)
    182                                // 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を､SXSSFWorkbook に切り替えてみる｡
    183        //                      wkbook = new XSSFWorkbook();
    184                                wkbook = new SXSSFWorkbook();   // 機能制限有：ｼｰﾄや行の削除や､AutoCellSize の指定ができないなど｡
    185                        }
    186                        else if( ".xls".equals( sufix ) ) {
    187                                wkbook = new HSSFWorkbook();
    188                        }
    189                        else {
    190                                final String errMsg = "ﾌｧｲﾙの拡張子が不正です｡(.xls か .xlsx/.xlsm のみ可能)" + CR
    191                                                                + " filename=[" + file + "]"  + CR ;
    192                                throw new IllegalArgumentException( errMsg );
    193                        }
    194                }
    195
    196                createHelper = wkbook.getCreationHelper();              // poi.xssf対応
    197        }
    198
    199        /**
    200         * 内部 Workbook に､ﾌｫﾝﾄ名､ﾌｫﾝﾄｻｲｽﾞを設定します｡
    201         * fontName(ﾌｫﾝﾄ名)は､"ＭＳ Ｐｺﾞｼｯｸ" など名称になります｡
    202         * fontPoint は､ﾌｫﾝﾄの大きさを指定します｡
    203         * 内部的には､setFontHeightInPoints(short)ﾒｿｯﾄﾞで設定します｡
    204         *
    205         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    206         *
    207         * @param       fontName        ﾌｫﾝﾄ名 ("ＭＳ Ｐｺﾞｼｯｸ" など｡nullの場合ｾｯﾄしません)
    208         * @param       fontPoint       ﾌｫﾝﾄの大きさ (0やﾏｲﾅｽの場合はｾｯﾄしません)
    209         */
    210        public void setFont( final String fontName , final short fontPoint ) {
    211        //      System.out.println( "FontName=" + fontName + " , Point=" + fontPoint );
    212
    213                if( style == null ) { style = wkbook.createCellStyle(); }
    214
    215                final Font font = wkbook.createFont();
    216        //      final Font font = wkbook.getFontAt( style.getFontIndex() );                             // A,B などのﾍｯﾀﾞｰもﾌｫﾝﾄが
    217                if( fontName != null ) {
    218                        font.setFontName( fontName );   // "ＭＳ Ｐｺﾞｼｯｸ" など
    219                }
    220                if( fontPoint > 0 ) {
    221                        font.setFontHeightInPoints( fontPoint );
    222                }
    223
    224                style.setFont( font );
    225        }
    226
    227        /**
    228         * ﾃﾞｰﾀ設定する ｾﾙに､罫線を追加します｡
    229         *
    230         * ここで設定するのは､罫線の種類と､罫線の色ですが､内部的に固定にしています｡
    231         *   Border=CellStyle.BORDER_THIN
    232         *   BorderColor=IndexedColors.BLACK
    233         *
    234         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    235         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    236         */
    237        public void setCellStyle() {
    238                if( style == null ) { style = wkbook.createCellStyle(); }
    239
    240        //      style.setBorderBottom(  CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
    241        //      style.setBorderLeft(    CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
    242        //      style.setBorderRight(   CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
    243        //      style.setBorderTop(             CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
    244
    245                style.setBorderBottom(  BorderStyle.THIN );                     // 6.4.6.0 (2016/05/27) poi-3.15
    246                style.setBorderLeft(    BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
    247                style.setBorderRight(   BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
    248                style.setBorderTop(             BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
    249
    250                style.setBottomBorderColor(     IndexedColors.BLACK.getIndex() );
    251                style.setLeftBorderColor(       IndexedColors.BLACK.getIndex() );
    252                style.setRightBorderColor(      IndexedColors.BLACK.getIndex() );
    253                style.setTopBorderColor(        IndexedColors.BLACK.getIndex() );
    254
    255        //      style.setVerticalAlignment( CellStyle.VERTICAL_TOP );   // isAutoCellSize=true 文字は上寄せする｡        // 6.5.0.0 (2016/09/30) poi-3.12
    256                style.setVerticalAlignment( VerticalAlignment.TOP  );   // isAutoCellSize=true 文字は上寄せする｡        // 6.5.0.0 (2016/09/30) poi-3.15
    257        //      style.setWrapText( true );                                                              // isAutoCellSize=true 折り返して表示する｡
    258        }
    259
    260        /**
    261         * 全てのSheetに対して､autoSizeColumn設定を行うかどうか指定します(初期値:false)｡
    262         *
    263         * autoSize設定で､ｶﾗﾑ幅が大きすぎる場合､現状では､
    264         * 初期ｶﾗﾑ幅の５倍を限度にしています｡
    265         *
    266         * なお､autoSizeColumn設定は負荷の大きな処理なので､saveFile(String)の
    267         * 中で実行されます｡(ｾｰﾌﾞしなければ実行されません｡)
    268         * よって､指定は､いつ行っても構いません｡
    269         *
    270         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    271         *
    272         * @param flag autoSizeColumn設定を行うかどうか [true:自動ｶﾗﾑ幅設定を行う/false:行わない]
    273         * @see #useAutoCellSize( boolean,int )
    274         */
    275        public void useAutoCellSize( final boolean flag ) {
    276                isAutoCellSize = flag;
    277        }
    278
    279        /**
    280         * 全てのSheetに対して､autoSizeColumn設定を行うかどうか指定します(初期値:false)｡
    281         *
    282         * autoSize設定で､ｶﾗﾑ幅が大きすぎる場合､現状では､
    283         * 初期ｶﾗﾑ幅のcount倍を限度に設定します｡
    284         * ただし､count がﾏｲﾅｽの場合は､無制限になります｡
    285         *
    286         * なお､autoSizeColumn設定は負荷の大きな処理なので､saveFile(String)の
    287         * 中で実行されます｡(ｾｰﾌﾞしなければ実行されません｡)
    288         * よって､指定は､いつ行っても構いません｡
    289         *
    290         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    291         *
    292         * @param flag autoSizeColumn設定を行うかどうか [true:自動ｶﾗﾑ幅設定を行う/false:行わない]
    293         * @param count 最大幅を標準ｾﾙ幅の何倍にするかを指定｡ﾏｲﾅｽの場合は､無制限
    294         * @see #useAutoCellSize( boolean )
    295         */
    296        public void useAutoCellSize( final boolean flag, final int count ) {
    297                isAutoCellSize = flag;
    298                maxColCount    = count ;
    299        }
    300
    301        /**
    302         * EXCELで､出力処理の最後にｾﾙの計算式の再計算をさせるｼｰﾄ名の配列を指定します｡
    303         *
    304         * null の場合は､再計算しません｡
    305         * なお､再計算は､saveFile(String)の中で実行されます｡(ｾｰﾌﾞしなければ実行されません｡)
    306         *
    307         * @og.rev 6.5.0.0 (2016/09/30) ｾﾙの計算式の再計算をさせる recalcSheetNames 属性の追加｡
    308         *
    309         * @param  sheets 対象ｼｰﾄ名の配列
    310         */
    311        public void setRecalcSheetName( final String[] sheets ){
    312                recalcSheetNames = sheets;
    313        }
    314
    315        /**
    316         * ﾃﾞｰﾀ行の書き込み開始位置の行番号を設定します｡
    317         *
    318         * これは､autoSize設定で､自動調整するｶﾗﾑを､ﾍｯﾀﾞｰではなく､
    319         * ﾃﾞｰﾀ部で計算する場合に使用します｡
    320         *
    321         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    322         *
    323         * @param st  ﾃﾞｰﾀ行の開始位置｡未設定時は､-1
    324         * @see #useAutoCellSize( boolean )
    325         */
    326        public void setDataStartRow( final int st ) {
    327                dataStartRow = st;
    328        }
    329
    330        /**
    331         * Sheet一覧を先頭Sheetに作成する場合のSheet名を指定します｡
    332         *
    333         * これは､Workbook に含まれる Sheet 一覧を作成する場合に､利用可能です｡
    334         *
    335         * この処理は､#saveFile( File ) 処理時に､実行されます｡
    336         *
    337         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    338         *
    339         * @param shtName  Sheet一覧のSheet名
    340         * @see #makeAddTitleSheet()
    341         */
    342        public void setAddTitleSheet( final String shtName ) {
    343                addTitleSheet = shtName;
    344        }
    345
    346        /**
    347         * 内部 Workbookの Sheet数を返します｡
    348         *
    349         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    350         *
    351         * @return      ｼｰﾄ数
    352         */
    353        public int getNumberOfSheets() {
    354                return wkbook.getNumberOfSheets();
    355        }
    356
    357        /**
    358         * 内部 Workbookより､雛形Sheetをｾｯﾄします｡
    359         *
    360         * これは､雛形ｼｰﾄを使用する場合に､使います｡このﾒｿｯﾄﾞが呼ばれると､
    361         * 雛形ｼｰﾄを使用すると判定されます｡
    362         * 雛形ｼｰﾄ名が､内部 Workbook に存在しない場合は､ｴﾗｰになります｡
    363         * ただし､null をｾｯﾄした場合は､最初のｼｰﾄを雛形ｼｰﾄとして使用すると
    364         * 判定します｡
    365         *
    366         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    367         *
    368         * @param       refSheetName    参照ｼｰﾄ名(nullの場合､参照ｼｰﾄ使用する場合は､先頭のｼｰﾄ)
    369         */
    370        public void setRefSheetName( final String refSheetName ) {
    371                // 参照ｼｰﾄ名の指定がない場合は､最初のｼｰﾄ
    372                refSheetIdx = ( refSheetName == null ) ? 0 : wkbook.getSheetIndex( refSheetName );
    373
    374                if( refSheetIdx < 0 ) {         // 参照ｼｰﾄ名が存在しなかった｡
    375                        final String errMsg = "指定の参照ｼｰﾄ名は存在しませんでした｡" + CR
    376                                                        + " inFilename=[" + inFilename + "] , refSheetName=[" + refSheetName + "]"  + CR ;
    377                        throw new IllegalArgumentException( errMsg );
    378                }
    379        }
    380
    381        /**
    382         * 内部 Workbookより､新しいSheetを作ります｡
    383         *
    384         * 先に雛形ｼｰﾄを指定している場合は､その雛形ｼｰﾄから作成します｡
    385         * 指定していない場合は､新しいｼｰﾄを作成します｡
    386         * 雛形ｼｰﾄを参照する場合は､雛形ｼｰﾄそのものを返します｡
    387         * また､雛形ｼｰﾄの枚数を超える場合は､前の雛形ｼｰﾄをｺﾋﾟｰします｡
    388         * 雛形ｼｰﾄが存在しない場合は､新しいｼｰﾄを作成します｡
    389         *
    390         * ｼｰﾄ名は､重複ﾁｪｯｸを行い､同じ名前のｼｰﾄの場合は､(1),(2)が付けられます｡
    391         * shtName が null の場合は､"Sheet" が割り振られます｡
    392         *
    393         * この処理を行うと､内部の Sheet にも､ここで作成された Sheet が設定されます｡
    394         *
    395         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    396         * @og.rev 6.2.2.3 (2015/04/10) 雛形ｼｰﾄにそのままﾃﾞｰﾀを書き込んでいく｡
    397         * @og.rev 6.5.0.0 (2016/09/30) 雛形ｼｰﾄ名をそのまま使用する場合は､isOverwrite に､true を指定します｡
    398         *
    399         * @param       shtName ｼｰﾄ名 (重複する場合は､(2)､(3)のような文字列を追加 ､nullの場合は､"Sheet")
    400         * @param       isOverwrite     雛形ｼｰﾄ名をそのまま使用する場合は､true を指定します｡
    401         */
    402        public void createSheet( final String shtName , final boolean isOverwrite ) {
    403                // 参照ｼｰﾄを使う場合(整合性の問題で､両方ともﾁｪｯｸしておきます)
    404
    405                // 6.2.2.3 (2015/04/10) 雛形ｼｰﾄにそのままﾃﾞｰﾀを書き込んでいく｡
    406                final int shtNo ;
    407                if( refSheetIdx < 0 ) {                                                                 // 雛形ｼｰﾄを使用しない｡
    408                        sheet = wkbook.createSheet();
    409                        shtNo = wkbook.getNumberOfSheets() - 1;
    410                }
    411                else if( refSheetIdx >= wkbook.getNumberOfSheets() ) {  // ｼｰﾄ数が雛形より超えている｡
    412                        sheet = wkbook.cloneSheet( refSheetIdx-1 );                     // 最後の雛形ｼｰﾄをｺﾋﾟｰします｡
    413                        shtNo = wkbook.getNumberOfSheets() - 1;
    414                        refSheetIdx++ ;
    415                }
    416                else {
    417                        sheet = wkbook.getSheetAt( refSheetIdx );                       // 雛形ｼｰﾄをそのまま使用
    418                        shtNo = refSheetIdx;
    419                        refSheetIdx++ ;
    420                }
    421
    422                // 6.5.0.0 (2016/09/30) 雛形ｼｰﾄ名をそのまま使用する場合｡
    423                if( !isOverwrite ) {
    424                        setSheetName( shtNo , shtName );
    425                }
    426        }
    427
    428        /**
    429         * 内部 Workbook の指定のｼｰﾄ番号の Sheet の名前を設定します｡
    430         *
    431         * 指定のｼｰﾄ名が､既存のｼｰﾄになければ､そのまま設定します｡
    432         * すでに､同じ名前のｼｰﾄが存在する場合は､そのｼｰﾄ名の後に
    433         * (1)､(2)､(3)のような文字列を追加します｡
    434         * shtName が null の場合は､"Sheet" が割り振られます｡
    435         *
    436         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    437         * @og.rev 6.2.5.1 (2015/06/12) ｼｰﾄ名重複が自分自身の場合は､(1)等の追加は行わない｡
    438         *
    439         * @param       shtNo           ｼｰﾄ番号
    440         * @param       shtName ｼｰﾄ名 (重複する場合は､(1)､(2)のような文字列を追加 ､nullの場合は､"Sheet")
    441         */
    442        public void setSheetName( final int shtNo, final String shtName ) {
    443                String tempName = ( shtName == null ) ? DEF_SHEET_NAME : WorkbookUtil.createSafeSheetName( shtName ) ;
    444                int cnt = 1;
    445
    446                // 6.2.5.1 (2015/06/12) ｼｰﾄ名重複が自分自身の場合は､(1)等の追加は行わない｡
    447                // ※ EXCELのｼｰﾄ名は､大文字､小文字だけでなく､全角半角の区別もしない｡
    448                final String nowName = wkbook.getSheetName( shtNo );
    449                if( tempName != null && !tempName.equals( nowName ) ) {                 // 全く同一の場合は､何もしない｡
    450                        if( shtNo == wkbook.getSheetIndex( tempName ) ) {                       // ｼｰﾄ名判定が､自身の場合
    451                                wkbook.setSheetName( shtNo,tempName );
    452                        }
    453                        else {
    454                                while( wkbook.getSheetIndex( tempName ) >= 0 ) {                // ｼｰﾄ名が存在している場合
    455                                        tempName = WorkbookUtil.createSafeSheetName( shtName + "(" + cnt + ")" );
    456                                        if( tempName.length() >= 31 ) {                                         // 重複時の追加文字分を減らす｡
    457                                                tempName = tempName.substring( 0,26 ) + "(" + cnt + ")" ;       // cnt３桁まで可能
    458                                        }
    459                                        cnt++;
    460                                }
    461                                wkbook.setSheetName( shtNo,tempName );
    462                        }
    463                }
    464        }
    465
    466        /**
    467         * 内部 Workbook の 指定のSheet番号のｼｰﾄ名前を返します｡
    468         *
    469         * ｼｰﾄが存在しない場合は､null を返します｡
    470         *
    471         * この処理を行うと､内部の Sheet にも､ここで見つけた Sheet が設定されます｡
    472         *
    473         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    474         *
    475         * @param        shtNo          ｼｰﾄ番号
    476         *
    477         * @return      shtName ｼｰﾄ名
    478         */
    479        public String getSheetName( final int shtNo ) {
    480                final int shLen = wkbook.getNumberOfSheets();
    481
    482                String shtName = null;
    483                if( shtNo < shLen ) {
    484                        sheet = wkbook.getSheetAt( shtNo );             // 現在の sheet に設定する｡
    485                        shtName = sheet.getSheetName();
    486                }
    487
    488                return shtName ;
    489        }
    490
    491        /**
    492         * 内部 Workbook の 指定のSheet名のｼｰﾄ番号を返します｡
    493         *
    494         * ｼｰﾄが存在しない場合は､-1 を返します｡
    495         * この処理を行うと､内部の Sheet にも､ここで見つけた Sheet が設定されます｡
    496         * ｼｰﾄが存在しない場合､内部の Sheet ｵﾌﾞｼﾞｪｸﾄも null がｾｯﾄされますのでご注意ください｡
    497         *
    498         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    499         *
    500         * @param        shtName                ｼｰﾄ名
    501         *
    502         * @return      ｼｰﾄ番号(名前のｼｰﾄがなければ､-1)
    503         */
    504        public int getSheetNo( final String shtName ) {
    505                sheet = wkbook.getSheet( shtName );                                     // ｼｰﾄ名がﾏｯﾁしなければ､null
    506
    507                return wkbook.getSheetIndex( shtName ) ;                        // ｼｰﾄ名がﾏｯﾁしなければ､-1
    508        }
    509
    510        /**
    511         * Excelの指定Sheetｵﾌﾞｼﾞｪｸﾄを削除します｡
    512         *
    513         * 削除するｼｰﾄは､ｼｰﾄ番号でFrom-To形式で指定します｡
    514         * Fromも Toも､削除するｼｰﾄ番号を含みます｡
    515         * 例えば､0,3 と指定すると､0,1,2,3 の ４ｼｰﾄ分を削除します｡
    516         *
    517         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    518         *
    519         * @param        fromNo         削除する開始ｼｰﾄ番号(含む)
    520         * @param        toNo           削除する終了ｼｰﾄ番号(含む)
    521         */
    522        public void removeSheet( final int fromNo,final int toNo ) {
    523                for( int shtNo=toNo; shtNo>=fromNo; shtNo-- ) {                 // 逆順に処理します｡
    524                        wkbook.removeSheetAt( shtNo );
    525                }
    526        }
    527
    528        /**
    529         * 内部 Workbookの 現在Sheet の最初の行番号を返します｡
    530         *
    531         * 行は､0 から始まります｡
    532         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    533         *
    534         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    535         *
    536         * @return      最初の行番号
    537         */
    538        public int getFirstRowNum() {
    539                return sheet.getFirstRowNum();
    540        }
    541
    542        /**
    543         * 内部 Workbookの 現在Sheet の最後の行番号を返します｡
    544         *
    545         * 最終行は､含みます｡よって､行数は､getLastRowNum()＋１になります｡
    546         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    547         *
    548         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    549         *
    550         * @return      最後の行番号
    551         */
    552        public int getLastRowNum() {
    553                return sheet.getLastRowNum();
    554        }
    555
    556        /**
    557         * Excelの指定行のRowｵﾌﾞｼﾞｪｸﾄを作成します｡
    558         *
    559         * 指定行の Row ｵﾌﾞｼﾞｪｸﾄが存在しない場合は､新規作成します｡
    560         * この処理を実行すると､指定行の Rowｵﾌﾞｼﾞｪｸﾄが内部 Row に設定されます｡
    561         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    562         *
    563         * この処理を行うと､内部の Rowｵﾌﾞｼﾞｪｸﾄが設定されます｡
    564         *
    565         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    566         *
    567         * @param        rowNo          行の番号
    568         */
    569        public void createRow( final int rowNo ) {
    570                rowObj = sheet.getRow( rowNo );
    571                if( rowObj == null ) { rowObj = sheet.createRow( rowNo ); }
    572        }
    573
    574        /**
    575         * Excelの指定行以降の余計なRowｵﾌﾞｼﾞｪｸﾄを削除します｡
    576         *
    577         * 指定行の Row ｵﾌﾞｼﾞｪｸﾄから､getLastRowNum() までの行を､削除します｡
    578         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    579         *
    580         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    581         *
    582         * @param        startRowNum            指定以降の余計な行を削除
    583         */
    584        public void removeRow( final int startRowNum ) {
    585                final int stR = startRowNum;
    586                final int edR = sheet.getLastRowNum();
    587
    588                for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {                 // 逆順に処理します｡
    589                        final Row rowObj = sheet.getRow( rowNo );
    590                        if( rowObj != null ) { sheet.removeRow( rowObj ); }
    591                }
    592        }
    593
    594        /**
    595         * Excelの処理中のRowｵﾌﾞｼﾞｪｸﾄの指定ｶﾗﾑ以降の余計なCellｵﾌﾞｼﾞｪｸﾄを削除します｡
    596         *
    597         * 指定行の Row ｵﾌﾞｼﾞｪｸﾄから､getLastCellNum() までのｶﾗﾑを､削除します｡
    598         * この処理は､内部Rowが作成されているか､null でない場合のみ実行できます｡
    599         *
    600         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    601         *
    602         * @param        startCellNum           指定以降の余計なｶﾗﾑを削除
    603         */
    604        public void removeCell( final int startCellNum ) {
    605                final int stC = startCellNum;
    606                final int edC = rowObj.getLastCellNum();
    607
    608                for( int colNo=edC; colNo>=stC; colNo-- ) {                     // 逆順に処理します｡
    609                        final Cell colObj = rowObj.getCell( colNo );
    610                        if( colObj != null ) { rowObj.removeCell( colObj ); }
    611                }
    612        }
    613
    614        /**
    615         * row にあるｾﾙのｵﾌﾞｼﾞｪｸﾄ値を設定します｡
    616         *
    617         * 行が存在しない場合､行を追加します｡
    618         * この処理を行うと､内部の Rowｵﾌﾞｼﾞｪｸﾄがなければ新規作成されます｡
    619         *
    620         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    621         *
    622         * @param   vals  新しい配列値｡
    623         * @param   rowNo   値が変更される行(無視されます)
    624         */
    625        public void setValues( final String[] vals,final int rowNo ) {
    626                if( rowObj == null ) { createRow( rowNo ); }
    627
    628                if( vals != null ) {
    629                        for( int colNo=0; colNo<vals.length; colNo++ ) {
    630                                setCellValue( vals[colNo],colNo );
    631                        }
    632                }
    633        }
    634
    635        /**
    636         * row にあるｾﾙのｵﾌﾞｼﾞｪｸﾄ値を設定します｡
    637         *
    638         * 行が存在しない場合､行を追加します｡
    639         * 引数に､ｶﾗﾑがNUMBER型かどうかを指定することが出来ます｡
    640         * この処理を行うと､内部の Rowｵﾌﾞｼﾞｪｸﾄがなければ新規作成されます｡
    641         *
    642         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    643         *
    644         * @param   vals  新しい配列値｡
    645         * @param   rowNo   値が変更される行(無視されます)
    646         * @param       isNums  ｾﾙが､NUMBER型の場合は､true/それ以外は､false
    647         */
    648        public void setValues( final String[] vals,final int rowNo,final boolean[] isNums ) {
    649                if( rowObj == null ) { createRow( rowNo ); }
    650
    651                if( vals != null ) {
    652                        for( int colNo=0; colNo<vals.length; colNo++ ) {
    653                                setCellValue( vals[colNo],colNo,isNums[colNo] );
    654                        }
    655                }
    656        }
    657
    658        /**
    659         * Excelの指定ｾﾙにﾃﾞｰﾀを設定します｡
    660         *
    661         * ここで設定する行は､現在の内部 Row です｡
    662         * Row を切り替えたい場合は､#createRow( int ) を呼び出してください｡
    663         * このﾒｿｯﾄﾞでは､ﾃﾞｰﾀを文字列型として設定します｡
    664         * この処理は､内部Rowが作成されているか､null でない場合のみ実行できます｡
    665         *
    666         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    667         *
    668         * @param       dataVal    String文字列
    669         * @param       colNo           ｾﾙの番号(0,1,2････)
    670         * @see         #setCellValue( String,int,boolean )
    671         */
    672        public void setCellValue( final String dataVal , final int colNo ) {
    673                setCellValue( dataVal,colNo,false );
    674        }
    675
    676        /**
    677         * Excelの指定ｾﾙにﾃﾞｰﾀを設定します｡
    678         *
    679         * ここで設定する行は､現在の内部 Row です｡
    680         * Row を切り替えたい場合は､#createRow( int ) を呼び出してください｡
    681         * このﾒｿｯﾄﾞでは､引数のﾃﾞｰﾀ型をNUMBER型の場合は､doubleに変換して､
    682         * それ以外は文字列としてとして設定します｡
    683         * この処理は､内部Rowが作成されているか､null でない場合のみ実行できます｡
    684         *
    685         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    686         *
    687         * @param       dataVal         String文字列
    688         * @param       colNo           ｾﾙの番号(0,1,2････)
    689         * @param       isNumber        ｾﾙが､NUMBER型の場合は､true/それ以外は､false
    690         * @see         #createRow( int )
    691         * @see         #setCellValue( String,int )
    692         */
    693        public void setCellValue( final String dataVal , final int colNo , final boolean isNumber ) {
    694                Cell colObj = rowObj.getCell( colNo );
    695                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
    696
    697                if( style != null ) { colObj.setCellStyle(style); }
    698
    699                // CELL_TYPE_NUMERIC 以外は､String扱いします｡
    700                if( isNumber ) {
    701                        final Double dbl = parseDouble( dataVal );
    702                        if( dbl != null ) {
    703                                colObj.setCellValue( dbl.doubleValue() );
    704                                return ;                // Double 変換できた場合は､即抜けます｡
    705                        }
    706                }
    707
    708                final RichTextString richText = createHelper.createRichTextString( dataVal );
    709                colObj.setCellValue( richText );
    710        }
    711
    712        /**
    713         * Excelの指定ｾﾙにHyperlinkを設定します｡
    714         *
    715         * ここで設定する行は､現在の内部 Row です｡
    716         * Row を切り替えたい場合は､#createRow( int ) を呼び出してください｡
    717         * このﾒｿｯﾄﾞで設定するHyperlinkは､Sheetに対する LINK_DOCUMENT です｡
    718         * 先に､ｾﾙに対する値をｾｯﾄしておいてください｡
    719         * Hyperlinkは､文字に対して､下線 と 青字 のｽﾀｲﾙ設定を行います｡
    720         *
    721         * Link文字列(ｼｰﾄ名) が､null や ｾﾞﾛ文字列の場合は､処理を行いません｡
    722         *
    723         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    724         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Hyperlink.LINK_XXXX → HyperlinkType.XXXX)
    725         *
    726         * @param       linkVal         Link文字列(ｼｰﾄ名)
    727         * @param       colNo           ｾﾙの番号(0,1,2････)
    728         * @see         #setCellValue( String,int )
    729         */
    730        public void setCellLink( final String linkVal , final int colNo ) {
    731                if( linkVal == null || linkVal.isEmpty() ) { return; }
    732
    733                Cell colObj = rowObj.getCell( colNo );
    734                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
    735
    736                if( hLinkStyle == null ) {
    737                        hLinkStyle = wkbook.createCellStyle();
    738                        if( style != null ) { hLinkStyle.cloneStyleFrom(style); }
    739
    740                        final Font font = wkbook.createFont();
    741                        font.setColor( IndexedColors.BLUE.getIndex() );         // ﾘﾝｸは青文字
    742                        font.setUnderline( Font.U_SINGLE );                                     // 下線付
    743
    744                        hLinkStyle.setFont( font );
    745                }
    746                colObj.setCellStyle(hLinkStyle);
    747
    748        //      final Hyperlink hLink = createHelper.createHyperlink( Hyperlink.LINK_DOCUMENT );                // 6.5.0.0 (2016/09/30) poi-3.12
    749                final Hyperlink hLink = createHelper.createHyperlink( HyperlinkType.DOCUMENT );                 // 6.5.0.0 (2016/09/30) poi-3.15
    750                hLink.setAddress( "'" + linkVal + "'!A1" );
    751                colObj.setHyperlink( hLink );
    752        }
    753
    754        /**
    755         * 現在のRow にあるｾﾙの属性値を配列で返します｡
    756         *
    757         * Rowｵﾌﾞｼﾞｪｸﾄが存在しない場合は､長さ０の配列を返します｡
    758         * また､Rowｵﾌﾞｼﾞｪｸﾄの中の ｾﾙｵﾌﾞｼﾞｪｸﾄが存在しない場合は､
    759         * null がｾｯﾄされます｡
    760         *
    761         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    762         * この処理を実行すると､指定行の Rowｵﾌﾞｼﾞｪｸﾄが内部 Row に設定されます｡
    763         *
    764         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    765         * @og.rev 6.3.9.0 (2015/11/06) ExcelModel#getValues(int) では､nullは返さない｡
    766         * @og.rev 6.3.9.1 (2015/11/27) ﾒｿｯﾄﾞの出口は､最後の１か所にすべきです(PMD)｡
    767         *
    768         * @param       rowNo           行の番号
    769         * @return      指定されたｾﾙの属性値｡Rowがnullの場合は､長さ０の配列を返します｡
    770         * @og.rtnNotNull
    771         */
    772        public String[] getValues( final int rowNo ) {
    773                rowObj = sheet.getRow( rowNo );
    774
    775                final int len = rowObj == null ? 0 : rowObj.getLastCellNum();           // 含まないので､length と同じ意味になる｡
    776                final String[] vals = new String[len];                          // 6.3.9.1 (2015/11/27) ﾒｿｯﾄﾞの出口
    777
    778                for( int colNo=0; colNo<len; colNo++ ) {
    779                        final Cell colObj = rowObj.getCell( colNo );
    780                        vals[colNo] = POIUtil.getValue( colObj );
    781                }
    782
    783                return vals ;
    784        }
    785
    786        /**
    787         * 現在のrow にあるｾﾙの属性値を返します｡
    788         *
    789         * ｾﾙｵﾌﾞｼﾞｪｸﾄが存在しない場合は､null を返します｡
    790         *
    791         * この処理は､内部Sheetが作成されているか､null でない場合のみ実行できます｡
    792         * この処理を実行すると､指定行の Rowｵﾌﾞｼﾞｪｸﾄが内部 Row に設定されます｡
    793         *
    794         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    795         * @og.rev 6.3.9.1 (2015/11/27) ﾒｿｯﾄﾞの出口は､最後の１か所にすべきです(PMD)｡
    796         *
    797         * @param   rowNo     値が参照される行
    798         * @param   colNo     値が参照される列
    799         *
    800         * @return  指定されたｾﾙの値 T
    801         */
    802        public String getValue( final int rowNo, final int colNo ) {
    803                rowObj = sheet.getRow( rowNo );
    804
    805                return rowObj == null ? null : POIUtil.getValue( rowObj.getCell( colNo ) );
    806        }
    807
    808        /**
    809         * 指定のｼｰﾄの行･列の箇所に､ｲﾒｰｼﾞﾌｧｲﾙを挿入します｡
    810         *
    811         * ここでは､ｾﾙ範囲ではなく､指定の行列の箇所に､ｱﾝｶｰを設定して､画像ﾌｧｲﾙを
    812         * 挿入します｡一応､ﾘｻｲｽﾞして､元の大きさ近くに戻しますが､縦横比が変わってしまいます｡
    813         * 正確に挿入する場合は､ｾﾙ範囲の指定と､ﾏｰｼﾞﾝを指定しなければなりませんが､
    814         * 微調整が必要です｡
    815         *
    816         * この処理で使用される Sheetｵﾌﾞｼﾞｪｸﾄは一時的に作成されます｡(ｷｬｯｼｭされません)
    817         * 一連処理のどのﾀｲﾐﾝｸﾞで実行しても､内部の状態には影響はありません｡
    818         *
    819         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    820         *
    821         * @param   imgFile   挿入するｲﾒｰｼﾞﾌｧｲﾙ名
    822         * @param   shtNo     ｼｰﾄ番号
    823         * @param   rowNo     挿入する行
    824         * @param   colNo     挿入する列
    825         */
    826        public void addImageFile( final String imgFile, final int shtNo, final int rowNo, final int colNo ) {
    827                addImageFile( imgFile,shtNo,rowNo,colNo,rowNo,colNo,0,0,0,0 );
    828        }
    829
    830        /**
    831         * 指定のｼｰﾄの行･列の箇所に､ｲﾒｰｼﾞﾌｧｲﾙを挿入します｡
    832         *
    833         * ここでは､ｾﾙ範囲ではなく､指定の行列の箇所に､ｱﾝｶｰを設定して､画像ﾌｧｲﾙを
    834         * 挿入します｡一応､ﾘｻｲｽﾞして､元の大きさ近くに戻しますが､縦横比が変わってしまいます｡
    835         * 正確に挿入する場合は､ｾﾙ範囲の指定と､ﾏｰｼﾞﾝを指定しなければなりませんが､
    836         * 微調整が必要です｡
    837         *
    838         * この処理で使用される Sheetｵﾌﾞｼﾞｪｸﾄは一時的に作成されます｡(ｷｬｯｼｭされません)
    839         * 一連処理のどのﾀｲﾐﾝｸﾞで実行しても､内部の状態には影響はありません｡
    840         *
    841         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    842         * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備
    843         * @og.rev 6.8.2.4 (2017/11/20) poi-3.17 で､警告: [rawtypes] raw型が見つかりました対応
    844         * @og.rev 7.2.9.0 (2020/10/12) ClientAnchorのｵﾌｾｯﾄ指定は､Units.EMU_PER_PIXEL が単位
    845         *
    846         * @param   imgFile   挿入するｲﾒｰｼﾞﾌｧｲﾙ名
    847         * @param   shtNo     ｼｰﾄ番号
    848         * @param   row1      挿入する行(開始)
    849         * @param   col1      挿入する列(開始)
    850         * @param   row2      挿入する行(終了-含まず)
    851         * @param   col2      挿入する列(終了-含まず)
    852         * @param   dx1       開始ｾﾙのX軸座標のｵﾌｾｯﾄ(ﾋﾟｸｾﾙ)
    853         * @param   dy1       開始ｾﾙのY軸座標のｵﾌｾｯﾄ(ﾋﾟｸｾﾙ)
    854         * @param   dx2       終了ｾﾙのX軸座標のｵﾌｾｯﾄ(ﾋﾟｸｾﾙ)
    855         * @param   dy2       終了ｾﾙのY軸座標のｵﾌｾｯﾄ(ﾋﾟｸｾﾙ)
    856         */
    857        public void addImageFile( final String imgFile , final int shtNo ,
    858                                                                final int row1 , final int col1 , final int row2 , final int col2 ,
    859                                                                final int dx1  , final int dy1  , final int dx2  , final int dy2   ) {
    860                final String suffix   = ImageUtil.getSuffix( imgFile );
    861                final Integer picType = PICTURE_TYPE.get( suffix );
    862
    863                // 実験した結果､bmp,gif,tif については､PICTURE_TYPE_PNG で､挿入できた｡
    864                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    865                final int pictureType = picType == null ? Workbook.PICTURE_TYPE_PNG : picType.intValue() ;
    866
    867                final byte[] imgs = ImageUtil.byteImage( imgFile );
    868
    869                final int pictureIdx = wkbook.addPicture( imgs, pictureType );
    870
    871                final Sheet sheet = wkbook.getSheetAt( shtNo );
    872                // 6.8.2.4 (2017/11/20) poi-3.17 で､警告: [rawtypes] raw型が見つかりました対応
    873                final Drawing<?> patriarch = sheet.createDrawingPatriarch();            // 昔は一度しか実行できなかったようです｡
    874        //      final Drawing patriarch = sheet.createDrawingPatriarch();                       // 昔は一度しか実行できなかったようです｡
    875
    876//              final ClientAnchor anchor = patriarch.createAnchor( dx1,dy1,dx2,dy2,col1,row1,col2,row2 );
    877                final int px = Units.EMU_PER_PIXEL;                                                                     // 7.2.9.0 (2020/10/12)
    878                final ClientAnchor anchor = patriarch.createAnchor( px*dx1,px*dy1,px*dx2,px*dy2,col1,row1,col2,row2 );
    879
    880                // ClientAnchor anchor = createHelper.createClientAnchor();     でも作成可能｡
    881
    882                // MOVE_AND_RESIZE, MOVE_DONT_RESIZE, DONT_MOVE_AND_RESIZE から､決め打ち｡
    883        //      anchor.setAnchorType( ClientAnchor.MOVE_DONT_RESIZE );                                  // 6.4.6.0 (2016/05/27) poi-3.12
    884                anchor.setAnchorType( ClientAnchor.AnchorType.MOVE_DONT_RESIZE );               // 6.4.6.0 (2016/05/27) poi-3.15
    885
    886                final Picture pic = patriarch.createPicture( anchor, pictureIdx );
    887                // ｾﾙの範囲指定がｾﾞﾛの場合､画像ｻｲｽﾞもｾﾞﾛになる為､ﾘｻｲｽﾞしておく｡
    888                if( row1 == row2 || col1 == col2 ) { pic.resize(); }    // resize すると､anchor のﾏｰｼﾞﾝが無視されるようです｡
    889        }
    890
    891        /**
    892         * 内部 Workbook ｵﾌﾞｼﾞｪｸﾄをﾌｧｲﾙに書き出します｡
    893         *
    894         * Excelの形式は､ここで指定する出力ﾌｧｲﾙの拡張子ではなく､ｺﾝｽﾄﾗｸﾀで
    895         * 指定したﾌｧｲﾙの拡張子で決まります｡
    896         * 異なる形式の拡張子を持つﾌｧｲﾙを指定した場合､強制的に､ｵｰﾌﾟﾝした
    897         * Workbook の形式の拡張子を追加します｡
    898         *
    899         * 拡張子は､Excel 2007以降の形式(.xlsx)か､Excel 2003以前の形式(.xls) が指定できます｡
    900         * 拡張子が未設定の場合は､ｵｰﾌﾟﾝした Workbook の形式に合わせた拡張子を付与します｡
    901         *
    902         * isAutoCellSize=true の場合は､ここで全Sheetに対してCell幅の自動調整が行われます｡
    903         *
    904         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    905         * @og.rev 6.1.0.0 (2014/12/26) 入力ﾌｧｲﾙの拡張子判定の対応
    906         * @og.rev 6.2.0.0 (2015/02/27) ﾌｧｲﾙ引数を､String → File に変更
    907         * @og.rev 6.5.0.0 (2016/09/30) ｾﾙの計算式の再計算をさせる recalcSheetNames 属性の追加｡
    908         *
    909         * @param       file    ｾｰﾌﾞするﾌｧｲﾙ
    910         */
    911        public void saveFile( final File file ) {
    912                final File saveFile ;
    913                String fname = file.getName();
    914                if( fname.toLowerCase(Locale.JAPAN).endsWith( sufix ) ) {
    915                        saveFile = file;
    916                }
    917                else {
    918                        final int idx = fname.lastIndexOf( '.' );
    919                        if( idx >= 0 ) { fname = fname.substring( 0,idx ); }
    920                        saveFile = new File( file.getParent() , fname + sufix );
    921                }
    922
    923                if( isAutoCellSize ) { POIUtil.autoCellSize( wkbook, maxColCount, dataStartRow ); }
    924
    925                // 6.5.0.0 (2016/09/30) ｾﾙの計算式の再計算をさせる recalcSheetNames 属性の追加｡
    926                if( recalcSheetNames != null && recalcSheetNames.length > 0 ) {
    927                        for( final String shtName : recalcSheetNames ) {
    928                                final Sheet sht = wkbook.getSheet( shtName );                   // ｼｰﾄ名がﾏｯﾁしなければ､null
    929                                if( sht != null ) { sht.setForceFormulaRecalculation(true); }
    930                        }
    931                }
    932
    933                // こちらの都合で､TitleSheet は､autoCellSize ではなく､Sheet#autoSizeColumn(int) を使用して､自動計算させる｡
    934                if( addTitleSheet != null ) { makeAddTitleSheet(); }
    935
    936                OutputStream fileOut = null ;
    937                try {
    938                        fileOut = new BufferedOutputStream( new FileOutputStream( saveFile ) );         // 6.1.0.0 (2014/12/26)
    939                        wkbook.write( fileOut );
    940                }
    941                catch( final IOException ex ) {
    942                        final String errMsg = "ﾌｧｲﾙへ書込み中にｴﾗｰが発生しました｡" + CR
    943                                                        + "  File=" + saveFile + CR
    944                                                        + ex.getMessage() ;
    945                        throw new OgRuntimeException( errMsg,ex );
    946                }
    947                finally {
    948                        Closer.ioClose( fileOut );
    949                }
    950        }
    951
    952        /**
    953         * 内部 Workbook ｵﾌﾞｼﾞｪｸﾄのSheet一覧のSheetを､先頭に追加します｡
    954         *
    955         * これは､Workbook に含まれる Sheet 一覧を作成する場合に､利用可能です｡
    956         *
    957         * この処理は､内部のWorkbook､Sheetｵﾌﾞｼﾞｪｸﾄに依存して実行されます｡
    958         * また､単独ではなく､#saveFile( File ) 実行時に､addTitleSheet が
    959         * 設定されている場合のみ､実行されます｡
    960         *
    961         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    962         *
    963         * @see         #saveFile( File )
    964         * @see         #setAddTitleSheet( String )
    965         */
    966        private void makeAddTitleSheet() {
    967                sheet = wkbook.createSheet();
    968                final String shtNm = sheet.getSheetName();                              // Sheet名の取得
    969                wkbook.setSheetOrder( shtNm,0 );                                        // そのSheetを先頭に移動
    970                setSheetName( 0,addTitleSheet );                                        // そのSheet名を変更 → これが､TitleSheet
    971
    972                int rowNo = 0;
    973                createRow( rowNo++ );                                                           // 先頭行(ｲﾝｽﾀﾝｽ共通のRowｵﾌﾞｼﾞｪｸﾄ)作成
    974                setCellValue( "No"       , 0 );
    975                setCellValue( "Sheet", 1 );
    976
    977                final int shCnt = wkbook.getNumberOfSheets();
    978                for( int shtNo=1; shtNo<shCnt; shtNo++,rowNo++ ) {
    979                        final String nm = wkbook.getSheetName( shtNo );
    980
    981                        createRow( rowNo );                                                                     // 行の追加作成
    982                        setCellValue( String.valueOf( rowNo ),0,true );         // 行番号として､数字型で登録
    983                        setCellValue( nm , 1 );                                                         // ｼｰﾄの値を書き込む
    984                        setCellLink(  nm , 1 );                                                         // ｼｰﾄへのﾘﾝｸを作成する｡
    985                }
    986
    987                sheet.autoSizeColumn( 0 );
    988                sheet.autoSizeColumn( 1 );
    989        }
    990
    991        /**
    992         * 指定の Workbook の全Sheetを対象に､実際の有効行と有効ｶﾗﾑを取得します｡
    993         *
    994         * ※ 現在､唯一LibreOfficeでのみ､xslx 変換できますが､有効行とｶﾗﾑが
    995         *    ｼｭﾘﾝｸされず､無駄な行とｶﾗﾑが存在します｡
    996         *    これは､xsl で出力されたﾌｧｲﾙから有効な値を取得して､xslxに適用させるための
    997         *    機能で､本来きちんとした有効範囲の xslx が生成されれば､不要な処理です｡
    998         *
    999         * 配列は､[0]=行の最大値(Sheet#getLastRowNum())と､[1]は有効行の中の列の
    1000         * 最大値(Row#getLastCellNum())を､ｼｰﾄごとにListに追加していきます｡
    1001         *
    1002         * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に､実際の有効行と有効ｶﾗﾑを取得
    1003         *
    1004         * @return      ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1005         * @see         #activeWorkbook( List<int[]> )
    1006         */
    1007        public List<int[]> getLastRowCellNum() {
    1008                return POIUtil.getLastRowCellNum( wkbook );
    1009        }
    1010
    1011        /**
    1012         * Workbook の全Sheetを対象に､空行を取り除き､全体をｼｭﾘﾝｸします｡
    1013         *
    1014         * この処理は､#saveFile( File ) の直前に行うのがよいでしょう｡
    1015         *
    1016         * ここでは､Row を逆順にｽｷｬﾝし､Cellが 存在しない間は､行を削除します｡
    1017         * 途中の空行の削除ではなく､最終行からの連続した空行の削除です｡
    1018         *
    1019         * isCellDel=true を指定すると､Cellの末尾削除を行います｡
    1020         * 有効行の最後のCellから空ｾﾙを削除していきます｡
    1021         * 表形式などの場合は､Cellのあるなしで､ﾚｲｱｳﾄが崩れる場合がありますので
    1022         * 処理が不要な場合は､isCellDel=false を指定してください｡
    1023         *
    1024         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1025         *
    1026         * @param       isCellDel       Cellの末尾削除を行うかどうか(true:行う/false:行わない)
    1027         */
    1028        public void activeWorkbook( final boolean isCellDel ) {
    1029                POIUtil.activeWorkbook( wkbook, isCellDel );
    1030        }
    1031
    1032        /**
    1033         * 指定の Workbook の全Sheetを対象に､実際の有効行と有効ｶﾗﾑを元に全体をｼｭﾘﾝｸします｡
    1034         *
    1035         * ※ 現在､唯一LibreOfficeでのみ､xslx 変換できますが､有効行とｶﾗﾑが
    1036         *    ｼｭﾘﾝｸされず､無駄な行とｶﾗﾑが存在します｡
    1037         *    これは､xsl で出力されたﾌｧｲﾙから有効な値を取得して､xslxに適用させるための
    1038         *    機能で､本来きちんとした有効範囲の xslx が生成されれば､不要な処理です｡
    1039         *
    1040         * 引数のListｵﾌﾞｼﾞｪｸﾄに従って､無条件に処理を行います｡
    1041         *
    1042         * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に､実際の有効行と有効ｶﾗﾑを取得
    1043         *
    1044         * @param       rcList          ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1045         * @see         #getLastRowCellNum()
    1046         * @see         #activeWorkbook( boolean )
    1047         */
    1048        public void activeWorkbook( final List<int[]> rcList ) {
    1049                POIUtil.activeWorkbook( wkbook, rcList );
    1050        }
    1051
    1052        /**
    1053         * Workbook の全Sheetを対象に､ﾃｷｽﾄ変換処理を行います(XSLX限定)｡
    1054         *
    1055         * この処理は､#saveFile( File ) の直前に行うのがよいでしょう｡
    1056         * #activeWorkbook( boolean ) との順番は構いません｡
    1057         *
    1058         * ･ｼｰﾄ名の一覧をﾋﾟｯｸｱｯﾌﾟします｡
    1059         * ･ｾﾙ値を､ｾﾙ単位にﾋﾟｯｸｱｯﾌﾟします｡
    1060         * ･ｵﾌﾞｼﾞｪｸﾄ文字列を､改行単位にﾋﾟｯｸｱｯﾌﾟし､結果を合成します｡
    1061         *
    1062         * ここでは､内部的に､TextConverterｲﾝﾀｰﾌｪｰｽを作成して処理します｡
    1063         *
    1064         * @og.rev 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    1065         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに､引数(cmnt)を追加
    1066         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    1067         *
    1068         * @param       convMap 変換対象を管理するMapｵﾌﾞｼﾞｪｸﾄ
    1069         * @see         #textConverter( TextConverter )
    1070         */
    1071        public void textConverter( final Map<String,String> convMap ) {
    1072                textConverter(
    1073                        ( val,cmnt ) -> convMap.get( val )
    1074                );
    1075
    1076        //      textConverter(
    1077        //              new TextConverter<String,String>() {
    1078        //                      /**
    1079        //                       * 入力文字列を､変換します｡
    1080        //                       *
    1081        //                       * @param       val  入力文字列
    1082        //                       * @param       cmnt ｺﾒﾝﾄ
    1083        //                       * @return      変換文字列(変換されない場合は､null)
    1084        //                       */
    1085        //                      @Override
    1086        //                      public String change( final String val , final String cmnt ) {
    1087        //                              return convMap.get( val );
    1088        //                      }
    1089        //              }
    1090        //      );
    1091        }
    1092
    1093        /**
    1094         * Workbook の全Sheetを対象に､ﾃｷｽﾄ変換処理を行います(XSLX限定)｡
    1095         *
    1096         * この処理は､#saveFile( File ) の直前に行うのがよいでしょう｡
    1097         * #activeWorkbook( boolean ) との順番は構いません｡
    1098         *
    1099         * ･ｼｰﾄ名の一覧をﾋﾟｯｸｱｯﾌﾟします｡
    1100         * ･ｾﾙ値を､ｾﾙ単位内の改行単位にﾋﾟｯｸｱｯﾌﾟし､結果を合成ます｡
    1101         * ･ｵﾌﾞｼﾞｪｸﾄ文字列を､改行単位にﾋﾟｯｸｱｯﾌﾟし､結果を合成します｡
    1102         *
    1103         * ここでは､ｼｰﾄ名､ｾﾙﾃｷｽﾄ､SimpleShapeｵﾌﾞｼﾞｪｸﾄのﾃｷｽﾄを
    1104         * input に､TextConverterｲﾝﾀｰﾌｪｰｽ の change ﾒｿｯﾄﾞを呼び出します｡
    1105         * 戻り値が､null でないなら､元のﾃﾞｰﾀと置き換えます｡
    1106         * 戻り値が､null の場合は､そのまま読み飛ばします｡(なにもしません)
    1107         * EXCELへの書き戻しが発生しますので､万一､ﾌｧｲﾙ破損で､開けなくなる場合を
    1108         * 想定して､ﾊﾞｯｸｱｯﾌﾟﾌｧｲﾙは､各自で準備してください｡
    1109         *
    1110         * @og.rev 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    1111         * @og.rev 6.2.5.0 (2015/06/05) xsl形式のｵﾌﾞｼﾞｪｸﾄ取得…はできなかった｡
    1112         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに､引数(cmnt)を追加
    1113         * @og.rev 6.3.9.0 (2015/11/06) ｾﾙに値をｾｯﾄするときに､ｾﾙﾀｲﾌﾟを考慮する｡
    1114         *
    1115         * @param       conv    TextConverterｲﾝﾀｰﾌｪｰｽ
    1116         * @see         #textConverter( Map )
    1117         */
    1118        @SuppressWarnings(value={"deprecation"})        // poi-3.15
    1119        public void textConverter( final TextConverter<String,String> conv ) {
    1120        //      if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) {
    1121                        final int shCnt = wkbook.getNumberOfSheets();
    1122                        for( int shtNo=0; shtNo<shCnt; shtNo++ ) {
    1123                                final Sheet sht = wkbook.getSheetAt( shtNo );
    1124                                // ｼｰﾄ名の変換
    1125                                final String shtNm = conv.change( sht.getSheetName() , "Sheet" );
    1126                                if( shtNm != null ) {
    1127                                        setSheetName( shtNo,shtNm );                    // 同一ｼｰﾄ対策済みのﾒｿｯﾄﾞを呼び出す｡
    1128                                }
    1129
    1130                                // ｾﾙ値の変換
    1131                                final int stR = Math.max( sht.getFirstRowNum(),0 );             // stR が､ﾏｲﾅｽのｹｰｽがある｡
    1132                                final int edR = sht.getLastRowNum();
    1133
    1134                                for( int rowNo=stR; rowNo<=edR; rowNo++ ) {
    1135                                        final Row rowObj = sht.getRow( rowNo );
    1136                                        if( rowObj != null ) {
    1137                                                final int stC = Math.max( rowObj.getFirstCellNum(),0 );         // stC が､ﾏｲﾅｽのｹｰｽがある｡
    1138                                                final int edC = rowObj.getLastCellNum();
    1139                                                for( int colNo=stC; colNo<=edC; colNo++ ) {
    1140                                                        final Cell colObj = rowObj.getCell( colNo );
    1141//                                                      if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) {          // 6.5.0.0 (2016/09/30) poi-3.12
    1142//                                                      if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) {            // 6.5.0.0 (2016/09/30) poi-3.15
    1143                                                        if( colObj != null && colObj.getCellType() != CellType.BLANK ) {                        // 8.0.0.0 (2021/07/31) poi-4.1.2.jar → poi-5.0.0.jar
    1144                                                                final String cmnt= "Sht" + shtNo + ":" + POIUtil.getCelKigo( rowNo,colNo );
    1145                                                                final String val = crConv( conv, POIUtil.getValue( colObj ),cmnt );             // 改行対応
    1146                                                                if( val != null ) {
    1147                                                                        POIUtil.setValue( colObj,val );         // 6.3.9.0 (2015/11/06)
    1148                                                //                      colObj.setCellValue( val );
    1149                                                                }
    1150                                                        }
    1151                                                }
    1152                                        }
    1153                                }
    1154
    1155                                // ｵﾌﾞｼﾞｪｸﾄ文字列の変換
    1156                                if( sht instanceof POIXMLDocumentPart ) {
    1157                                        for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sht).getRelations() ) {
    1158                                                if( pxdp instanceof XSSFDrawing ) {
    1159                                                        for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) {
    1160                                                                final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor();
    1161                                                                final String ancSt = "XY(" + anc.getDx1() + "-" + anc.getDy1() + ")" ;
    1162                                                                int cnt = 0;
    1163                                                                if( shape instanceof XSSFSimpleShape ) {
    1164                                                                        for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) {
    1165                                                                                for( final XSSFTextRun text : para.getTextRuns() ) {
    1166                                                                                        final String cmnt= "Sht" + shtNo + ":" + ancSt + ":Tb(" + cnt++ + ")" ;
    1167                                                                                        final String val = crConv( conv,text.getText() , cmnt );
    1168                                                                                        if( val != null ) {
    1169                                                                                                text.setText( val );
    1170                                                                                        }
    1171                                                                                }
    1172                                                                        }
    1173                                                                }
    1174                                                        }
    1175                                                }
    1176                                        }
    1177                                }
    1178                        // 6.2.5.0 (2015/06/05) xsl形式のｵﾌﾞｼﾞｪｸﾄ取得…はできなかった｡
    1179                        //      else if( sht instanceof HSSFSheet ) {
    1180                        //              HSSFPatriarch patri = ((HSSFSheet)sht).getDrawingPatriarch();
    1181                        //              for( final HSSFShape shape : patri.getChildren() ) {
    1182                        //                      if( shape instanceof HSSFTextbox ) {
    1183                        //                              HSSFRichTextString rts = ((HSSFSimpleShape)shape).getString();
    1184                        //                              if( rts != null ) {
    1185                        //                                      final String val = crConv( conv,rts.getString() );
    1186                        //                                      if( val != null ) {
    1187                        //                                              HSSFRichTextString rts2 = new HSSFRichTextString( val );
    1188                        //                                              ((HSSFSimpleShape)shape).setString( rts2 );
    1189                        //                                      }
    1190                        //                              }
    1191                        //                      }
    1192                        //              }
    1193                        //      }
    1194                        }
    1195        //      }
    1196        }
    1197
    1198        /**
    1199         * 現在のｼｰﾄを選択済み(true)か､非選択済み(false)に設定します｡
    1200         *
    1201         * 通常は､ｼｰﾄは､先頭ｼｰﾄ以外は､非選択状態になっています｡
    1202         * ｼｰﾄを選択済みにすることで､印刷範囲を指定する事ができます｡
    1203         *
    1204         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
    1205         *
    1206         * @param       isSelect        true:ｼｰﾄ選択/false:非選択
    1207         */
    1208        public void sheetSelected( final boolean isSelect ) {
    1209                sheet.setSelected( isSelect );
    1210        }
    1211
    1212        /**
    1213         * Workbook の雛形ｼｰﾄのTextConverter した､新しいSheetを作成します｡
    1214         *
    1215         * 正確には､
    1216         *   １．雛形ｼｰﾄを､ｺﾋﾟｰして､新しいSheet(shtName)を､作成します｡
    1217         *   ２．雛形ｼｰﾄが指定されていない場合は､一番最後のｼｰﾄをｺﾋﾟｰします｡
    1218         *   ２．そのｼｰﾄに対して､TextConverter を行い､文字列変換します｡
    1219         *
    1220         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
    1221         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    1222         *
    1223         * @param       conv    TextConverterｲﾝﾀｰﾌｪｰｽ
    1224         * @param       shtName         ｼｰﾄ名
    1225         * @see         #textConverter( Map )
    1226         */
    1227        @SuppressWarnings(value={"deprecation"})        // poi-3.15
    1228        public void sheetCopy( final TextConverter<String,String> conv , final String shtName ) {
    1229                int shtNo = wkbook.getNumberOfSheets() - 1;
    1230                if( refSheetIdx >= 0 && refSheetIdx < shtNo ) {         // 雛形ｼｰﾄをｺﾋﾟｰする｡
    1231                        sheet = wkbook.cloneSheet( refSheetIdx );
    1232                }
    1233                else {
    1234                        sheet = wkbook.cloneSheet( shtNo );                             // 最後のｼｰﾄをｺﾋﾟｰします｡
    1235                }
    1236                shtNo++ ;                                                                                       // ｼｰﾄ番号を増やしておく｡
    1237
    1238                // ｼｰﾄ名の変換
    1239                setSheetName( shtNo,shtName );                                          // 同一ｼｰﾄ対策済みのﾒｿｯﾄﾞを呼び出す｡
    1240
    1241                // ｾﾙ値の変換
    1242                final int stR = Math.max( sheet.getFirstRowNum(),0 );           // stR が､ﾏｲﾅｽのｹｰｽがある｡
    1243                final int edR = sheet.getLastRowNum();
    1244
    1245                for( int rowNo=stR; rowNo<=edR; rowNo++ ) {
    1246                        final Row rowObj = sheet.getRow( rowNo );
    1247                        if( rowObj != null ) {
    1248                                final int stC = Math.max( rowObj.getFirstCellNum(),0 );         // stC が､ﾏｲﾅｽのｹｰｽがある｡
    1249                                final int edC = rowObj.getLastCellNum();
    1250                                for( int colNo=stC; colNo<=edC; colNo++ ) {
    1251                                        final Cell colObj = rowObj.getCell( colNo );
    1252//                                      if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) {          // 6.5.0.0 (2016/09/30) poi-3.12
    1253//                                      if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) {            // 6.5.0.0 (2016/09/30) poi-3.15
    1254                                        if( colObj != null && colObj.getCellType() != CellType.BLANK ) {                        // 8.0.0.0 (2021/07/31) poi-4.1.2.jar → poi-5.0.0.jar
    1255                                                final String val = crConv( conv, POIUtil.getValue( colObj ),null );             // 改行対応
    1256                                                if( val != null ) {
    1257                                                        POIUtil.setValue( colObj,val );
    1258                                //                      colObj.setCellValue( val );
    1259                                                }
    1260                                        }
    1261                                }
    1262                        }
    1263                }
    1264
    1265                // ｵﾌﾞｼﾞｪｸﾄ文字列の変換
    1266                if( sheet instanceof POIXMLDocumentPart ) {
    1267                        for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sheet).getRelations() ) {
    1268                                if( pxdp instanceof XSSFDrawing ) {
    1269                                        for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) {
    1270                                //              final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor();
    1271                                                if( shape instanceof XSSFSimpleShape ) {
    1272                                                        for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) {
    1273                                                                for( final XSSFTextRun text : para.getTextRuns() ) {
    1274                                                                        final String val = crConv( conv,text.getText() , null );
    1275                                                                        if( val != null ) {
    1276                                                                                text.setText( val );
    1277                                                                        }
    1278                                                                }
    1279                                                        }
    1280                                                }
    1281                                        }
    1282                                }
    1283                        }
    1284                }
    1285        }
    1286
    1287        /**
    1288         * Workbook の全Sheetを対象に､ﾃｷｽﾄ変換処理を行います(XSLX限定)｡
    1289         *
    1290         * この処理は､#saveFile( File ) の直前に行うのがよいでしょう｡
    1291         * #activeWorkbook( boolean ) との順番は構いません｡
    1292         *
    1293         * ･ｼｰﾄ名の一覧をﾋﾟｯｸｱｯﾌﾟします｡
    1294         * ･ｾﾙ値を､ｾﾙ単位内の改行単位にﾋﾟｯｸｱｯﾌﾟし､結果を合成ます｡
    1295         * ･ｵﾌﾞｼﾞｪｸﾄ文字列を､改行単位にﾋﾟｯｸｱｯﾌﾟし､結果を合成します｡
    1296         *
    1297         * ここでは､ｼｰﾄ名､ｾﾙﾃｷｽﾄ､SimpleShapeｵﾌﾞｼﾞｪｸﾄのﾃｷｽﾄを
    1298         * input に､TextConverterｲﾝﾀｰﾌｪｰｽ の change ﾒｿｯﾄﾞを呼び出します｡
    1299         * 戻り値が､null でないなら､元のﾃﾞｰﾀと置き換えます｡
    1300         * 戻り値が､null の場合は､そのまま読み飛ばします｡(なにもしません)
    1301         * EXCELへの書き戻しが発生しますので､万一､ﾌｧｲﾙ破損で､開けなくなる場合を
    1302         * 想定して､ﾊﾞｯｸｱｯﾌﾟﾌｧｲﾙは､各自で準備してください｡
    1303         *
    1304         * @og.rev 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    1305         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに､引数(cmnt)を追加
    1306         *
    1307         * @param       conv    TextConverterｲﾝﾀｰﾌｪｰｽ
    1308         * @param       val             改行処理を行う元の値
    1309         * @param       cmnt    ｺﾒﾝﾄ
    1310         * @return      改行処理の結果の値(対象が無ければ､null)
    1311         * @see         #textConverter( Map )
    1312         */
    1313        private String crConv( final TextConverter<String,String> conv , final String val , final String cmnt ) {
    1314                String rtn = null;
    1315                if( val != null ) {
    1316                        if( val.contains( "\n" ) ) {                                            // 改行がある場合(EXCEL のｾﾙ内改行ｺｰﾄﾞは､LF(0A)=\n のみ｡
    1317                                final String[] val2 = val.split( "\\n" );               // 改行で分割する｡
    1318                                boolean flag = false;
    1319                                for( int i=0; i<val2.length; i++ ) {
    1320                                        final String val3 = conv.change( val2[i],cmnt );        // 6.3.1.0 (2015/06/28)
    1321                                        if( val3 != null ) { val2[i] = val3; flag = true; }
    1322                                }
    1323                                if( flag ) {
    1324                                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    1325                                        buf.append( val2[0] );
    1326                                        for( int i=1; i<val2.length; i++ ) {
    1327                                                buf.append( '\n' ).append( val2[i] );           // LF(\n)で､ｾﾊﾟﾚｰﾄしているので､LF のみ追加する｡
    1328                                        }
    1329                                        rtn = buf.toString();
    1330                                }
    1331                        }
    1332                        else {                                                                                          // 改行がない場合
    1333                                rtn = conv.change( val,cmnt );                                  // 6.3.1.0 (2015/06/28)
    1334                        }
    1335                }
    1336                return rtn;
    1337        }
    1338
    1339        /**
    1340         * ｼｰﾄ一覧を､内部の Workbook から取得します｡
    1341         *
    1342         * 取得元が､Workbook なので､xls , xlsx どちらの形式でも取り出せます｡
    1343         *
    1344         * EXCEL上のｼｰﾄ名を､配列で返します｡
    1345         *
    1346         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
    1347         *
    1348         * @return      ｼｰﾄ名の配列
    1349         * @see         POIUtil#getSheetNames( Workbook )
    1350         */
    1351        public String[] getSheetNames() {
    1352                return POIUtil.getSheetNames( wkbook );
    1353        }
    1354
    1355        /**
    1356         * 名前定義一覧を内部の Workbook から取得します｡
    1357         *
    1358         * EXCEL上に定義された名前を､配列で返します｡
    1359         * ここでは､名前とFormulaをﾀﾌﾞで連結した文字列を配列で返します｡
    1360         * Name ｵﾌﾞｼﾞｪｸﾄを削除すると､EXCELが開かなくなったりするので､
    1361         * 取りあえず一覧を作成して､手動で削除してください｡
    1362         * なお､名前定義には､非表示というのがありますので､ご注意ください｡
    1363         *
    1364         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
    1365         *
    1366         * @return      名前定義(名前+TAB+Formula)の配列
    1367         * @see         POIUtil#getNames( Workbook )
    1368         * @og.rtnNotNull
    1369         */
    1370        public String[] getNames() {
    1371                return POIUtil.getNames( wkbook );
    1372        }
    1373
    1374        /**
    1375         * 書式のｽﾀｲﾙ一覧を内部の Workbook から取得します｡
    1376         *
    1377         * EXCEL上に定義された書式のｽﾀｲﾙを､配列で返します｡
    1378         * 書式のｽﾀｲﾙの名称は､CellStyle にﾒｿｯﾄﾞが定義されていません｡
    1379         * 実ｸﾗｽである HSSFCellStyle にｷｬｽﾄして使用する
    1380         * 必要があります｡(XSSFCellStyle にも名称を取得するﾒｿｯﾄﾞがありません｡)
    1381         *
    1382         * ※ EXCEL2010 ﾎｰﾑﾀﾌﾞ→ｾﾙのｽﾀｲﾙ は､一つづつしか削除できません｡
    1383         *    ﾏｸﾛは､開発ﾀﾌﾞ→Visual Basic で､挿入→標準ﾓｼﾞｭｰﾙ を開き
    1384         *    ﾃｷｽﾄを張り付けてください｡
    1385         *    実行は､開発ﾀﾌﾞ→ﾏｸﾛ で､ﾏｸﾛ名を選択して､実行します｡
    1386         *    最後は､削除してください｡
    1387         *
    1388         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
    1389         *
    1390         * @return      書式のｽﾀｲﾙ一覧
    1391         * @see         POIUtil#getStyleNames( Workbook )
    1392         * @og.rtnNotNull
    1393         */
    1394        public String[] getStyleNames() {
    1395                return POIUtil.getStyleNames( wkbook );
    1396        }
    1397
    1398        /**
    1399         * 文字列を Double ｵﾌﾞｼﾞｪｸﾄに変換します｡
    1400         *
    1401         * これは､引数の ｶﾝﾏ(,) を削除した文字列から､Double ｵﾌﾞｼﾞｪｸﾄを生成します｡
    1402         * 処理中に､文字列が解析可能な double を含まない場合(NumberFormatException)
    1403         * また､引数が､null,ｾﾞﾛ文字列,'_', ｴﾗｰ の時には､null を返します｡
    1404         *
    1405         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1406         * @og.rev 6.3.9.0 (2015/11/06) もう少し判りやすくする｡(処理速度は落ちてます｡)
    1407         *
    1408         * @param       value   Doubleに変換する元の文字列
    1409         *
    1410         * @return      変換後のDoubleｵﾌﾞｼﾞｪｸﾄ(ｴﾗｰ発生時や変換不可の場合は､null)
    1411         */
    1412        private Double parseDouble( final String value ) {
    1413                Double rtn = null ;
    1414
    1415                try {
    1416                        if( value == null || value.isEmpty() || value.equals( "_" ) ) {
    1417                                rtn = null;
    1418                        }
    1419                        else if( value.indexOf( ',' ) < 0 ) {
    1420                                rtn = Double.valueOf( value );          // 6.0.2.4 (2014/10/17) ﾒｿｯﾄﾞが非効率だった｡
    1421                        }
    1422                        else {
    1423                                // 6.3.9.0 (2015/11/06) もう少し判りやすくする｡(処理速度は落ちてます｡)
    1424                                rtn = Double.valueOf( value.replaceAll( ",","" ) );
    1425                        }
    1426                }
    1427                catch( final NumberFormatException ex ) {               // 文字列が解析可能な数値を含まない場合
    1428                        final String errMsg = "Double変換できませんでした｡" + CR
    1429                                                                + ex.getMessage() + CR
    1430                                                                + "  value=" + value;
    1431                        System.err.println( errMsg );
    1432                        rtn = null;
    1433                }
    1434
    1435                return rtn ;
    1436        }
    1437
    1438        /**
    1439         * ｱﾌﾟﾘｹｰｼｮﾝのｻﾝﾌﾟﾙです｡
    1440         *
    1441         * Usage: java org.opengion.fukurou.model.ExcelModel 入力ﾌｧｲﾙ名 [出力ﾌｧｲﾙ名] ･･･
    1442         *  通常は標準出力に行単位に､ｾﾙをﾀﾌﾞ区切り出力します｡
    1443         *  出力ﾌｧｲﾙ名 を指定すると､EXCEL ﾌｧｲﾙとしてｾｰﾌﾞし直します｡
    1444         *  その場合は､以下のﾊﾟﾗﾒｰﾀも使用できます｡
    1445         *   -CS      CellStyleを 設定します｡
    1446         *   -AS      useAutoCellSizeを 設定します｡
    1447         *   -FN=***  FontNameを 設定します｡
    1448         *   -FP=**   FontPointを 設定します｡
    1449         *   -IMG     画像ﾌｧｲﾙを挿入します｡(-IMG 画像ﾌｧｲﾙ名 ｼｰﾄ番号 行 列)をｽﾍﾟｰｽ区切りで続けます｡
    1450         *
    1451         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1452         *
    1453         * @param       args    ｺﾏﾝﾄﾞ引数配列
    1454         */
    1455        public static void main( final String[] args ) {
    1456                if( args.length == 0 ) {
    1457                        final String usage = "Usage: java org.opengion.fukurou.model.ExcelModel 入力ﾌｧｲﾙ名 [出力ﾌｧｲﾙ名] ･･･\n" +
    1458                                                "\t-CS      CellStyleを 設定します｡        \n" +
    1459                                                "\t-TC      TextConverterを実行します｡     \n" +
    1460                                                "\t-AS      useAutoCellSizeを 設定します｡  \n" +
    1461                                                "\t-FN=***  FontNameを 設定します｡         \n" +
    1462                                                "\t-FP=**   FontPointを 設定します｡        \n" +
    1463                                                "\t-IMG     画像ﾌｧｲﾙを挿入します｡      \n" +
    1464                                                "\t     (-IMG ﾌｧｲﾙ名 ｼｰﾄ番号 行 列)  \n" ;
    1465                        System.err.println( usage );
    1466                        return ;
    1467                }
    1468
    1469                final ExcelModel excel = new ExcelModel( new File( args[0] ) , true );
    1470
    1471                excel.activeWorkbook( true );                   // 余計な行を削除します｡
    1472
    1473                if( args.length > 1 ) {
    1474                        final File outFile = new File( args[1] );                       // 6.2.0.0 (2015/02/27)
    1475                        boolean isCS = false;
    1476                        boolean isAS = false;
    1477                        boolean isTC = false;                           // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    1478                        String  fn   = null;
    1479                        short   fp   = -1;
    1480
    1481                        for( int i=2; i<args.length; i++ ) {
    1482                                final String prm = args[i];
    1483
    1484                                if( "-CS".equalsIgnoreCase( prm ) ) { isCS = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
    1485                                if( "-AS".equalsIgnoreCase( prm ) ) { isAS = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
    1486                                if( "-TC".equalsIgnoreCase( prm ) ) { isTC = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
    1487                                if( prm.startsWith( "-FN" ) ) { fn   = prm.substring( 3 ); }
    1488                                if( prm.startsWith( "-FP" ) ) { fp   = Short.parseShort( prm.substring( 3 ) ); }
    1489                                if( "-IMG".equalsIgnoreCase( prm ) ) {                                  // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
    1490                                        final String img = args[++i];
    1491                                        final int  shtNo = Integer.parseInt( args[++i] );
    1492                                        final int  rowNo = Integer.parseInt( args[++i] );
    1493                                        final int  colNo = Integer.parseInt( args[++i] );
    1494
    1495                                        excel.addImageFile( img,shtNo,rowNo,colNo );
    1496                                }
    1497                        }
    1498
    1499                        if( isCS ) { excel.setCellStyle(); }
    1500                        excel.useAutoCellSize( isAS );
    1501                        excel.setFont( fn,fp );
    1502
    1503                        // 6.2.4.2 (2015/05/29) ﾃｷｽﾄ変換処理
    1504                        if( isTC ) {
    1505                                // 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    1506                                // 処理が複数行に別れるのは判りにくいので良くない｡
    1507                                excel.textConverter(
    1508                                        ( val,cmnt ) -> {
    1509                                                System.out.println( val );                      // すべてのﾃｷｽﾄを読み取る｡
    1510                                                return null;                                            // 変換せず｡
    1511                                        }
    1512                                );
    1513                        }
    1514
    1515                        excel.saveFile( outFile );
    1516                }
    1517                else {
    1518                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    1519
    1520                        final int shLen = excel.getNumberOfSheets();
    1521                        for( int shtNo=0; shtNo<shLen; shtNo++ ) {
    1522                                final String shtName = excel.getSheetName( shtNo );
    1523
    1524                                final int stRow = excel.getFirstRowNum();
    1525                                final int edRow = excel.getLastRowNum();
    1526                                for( int rowNo=stRow; rowNo<=edRow; rowNo++ ) {
    1527                                        buf.setLength(0);               // Clearの事
    1528                                        buf.append( shtName ).append( '\t' ).append( rowNo );
    1529                                        final String[] vals = excel.getValues( rowNo );
    1530                                        if( vals != null ) {
    1531                                                for( int colNo=0; colNo<vals.length; colNo++ ) {
    1532                                                        final String val = vals[colNo] == null ? "" : vals[colNo];
    1533                                                        buf.append( '\t' ).append( val );
    1534                                                }
    1535                                        }
    1536                                        System.out.println( buf );
    1537                                }
    1538                                System.out.println();
    1539                        }
    1540                }
    1541        }
    1542}