    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.InputStream;
    019import java.io.FileInputStream;
    020import java.io.BufferedInputStream;
    021import java.io.IOException;
    022import java.io.OutputStream;
    023import java.io.FileOutputStream;
    024import java.io.BufferedOutputStream;
    025// import java.text.DecimalFormat;
    026// import java.text.NumberFormat;
    027import java.util.Locale;
    028
    029import org.apache.poi.ss.usermodel.WorkbookFactory;
    030import org.apache.poi.ss.usermodel.Workbook;
    031import org.apache.poi.ss.usermodel.Sheet;
    032import org.apache.poi.ss.usermodel.Row;
    033import org.apache.poi.ss.usermodel.Cell;
    034import org.apache.poi.ss.usermodel.CellStyle;
    035import org.apache.poi.ss.usermodel.IndexedColors;
    036
    037import org.apache.poi.ss.usermodel.CreationHelper;
    038import org.apache.poi.ss.usermodel.Font;
    039
    040import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
    041// import org.apache.poi.ss.usermodel.DateUtil;
    042import org.apache.poi.ss.usermodel.RichTextString;
    043// import org.apache.poi.ss.usermodel.FormulaEvaluator;
    044
    045import org.opengion.fukurou.util.POIUtil;
    046import org.opengion.fukurou.util.Closer;
    047// import org.opengion.fukurou.util.HybsDateUtil;
    048
    049/**
    050 * POI による、EXCELバイナリファイルに対する、データモデルクラスです。
    051 *
    052 * 共通的な EXCEL処理 を集約しています。
    053 * staticメソッドによる簡易的なアクセスの他に、順次処理も可能なように
    054 * 現在アクセス中の、Workbook、Sheet、Row、Cell オブジェクトを内部で管理しています。
    055 *
    056 * 入力形式は、openXML形式にも対応しています。
    057 * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に
    058 * 自動判定されます。
    059 *
    060 * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    061 * @og.group その他
    062 *
    063 * @version  6.0
    064 * @author   Kazuhiko Hasegawa
    065 * @since    JDK7.0,
    066 */
    067public class ExcelModel {
    068        //* このプログラムのVERSION文字列を設定します。   {@value} */
    069        private static final String VERSION = "6.0.2.0 (2014/08/29)" ;
    070
    071        private static final String CR = System.getProperty("line.separator");
    072
    073        private String   inFilename = null;             // エラー発生時のキーとなる、EXCELファイル名
    074
    075        private Workbook wkbook = null;                 // 現在処理中の Workbook
    076        private Sheet    sheet  = null;                 // 現在処理中の Sheet
    077        private Row              rowObj = null;                 // 現在処理中の Row
    078
    079        private CreationHelper createHelper     = null;         // poi.xssf対応
    080
    081        private CellStyle style = null;                                 // Workbook のセルスタイル
    082        private int maxColCount = 5 ;                                   // 標準セル幅の５倍を最大幅とする。
    083        private boolean useAutoSizeColumn = false;              // カラム幅の自動調整を行うかどうか(true:行う)
    084
    085        /**
    086         * EXCELファイルのWookbookのデータ処理モデルを作成します。
    087         *
    088         * ここでは、既存のファイルを読み込んで、データ処理モデルを作成しますので、
    089         * ファイルがオープンできなければエラーになります。
    090         *
    091         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    092         *
    093         * @param   fname  EXCELファイル名
    094         * @see         #ExcelModel( String , boolean )
    095         */
    096        public ExcelModel( final String fname ) {
    097                this( fname,true );
    098        }
    099
    100        /**
    101         * EXCELファイルのWookbookのデータ処理モデルを作成します。
    102         *
    103         * isOpen条件によって、ファイルオープン(true)か、新規作成(false)が分かれます。
    104         * ファイルオープンの場合は、EXCELの読み込み以外に、追記するとか、雛形参照する
    105         * 場合にも、使用します。
    106         * ファイルオープンの場合は、当然、ファイルがオープンできなければエラーになります。
    107         *
    108         * isOpen=新規作成(false) の場合は、ファイル名の拡張子で、XSSFWorkbook か HSSFWorkbook を
    109         * 判定します。.xlsx の場合⇒XSSFWorkbook オブジェクトを使用します。
    110         *
    111         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    112         *
    113         * @param   fname  EXCELファイル名
    114         * @param   isOpen true:ファイルオープン/false:新規作成
    115         * @see         #ExcelModel( String )
    116         */
    117        public ExcelModel( final String fname , final boolean isOpen ) {
    118                inFilename = fname;
    119                if( isOpen ) {
    120                        // File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。
    121                        InputStream fileIn = null;
    122                        try {
    123                                fileIn = new BufferedInputStream( new FileInputStream( fname ) );
    124                                wkbook = WorkbookFactory.create( fileIn );
    125                        }
    126                        catch( IOException ex ) {
    127                                String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
    128                                throw new RuntimeException( errMsg,ex );
    129                        }
    130                        catch( InvalidFormatException ex ) {
    131                                String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ;
    132                                throw new RuntimeException( errMsg,ex );
    133                        }
    134                        finally {
    135                                Closer.ioClose( fileIn );
    136                        }
    137                }
    138                else {
    139                        // 新規の場合、ファイル名に.xlsxで終了した場合⇒.xlsx形式ファイル作成、その他⇒.xls形式ファイル作成
    140                        if( fname.toLowerCase(Locale.JAPAN).endsWith( ".xlsx" ) ) {
    141                                wkbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook();
    142                        }
    143                        else {
    144                                wkbook = new org.apache.poi.hssf.usermodel.HSSFWorkbook();
    145                        }
    146                }
    147
    148                createHelper = wkbook.getCreationHelper();              // poi.xssf対応
    149        }
    150
    151        /**
    152         * 内部 Workbook に、フォント名、フォントサイズを設定します。
    153         * fontName(フォント名)は、"ＭＳ Ｐゴシック" など名称になります。
    154         * fontPoint は、フォントの大きさを指定します。
    155         * 内部的には、setFontHeightInPoints(short)メソッドで設定します。
    156         *
    157         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
    158         *
    159         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    160         *
    161         * @param       fontName        フォント名("ＭＳ Ｐゴシック" など。nullの場合、セットしません)
    162         * @param       fontPoint       フォントの大きさ(0や、マイナスの場合は、セットしません)
    163         */
    164        public void setFont( final String fontName , final short fontPoint ) {
    165                Font font = wkbook.getFontAt((short)0);
    166                if( fontName != null ) {
    167                        font.setFontName( fontName );   // "ＭＳ Ｐゴシック" など
    168                }
    169                if( fontPoint > 0 ) {
    170                        font.setFontHeightInPoints( fontPoint );
    171                }
    172
    173//              createHelper.applyFont( font );         // Cellの雛形Fontを優先したいので、設定しません。
    174        }
    175
    176        /**
    177         * データ設定する セルに、罫線を追加します。
    178         *
    179         * ここで設定するのは、罫線の種類と、罫線の色ですが、内部的に固定にしています。
    180         *   Border=CellStyle.BORDER_THIN
    181         *   BorderColor=IndexedColors.BLACK
    182         *
    183         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    184         *
    185         */
    186        public void setCellStyle() {
    187                style = wkbook.createCellStyle();
    188
    189                style.setBorderBottom(  CellStyle.BORDER_THIN );
    190                style.setBorderLeft(    CellStyle.BORDER_THIN );
    191                style.setBorderRight(   CellStyle.BORDER_THIN );
    192                style.setBorderTop(             CellStyle.BORDER_THIN );
    193
    194                style.setBottomBorderColor(     IndexedColors.BLACK.getIndex() );
    195                style.setLeftBorderColor(       IndexedColors.BLACK.getIndex() );
    196                style.setRightBorderColor(      IndexedColors.BLACK.getIndex() );
    197                style.setTopBorderColor(        IndexedColors.BLACK.getIndex() );
    198        }
    199
    200        /**
    201         * Sheetに対して、autoSizeColumn設定を行うかどうか指定します。
    202         *
    203         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
    204         * 初期カラム幅の５倍を限度にしています。
    205         *
    206         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
    207         * 中で実行されます。(セーブしなければ実行されません。)
    208         *
    209         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    210         *
    211         * @param flag  true:自動カラム幅設定を行う / false:行わない。
    212         * @see #useAutoSizeColumn( boolean,int )
    213         */
    214        public void useAutoSizeColumn( final boolean flag ) {
    215                this.useAutoSizeColumn( flag , maxColCount );
    216        }
    217
    218        /**
    219         * Sheetに対して、autoSizeColumn設定を行うかどうか指定します。
    220         *
    221         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
    222         * 初期カラム幅のcount倍を限度に設定します。
    223         * ただし、count がマイナスの場合は、無制限になります。
    224         *
    225         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
    226         * 中で実行されます。(セーブしなければ実行されません。)
    227         *
    228         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    229         *
    230         * @param flag  true:自動カラム幅設定を行う / false:行わない。
    231         * @param count 最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限
    232         * @see #useAutoSizeColumn( boolean )
    233         */
    234        public void useAutoSizeColumn( final boolean flag, final int count ) {
    235                useAutoSizeColumn = flag;
    236                maxColCount = count ;
    237        }
    238
    239        /**
    240         * 内部 Workbookの Sheet数を返します。
    241         *
    242         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    243         *
    244         * @return      シート数
    245         */
    246        public int getNumberOfSheets() {
    247                return wkbook.getNumberOfSheets();
    248        }
    249
    250        /**
    251         * 内部 Workbookの 現在Sheet の最初の行番号を返します。
    252         *
    253         * 行は、0 から始まります。
    254         *
    255         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    256         *
    257         * @return      最初の行番号
    258         */
    259        public int getFirstRowNum() {
    260                return sheet.getFirstRowNum();
    261        }
    262
    263        /**
    264         * 内部 Workbookの 現在Sheet の最後の行番号を返します。
    265         *
    266         * 最終行は、含みます。よって、行数は、getLastRowNum()＋１になります。
    267         *
    268         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    269         *
    270         * @return      最後の行番号
    271         */
    272        public int getLastRowNum() {
    273                return sheet.getLastRowNum();
    274        }
    275
    276        /**
    277         * 内部 Workbookより、Sheetを作ります。
    278         * Sheetは、useRefName属性に応じて、参照コピーか、新規作成に分かれます。
    279         * useRefName=true:参照シート使用 の場合は、refSheetName を cloneして、Sheetを作ります。
    280         * refSheetName が null の場合は、第一番目のシートを clone します。
    281         * useRefName=false:新規作成 の場合は、新規作成されます。
    282         *
    283         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
    284         *
    285         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    286         *
    287         * @param       refSheetName    参照シート名(nullの場合、参照シート使用する場合は、先頭のシートをコピー)
    288         * @param       useRefName              true:参照シート使用/false:新規作成
    289         */
    290        public void createSheet( final String refSheetName , final boolean useRefName ) {
    291                // 参照シートを使う場合
    292                if( useRefName ) {
    293                        // 参照シート名の指定がない場合は、最初のシート
    294                        int refSheetIdx = ( refSheetName == null ) ? 0 : wkbook.getSheetIndex( refSheetName );
    295
    296                        if( refSheetIdx < 0 ) {         // 参照シート名が存在しなかった。
    297                                String errMsg = "指定の参照シート名は存在しませんでした。" + CR
    298                                                                + " inFilename=[" + inFilename + "] , refSheetName=[" + refSheetName + "]"  + CR ;
    299                                throw new IllegalArgumentException( errMsg );
    300                        }
    301
    302                        sheet = wkbook.cloneSheet( refSheetIdx );
    303                }
    304                else {
    305                        sheet = wkbook.createSheet();
    306                }
    307        }
    308
    309        /**
    310         * 内部 Workbook の指定のシート番号の Sheet の名前を設定します。
    311         *
    312         * 既存のシート番号に Sheet に対して名前をセットします。
    313         * セットしたSheetは、内部の Sheet として設定されます。
    314         * シート名の重複を避けるため、すでに、同じ名前のシートが存在する場合は、
    315         * そのシート名の後に(2)、(3)のような文字列を追加します。
    316         *
    317         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    318         *
    319         * @param       stNo            シート番号
    320         * @param       sheetName       シート名(重複する場合は、(2)、(3)のような文字列を追加)
    321         */
    322        public void setSheetName( final int stNo , final String sheetName ) {
    323                String tempName = sheetName;
    324                int cnt = 1;
    325
    326                while( wkbook.getSheetIndex( tempName ) >= 0 ) {        // シート名が存在している場合
    327                        tempName = sheetName + "(" + cnt + ")";
    328                        cnt++;
    329                }
    330                wkbook.setSheetName( stNo, tempName );
    331                sheet = wkbook.getSheetAt( stNo );
    332        }
    333
    334        /**
    335         * 内部 Workbook のいちばん最後の Sheet の名前を設定します。
    336         *
    337         * 判りにくいですが、Sheet を新規作成するか、参照コピーして作成した最後の Sheet に対して
    338         * 名前をセットします。
    339         * シート名の重複を避けるため、すでに、同じ名前のシートが存在する場合は、
    340         * そのシート名の後に(2)、(3)のような文字列を追加します。
    341         *
    342         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
    343         *
    344         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    345         *
    346         * @param       sheetName       シート名(重複する場合は、(2)、(3)のような文字列を追加)
    347         */
    348        public void setLastSheetName( final String sheetName ) {
    349                int lastNo = wkbook.getNumberOfSheets() -1 ;            // 最後のシート番号は、シート数-1
    350                setSheetName( lastNo,sheetName );
    351        }
    352
    353        /**
    354         * 内部 Workbook の 指定のSheet番号のシート名前を返します。
    355         *
    356         * シートが存在しない場合は、null を返します。
    357         * この処理で、内部にsheetオブジェクトをキャッシュします。
    358         *
    359         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    360         *
    361         * @param        shNo           シート番号
    362         *
    363         * @return      sheetName       シート名
    364         */
    365        public String getSheetName( final int shNo ) {
    366                int shLen = wkbook.getNumberOfSheets();
    367
    368                String shName = null;
    369                if( shNo < shLen ) {
    370                        sheet = wkbook.getSheetAt( shNo );              // 現在の sheet に設定する。
    371                        shName = sheet.getSheetName();
    372                }
    373
    374                return shName ;
    375        }
    376
    377        /**
    378         * 内部 Workbook の 指定のSheet名のシート番号を返します。
    379         *
    380         * シートが存在しない場合は、-1 を返します。
    381         * この処理で、内部にsheetオブジェクトをキャッシュします。
    382         *
    383         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    384         *
    385         * @param        shName         シート名
    386         *
    387         * @return      シート番号(名前のシートがなめれば、-1)
    388         */
    389        public int getSheetNo( final String shName ) {
    390                sheet = wkbook.getSheet( shName );                              // シート名がマッチしなければ、null
    391
    392                return wkbook.getSheetIndex( shName ) ;                 // シート名がマッチしなければ、-1
    393        }
    394
    395        /**
    396         * Excelの指定Sheetオブジェクトを削除します。
    397         *
    398         * 削除するシートは、シート番号でFrom-To形式で指定します。
    399         * Fromも Toも、削除するシート番号を含みます。
    400         * 例えば、0,3 と指定すると、0,1,2,3 の ４シート分を削除します。
    401         *
    402         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    403         *
    404         * @param        fromNo         削除する開始シート番号(含む)
    405         * @param        toNo           削除する終了シート番号(含む)
    406         */
    407        public void removeSheet( final int fromNo,final int toNo ) {
    408                for( int shtNo=toNo; shtNo>=fromNo; shtNo-- ) {                 // 逆順に処理します。
    409                        wkbook.removeSheetAt( shtNo );
    410                }
    411        }
    412
    413        /**
    414         * Excelの指定行のRowオブジェクトを作成します。
    415         *
    416         * 指定行の Row オブジェクトが存在しない場合は、新規作成します。
    417         * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。
    418         *
    419         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    420         *
    421         * @param        rowNo          行の番号
    422         */
    423        public void createRow( final int rowNo ) {
    424                rowObj = sheet.getRow( rowNo );
    425                if( rowObj == null ) { rowObj = sheet.createRow( rowNo ); }
    426        }
    427
    428        /**
    429         * Excelの指定行以降の余計なRowオブジェクトを削除します。
    430         *
    431         * 指定行の Row オブジェクトから、getLastRowNum() までの行を、削除します。
    432         *
    433         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    434         *
    435         * @param        startRowNum            指定以降の余計な行を削除
    436         */
    437        public void removeRow( final int startRowNum ) {
    438                int stR = startRowNum;
    439                int edR = sheet.getLastRowNum();
    440
    441                for( int rowNo=edR; rowNo>=stR; rowNo-- ) {                     // 逆順に処理します。
    442                        Row rowObj = sheet.getRow( rowNo );
    443                        if( rowObj != null ) { sheet.removeRow( rowObj ); }
    444                }
    445        }
    446
    447        /**
    448         * Excelの処理中のRowオブジェクトの指定カラム以降の余計なCellオブジェクトを削除します。
    449         *
    450         * 指定行の Row オブジェクトから、getLastCellNum() までのカラムを、削除します。
    451         *
    452         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    453         *
    454         * @param        startCellNum           指定以降の余計なカラムを削除
    455         */
    456        public void removeCell( final int startCellNum ) {
    457                int stC = startCellNum;
    458                int edC = rowObj.getLastCellNum();
    459
    460                for( int colNo=edC; colNo>=stC; colNo-- ) {                     // 逆順に処理します。
    461                        Cell colObj = rowObj.getCell( colNo );
    462                        if( colObj != null ) { rowObj.removeCell( colObj ); }
    463                }
    464        }
    465
    466        /**
    467         * row にあるセルのオブジェクト値を設定します。
    468         *
    469         * 行が存在しない場合、行を追加します。
    470         *
    471         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    472         *
    473         * @param   vals  新しい配列値。
    474         * @param   rowNo   値が変更される行(無視されます)
    475         */
    476        public void setValues( final String[] vals,final int rowNo ) {
    477                if( rowObj == null ) { createRow( rowNo ); }
    478
    479                if( vals != null ) {
    480                        for( int colNo=0; colNo<vals.length; colNo++ ) {
    481                                setCellValue( vals[colNo],colNo );
    482                        }
    483                }
    484        }
    485
    486        /**
    487         * row にあるセルのオブジェクト値を設定します。
    488         *
    489         * 行が存在しない場合、行を追加します。
    490         * 引数に、カラムがNUMBER型かどうかを指定することが出来ます。
    491         *
    492         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    493         *
    494         * @param   vals  新しい配列値。
    495         * @param   rowNo   値が変更される行(無視されます)
    496         * @param       isNums  セルが、NUMBER型の場合は、true/それ以外は、false
    497         */
    498        public void setValues( final String[] vals,final int rowNo,final boolean[] isNums ) {
    499                if( rowObj == null ) { createRow( rowNo ); }
    500
    501                if( vals != null ) {
    502                        for( int colNo=0; colNo<vals.length; colNo++ ) {
    503                                setCellValue( vals[colNo],colNo,isNums[colNo] );
    504                        }
    505                }
    506        }
    507
    508        /**
    509         * Excelの指定セルにデータを設定します。
    510         *
    511         * ここで設定する行は、現在の内部 Row です。
    512         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
    513         * このメソッドでは、データを文字列型として設定します。
    514         *
    515         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    516         *
    517         * @param       dataVal    String文字列
    518         * @param       colNo           セルの番号(0,1,2････)
    519         * @see         #setCellValue( String,int,boolean )
    520         */
    521        public void setCellValue( final String dataVal , final int colNo ) {
    522                this.setCellValue( dataVal,colNo,false );
    523        }
    524
    525        /**
    526         * Excelの指定セルにデータを設定します。
    527         *
    528         * ここで設定する行は、現在の内部 Row です。
    529         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
    530         * このメソッドでは、引数のデータ型をNUMBER型の場合は、doubleに変換して、
    531         * それ以外は文字列としてとして設定します。
    532         *
    533         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    534         *
    535         * @param       dataVal         String文字列
    536         * @param       colNo           セルの番号(0,1,2････)
    537         * @param       isNumber        セルが、NUMBER型の場合は、true/それ以外は、false
    538         * @see         #createRow( int )
    539         * @see         #setCellValue( String,int )
    540         */
    541        public void setCellValue( final String dataVal , final int colNo , final boolean isNumber ) {
    542                Cell colObj = rowObj.getCell( colNo );
    543                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
    544
    545                // CELL_TYPE_NUMERIC 以外は、String扱いします。
    546//              if( colObj.getCellType() == Cell.CELL_TYPE_NUMERIC ) {
    547                if( isNumber ) {
    548                        Double dbl = parseDouble( dataVal );
    549                        if( dbl != null ) {
    550                                colObj.setCellValue( dbl.doubleValue() );
    551                                return ;                // Double 変換できた場合は、即抜けます。
    552                        }
    553                }
    554
    555                RichTextString richText = createHelper.createRichTextString( dataVal );
    556                colObj.setCellValue( richText );
    557
    558                if( style != null ) { colObj.setCellStyle(style); }
    559        }
    560
    561        /**
    562         * 現在のRow にあるセルの属性値を配列で返します。
    563         *
    564         * Rowオブジェクトが存在しない場合は、null を返します。
    565         * また、Rowオブジェクトの中の セルオブジェクトが存在しない場合は、
    566         * null がセットされます。
    567         *
    568         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    569         *
    570         * @param       rowNo           行の番号
    571         * @return      指定されたセルの属性値。Rowがnullの場合は、nullを返します。
    572         */
    573        public String[] getValues( final int rowNo ) {
    574                rowObj = sheet.getRow( rowNo );
    575                if( rowObj == null ) { return null; }
    576
    577                int len = rowObj.getLastCellNum() + 1;
    578                String[] vals = new String[len];
    579
    580                for( int colNo=0; colNo<len; colNo++ ) {
    581                        Cell colObj = rowObj.getCell( colNo );
    582                        vals[colNo] = POIUtil.getValue( colObj );
    583                }
    584
    585                return vals ;
    586        }
    587
    588        /**
    589         * 現在のrow にあるセルの属性値を返します。
    590         *
    591         * セルオブジェクトが存在しない場合は、null を返します。
    592         *
    593         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    594         *
    595         * @param   rowNo     値が参照される行
    596         * @param   colNo     値が参照される列
    597         *
    598         * @return  指定されたセルの値 T
    599         */
    600        public String getValue( final int rowNo, final int colNo ) {
    601                rowObj = sheet.getRow( rowNo );
    602                if( rowObj == null ) { return null; }
    603
    604                Cell colObj = rowObj.getCell( colNo );
    605                return POIUtil.getValue( colObj );
    606        }
    607
    608        /**
    609         * 内部 Workbook オブジェクトをファイルに書き出します。
    610         *
    611         * 書き出すファイルの拡張子に応じて、自動的に、Excel 2007以降の形式(.xlsx)か、
    612         * Excel 2003以前の形式(.xls) で出力されます。
    613         *
    614         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    615         *
    616         * @param       filename        セーブするファイル名
    617         */
    618        public void saveFile( final String filename ) {
    619                if( useAutoSizeColumn ) {
    620                        int shCnt = wkbook.getNumberOfSheets();
    621                        for( int shNo=0; shNo<shCnt; shNo++ ) {
    622                                Sheet sht = wkbook.getSheetAt( shNo );
    623                                int defW = sht.getDefaultColumnWidth();         // 標準カラムの文字数
    624                                int maxWidth = defW*256*maxColCount ;           // Widthは、文字数(文字幅)*256*最大セル数
    625
    626                                int stR = sht.getFirstRowNum();
    627                                int edR = sht.getLastRowNum();
    628
    629                                Row rowObj = sht.getRow( stR );
    630                                int stC = rowObj.getFirstCellNum();
    631                                int edC = rowObj.getLastCellNum();
    632                                for( int colNo=stC; colNo<=edC; colNo++ ) {
    633                                        sht.autoSizeColumn( colNo );
    634                                        if( maxWidth >= 0 ) {                                   // マイナスや０の場合は、幅に制限をかけない。
    635                                                int wd = sht.getColumnWidth( colNo );
    636                                                if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); }
    637                                        }
    638                                }
    639                        }
    640                }
    641
    642                OutputStream fileOut = null ;
    643                try {
    644                        fileOut = new BufferedOutputStream( new FileOutputStream( filename ) );
    645                        wkbook.write( fileOut );
    646                }
    647                catch( IOException ex ) {
    648                        String errMsg = "ファイルへ書込み中にエラーが発生しました。" + CR
    649                                                + "  File=" + filename + CR
    650                                                + ex.getMessage() ;
    651                        throw new RuntimeException( errMsg,ex );
    652                }
    653                finally {
    654                        Closer.ioClose( fileOut );
    655                }
    656        }
    657
    658        /**
    659         * Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。
    660         *
    661         * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。
    662         * 途中の行を削除しても、データとしては残っており、LastRowNum との矛盾の為、
    663         * 最終行が表示されなくなってしまいます。
    664         * よって、途中の削除は行いません。（方法が見つかれば別）
    665         *
    666         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    667         */
    668        public void activeWorkbook() {
    669                int shCnt = wkbook.getNumberOfSheets();
    670                for( int shNo=0; shNo<shCnt; shNo++ ) {
    671                        Sheet sheet = wkbook.getSheetAt( shNo );
    672
    673                        int stR = sheet.getFirstRowNum();
    674                        int edR = sheet.getLastRowNum();
    675
    676                        boolean isRowDel = true;                                                        // 行の削除は、Cellが見つかるまで。
    677                        for( int rowNo=edR; rowNo>=stR; rowNo-- ) {                     // 逆順に処理します。
    678                                Row rowObj = sheet.getRow( rowNo );
    679                                if( rowObj != null ) {
    680                                        int stC = rowObj.getFirstCellNum();
    681                                        int edC = rowObj.getLastCellNum();
    682                                        for( int colNo=edC; colNo>=stC; colNo-- ) {
    683                                                Cell colObj = rowObj.getCell( colNo );
    684                                                if( colObj != null ) {
    685                                                        String val = POIUtil.getValue( colObj );
    686                                                        if( colObj.getCellType() != Cell.CELL_TYPE_BLANK && val != null && val.length() > 0 ) {
    687                                                                isRowDel = false;                                               // 一つでも現れれば、行の削除は中止
    688                                                                break;
    689                                                        }
    690                                                        else {
    691                                                                rowObj.removeCell( colObj );                    // CELL_TYPE_BLANK の場合は、削除
    692                                                        }
    693                                                }
    694                                        }
    695                                        if( isRowDel ) { sheet.removeRow( rowObj );     }
    696        //                              else               { break; }                                                   // Cell の処理を継続しない場合は、break すればよい。
    697                                }
    698                        }
    699                }
    700        }
    701
    702        /**
    703         * 文字列を Double オブジェクトに変換します。
    704         *
    705         * これは、引数の カンマ(,) を削除した文字列から、Double オブジェクトを生成します。
    706         * 処理中に、文字列が解析可能な double を含まない場合(NumberFormatException)
    707         * また、引数が、null,ゼロ文字列,'_', エラー の時には、null を返します。
    708         *
    709         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    710         *
    711         * @param       value   Doubleに変換する元の文字列
    712         *
    713         * @return      変換後のDoubleオブジェクト(エラー発生時や変換不可の場合は、null)
    714         */
    715        private Double parseDouble( final String value ) {
    716                Double rtn = null ;
    717
    718                try {
    719                        if( value == null || value.length() == 0 || value.equals( "_" ) ) {
    720                                rtn = null;
    721                        }
    722                        else if( value.indexOf( ',' ) < 0 ) {
    723                                rtn = new Double( value );
    724                        }
    725                        else {
    726                                char[] chs = value.toCharArray() ;
    727                                int j=0;
    728                                for( int i=0;i<chs.length; i++ ) {
    729                                        if( chs[i] == ',' ) { continue; }
    730                                        chs[j] = chs[i];
    731                                        j++;
    732                                }
    733                                rtn = new Double( String.valueOf( chs,0,j ) );
    734                        }
    735                }
    736                catch( NumberFormatException ex ) {             // 文字列が解析可能な数値を含まない場合
    737                        String errMsg = "Double変換できませんでした。" + CR
    738                                                + ex.getMessage() + CR
    739                                                + "  value=" + value;
    740                        System.err.println( errMsg );
    741                        rtn = null;
    742                }
    743
    744                return rtn ;
    745        }
    746
    747        /**
    748         * アプリケーションのサンプルです。
    749         *
    750         * Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名
    751         *
    752         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    753         *
    754         * @param       args    コマンド引数配列
    755         */
    756        public static void main( final String[] args ) {
    757                if( args.length == 0 ) {
    758                        System.err.println( "Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名" );
    759                        return ;
    760                }
    761
    762                ExcelModel model = new ExcelModel( args[0] , true );
    763
    764                model.activeWorkbook();                 // 余計な行を削除します。
    765
    766                StringBuilder buf = new StringBuilder( 200 );
    767
    768                int shLen = model.getNumberOfSheets();
    769                for( int shNo=0; shNo<shLen; shNo++ ) {
    770                        String sheetName = model.getSheetName( shNo );
    771
    772                        int stRow = model.getFirstRowNum();
    773                        int edRow = model.getLastRowNum();
    774                        for( int rowNo=stRow; rowNo<=edRow; rowNo++ ) {
    775                                buf.setLength(0);               // Clearの事
    776                                buf.append( sheetName ).append( '\t' ).append( rowNo );
    777                                String[] vals = model.getValues( rowNo );
    778                                if( vals != null ) {
    779                                        for( int colNo=0; colNo<vals.length; colNo++ ) {
    780                                                String val = vals[colNo] == null ? "" : vals[colNo];
    781                                                buf.append( '\t' ).append( val );
    782                                        }
    783                                }
    784                                System.out.println( buf );
    785                        }
    786                        System.out.println();
    787                }
    788        }
    789}