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