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