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