    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 org.opengion.fukurou.util.Closer;                                                // 6.2.0.0 (2015/02/27)
    019import static org.opengion.fukurou.util.HybsConst.CR;                   // 6.1.0.0 (2014/12/26) refactoring
    020
    021import java.io.File;                                                                                    // 6.2.0.0 (2015/02/27)
    022import java.io.InputStream;
    023import java.io.FileInputStream;
    024import java.io.BufferedInputStream;
    025import java.io.IOException;
    026
    027import java.util.List;
    028import java.util.ArrayList;
    029
    030import org.apache.poi.hssf.record.Record;
    031import org.apache.poi.hssf.record.CellRecord;
    032import org.apache.poi.hssf.record.SSTRecord;
    033import org.apache.poi.hssf.record.BOFRecord;
    034import org.apache.poi.hssf.record.EOFRecord;
    035import org.apache.poi.hssf.record.BoundSheetRecord;
    036import org.apache.poi.hssf.record.LabelSSTRecord;
    037import org.apache.poi.hssf.record.NumberRecord;
    038import org.apache.poi.hssf.record.BoolErrRecord;
    039import org.apache.poi.hssf.record.FormulaRecord;
    040import org.apache.poi.hssf.record.StringRecord;
    041import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
    042import org.apache.poi.hssf.eventusermodel.HSSFListener;
    043import org.apache.poi.hssf.eventusermodel.HSSFRequest;
    044
    045import org.apache.poi.hssf.record.ExtendedFormatRecord;                 // 6.2.0.0 (2015/02/27)
    046import org.apache.poi.hssf.record.FormatRecord;                                 // 6.2.0.0 (2015/02/27)
    047
    048import org.apache.poi.ss.usermodel.Cell;
    049import org.apache.poi.ss.usermodel.ErrorConstants;
    050// import org.apache.poi.ss.usermodel.ErrorConstants;                   // 6.3.1.0 (2015/06/28) Deprecated.
    051import org.apache.poi.ss.usermodel.FormulaError;                                // 6.3.1.0 (2015/06/28)
    052import org.apache.poi.ss.util.NumberToTextConverter;
    053import org.apache.poi.poifs.filesystem.POIFSFileSystem;
    054
    055/**
    056 * POI による、Excel(xls)の読み取りクラスです。
    057 *
    058 * xls形式のEXCELを、イベント方式でテキストデータを読み取ります。
    059 * このクラスでは、HSSF(.xls)形式のファイルを、TableModelHelper を介したイベントで読み取ります。
    060 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。
    061 * ※ openGion形式のEXCELファイルとは、#NAME 列に、カラム名があり、#で始まる
    062 *    レコードは、コメントとして判断し、読み飛ばす処理の事です。
    063 *
    064 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    065 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReader_XSSF → EventReader_XLSX)
    066 * @og.group ファイル入力
    067 *
    068 * @version  6.0
    069 * @author   Kazuhiko Hasegawa
    070 * @since    JDK7.0,
    071 */
    072public final class EventReader_XLS implements EventReader {
    073        /** このプログラムのVERSION文字列を設定します。   {@value} */
    074        private static final String VERSION = "6.2.0.0 (2015/02/27)" ;
    075
    076        /**
    077         * 引数ファイル(Excel)を、HSSFイベントモデルを使用してテキスト化します。
    078         *
    079         * TableModelHelperは、EXCEL読み取り処理用の統一されたイベント処理クラスです。
    080         * openGion特有のEXCEL処理方法(#NAME , 先頭行#コメントなど)を実装しています。
    081         * これは、HSSFやXSSFの処理を、統一的なイベントモデルで扱うためです。
    082         * SSモデルが良いのですが、巨大なXSSF(.xlsx)ファイルを解析すると、OutOfMemoryエラーが
    083         * 発生する為、個々に処理する必要があります。
    084         * あくまで、読み取り限定であれば、こちらのイベントモデルで十分です。
    085         *
    086         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    087         * @og.rev 6.2.0.0 (2015/02/27) staticメソッドをインスタンスメソッドに変更
    088         *
    089         * @param       file 入力ファイル
    090         * @param       helper イベント処理するオブジェクト
    091         */
    092        @Override
    093        public void eventReader( final File file , final TableModelHelper helper ) {
    094                InputStream fin  = null;
    095                InputStream din  = null;
    096
    097                try {
    098                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    099                        helper.startFile( file );
    100
    101                        fin = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    102                        final POIFSFileSystem poifs = new POIFSFileSystem( fin );
    103
    104                        din = poifs.createDocumentInputStream( "Workbook" );
    105
    106                        final HSSFRequest req = new HSSFRequest();
    107                        req.addListenerForAllRecords( new ExcelListener( helper ) );
    108                        final HSSFEventFactory factory = new HSSFEventFactory();
    109
    110                        factory.processEvents( req, din );
    111                }
    112                catch( IOException ex ) {
    113                        final String errMsg = "ファイルの読取処理に失敗しました。"
    114                                                                + " filename=" + file + CR
    115                                                                + ex.getMessage() ;
    116                        throw new RuntimeException( errMsg , ex );
    117                }
    118                finally {
    119                        Closer.ioClose( din );
    120                        Closer.ioClose( fin );
    121                        helper.endFile( file );                                 // 6.2.0.0 (2015/02/27)
    122                }
    123        }
    124
    125        /**
    126         * HSSF(.xls)処理に特化したイベント処理を行う、HSSFListener の実装内部クラス。
    127         *
    128         * HSSFListener のイベント処理を、TableModelHelper に変換します。
    129         * これは、HSSFやXSSFの処理を、統一的なイベントモデルで扱うためです。
    130         * SSモデルが良いのですが、巨大なXSSF(.xlsx)ファイルを解析すると、OutOfMemoryエラーが
    131         * 発生する為、個々に処理する必要があります。
    132         * あくまで、読み取り限定であれば、こちらのイベントモデルで十分です。
    133         *
    134         * 読み書きも含めた EXCEL処理を行うには、ExcelModel クラスが別にあります。
    135         *
    136         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    137         */
    138        private static final class ExcelListener implements HSSFListener {
    139                private final TableModelHelper  helper;
    140                private final ExcelStyleFormat  format;
    141
    142                private final List<String> shtNms = new ArrayList();      // シート名の一括登録
    143                private SSTRecord sstrec;                               // LabelSSTRecord のインデックスに対応した文字列配列
    144
    145                private int             shtNo                   = -1;   // 最初に見つけたときに、＋＋ するので初期値は －１にしておく
    146                private String  shtNm                   ;               // BOFRecord でキャッシュしておきます。
    147                private boolean isNextRecord    ;               // FormulaRecord で、次のレコードに値があるかどうかの判定
    148                private boolean isReadSheet             = true; // シートの読み取りを行うかどうか
    149
    150                private int             rcdLvl  ;                               // BOFRecord で＋１、EOFRecord で－１ して、シートの EOFRecord の判定に使う。
    151
    152                private int             rowNo   ;                               // 処理中の行番号(0～)
    153                private int             colNo   ;                               // 処理中の列番号(0～)
    154
    155                private final boolean   useDebug        ;       // デバッグフラグ
    156
    157                /**
    158                 * TableModelHelper を引数に取るコンストラクタ
    159                 *
    160                 * HSSFListener のイベント処理を、TableModelHelper に変換します。
    161                 *
    162                 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    163                 * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
    164                 *
    165                 * @param       helper イベント処理するオブジェクト
    166                 */
    167                public ExcelListener( final TableModelHelper helper ) {
    168                        this.helper = helper ;
    169                        useDebug        = helper.isDebug();                                             // 6.2.0.0 (2015/02/27) デバッグ情報の出力
    170                        format          = new ExcelStyleFormat();                               // 6.2.0.0 (2015/02/27) StylesTable 追加
    171                }
    172
    173                /**
    174                 * HSSFListener のイベントを受け取るメソッド。
    175                 *
    176                 * @og.rev 6.1.0.0 (2014/12/26) シートの数のイベント
    177                 * @og.rev 6.3.1.0 (2015/06/28) ErrorConstants のDeprecated に伴う、FormulaError への置き換え。
    178                 *
    179                 * @param record        イベント時に設定されるレコード
    180                 * @see org.apache.poi.hssf.eventusermodel.HSSFListener
    181                 */
    182                @Override
    183                public void processRecord( final Record record ) {
    184                        if( record instanceof CellRecord ) {
    185                                final CellRecord crec = (CellRecord)record;
    186                                rowNo = crec.getRow() ;
    187                                if( helper.isSkip( rowNo ) ) { return; }                // 行のスキップ判定
    188                                colNo = crec.getColumn();
    189                        }
    190                        String val = null;
    191                        switch( record.getSid() ) {
    192                                // the BOFRecord can represent either the beginning of a sheet or the workbook
    193                                case BOFRecord.sid:                             // Beginning Of File
    194                                        final BOFRecord bof = (BOFRecord)record;
    195                                        if( bof.getType() == BOFRecord.TYPE_WORKSHEET ) {
    196                                                // 6.1.0.0 (2014/12/26) シートの数のイベント
    197                                                // シート一覧の読み取り後、最初のレコードの判定に、shtNo を使います。
    198                                                if( shtNo < 0 ) { helper.sheetSize( shtNms.size() ); }
    199
    200                                                shtNo++ ;                                               // 現在のシート番号。初期値が、-1 してあるので、先に ＋＋ する。
    201                                                shtNm = shtNms.get( shtNo ) ;   // 現在のシート名。
    202                                                rcdLvl = 0;                                             // シートの開始
    203                                                isReadSheet = helper.startSheet( shtNm,shtNo );
    204                                                if( useDebug ) { System.out.println( "① BOFRecord:" + record ); }
    205                                        }
    206                                        else {
    207                                                rcdLvl++;                                               // シート以外の開始
    208                                        }
    209                                        break;
    210                                case EOFRecord.sid:                             // End Of File record
    211                                        if( rcdLvl == 0 ) {                                     // シートの終了
    212                                                helper.endSheet( shtNo );
    213                                                isReadSheet = true;
    214                                                if( useDebug ) { System.out.println( "② EOFRecord" + record ); }
    215                                        }
    216                                        else {
    217                                                rcdLvl--;                                               // シート以外の終了
    218                                        }
    219                                        break;
    220                                case BoundSheetRecord.sid:              // シート一覧(一括で最初にイベントが発生する)
    221                                        if( useDebug ) { System.out.println( "③ BoundSheetRecord" ); }
    222                                        final BoundSheetRecord bsr = (BoundSheetRecord) record;
    223                                        shtNms.add( bsr.getSheetname() );
    224                                        break;
    225                                case SSTRecord.sid:                             // Static String Table Record
    226                                        if( useDebug ) { System.out.println( "④ SSTRecord" ); }
    227                                        sstrec = (SSTRecord)record;             // LabelSSTRecord のインデックスに対応した文字列配列
    228                //                      for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) {
    229                //                              System.out.println("table[" + k + "]=" + sstrec.getString(k));
    230                //                      }
    231                                        break;
    232                //              case RowRecord.sid:                             // stores the row information for the sheet
    233                //                      if( useDebug ) { System.out.println( "⑤ RowRecord" ); }
    234                //                      RowRecord rowrec = (RowRecord) record;
    235                //                      System.out.println("Row=[" + rowrec.getRowNumber() + "],Col=["
    236                //                                      + rowrec.getFirstCol() + "]-[" + rowrec.getLastCol() + "]" );
    237                //                      break;
    238
    239                                // NumberRecord の XFIndex が、ExtendedFormatRecord の 番号になり、その値が、FormatIndex = FormatRecordのIndexCode
    240                                case ExtendedFormatRecord.sid:
    241                                        format.addExtFmtRec( (ExtendedFormatRecord)record );
    242                                break;
    243
    244                                // IndexCode をキーに、FormatString を取り出す。
    245                                case FormatRecord.sid:
    246                                        format.addFmtRec( (FormatRecord)record );
    247                                break;
    248
    249                                case NumberRecord.sid:                  // extend CellRecord
    250                                        if( isReadSheet ) {
    251                                                val = format.getNumberValue( (NumberRecord)record );
    252                                        }
    253                                        break;
    254                                // SSTRecords store a array of unique strings used in Excel.
    255                                case LabelSSTRecord.sid:                // extend CellRecord
    256                                        if( isReadSheet ) {
    257                                                final LabelSSTRecord lrec = (LabelSSTRecord)record;
    258                                                val = sstrec.getString(lrec.getSSTIndex()).getString();
    259                                        }
    260                                        break;
    261                                case BoolErrRecord.sid:                 // extend CellRecord
    262                                        if( isReadSheet ) {
    263                                                final BoolErrRecord berec = (BoolErrRecord)record;
    264                                                final byte errVal = berec.getErrorValue();
    265                                                val = errVal == 0 ? Boolean.toString( berec.getBooleanValue() )
    266                                                                                        // 6.3.1.0 (2015/06/28)
    267//                                                                                : ErrorConstants.getText( errVal );                   // Deprecated
    268                                                                                  : FormulaError.forInt( errVal ).getString();
    269                                        }
    270                                        break;
    271                                case FormulaRecord.sid:                 // extend CellRecord
    272                                        if( isReadSheet ) {
    273                                                final FormulaRecord frec = (FormulaRecord)record;
    274                                                switch (frec.getCachedResultType()) {
    275                                                        case Cell.CELL_TYPE_NUMERIC:
    276                                                                final double num = frec.getValue();
    277                                                                if( Double.isNaN(num) ) {
    278                                                                        // Formula result is a string
    279                                                                        // This is stored in the next record
    280                                                                        isNextRecord = true;
    281                                                                }
    282                                                                else {
    283                                                                        val = NumberToTextConverter.toText( num );
    284                                                                }
    285                                                                break;
    286                                                        case Cell.CELL_TYPE_BOOLEAN:
    287                                                                val = Boolean.toString(frec.getCachedBooleanValue());
    288                                                                break;
    289                                                        case Cell.CELL_TYPE_ERROR:
    290                                                                // 6.3.1.0 (2015/06/28)
    291                                                                val = ErrorConstants.getText(frec.getCachedErrorValue());       // Deprecated
    292//                                                              val = FormulaError.forInt( frec.getCachedErrorValue() ).getString();
    293                                                                break;
    294                                                        case Cell.CELL_TYPE_STRING:
    295                                                                isNextRecord = true;
    296                                                                break;
    297                                                        default : break;
    298                                                }
    299                                        }
    300                                        break;
    301                                case StringRecord.sid:                  // FormulaRecord の場合の次のレコードに値が設定されている
    302                                        if( isReadSheet ) {
    303                                                if( isNextRecord ) {
    304                                                        // String for formula
    305                                                        final StringRecord srec = (StringRecord)record;
    306                                                        val = srec.getString();
    307                                                        isNextRecord = false;
    308                                                }
    309                                        }
    310                                        break;
    311                //              case TextObjectRecord.sid:              // 6.2.5.0 (2015/06/05) TextBox などの、非セルテキスト
    312                //                      if( isReadSheet ) {
    313                //                              if( useDebug ) { System.out.println( "⑥ TextObjectRecord" ); }
    314                //                              final TextObjectRecord txrec = (TextObjectRecord)record;
    315                //                              val = txrec.getStr().getString();
    316                //                      }
    317                //                      break;
    318                                default :
    319                                        break ;
    320                        }
    321//System.out.println(rowNo + "/" +colNo +"/"+ val);
    322                        if( val != null ) {
    323                                //           値   行(Row) 列(Col)
    324                                helper.value( val, rowNo,  colNo );                     // イベント処理
    325                        }
    326                }
    327        }
    328
    329        /**
    330         * アプリケーションのサンプルです。
    331         *
    332         * 入力ファイル名 は必須で、第一引数固定です。
    333         *
    334         * Usage: java org.opengion.fukurou.model.EventReader_XLS 入力ファイル名
    335         *
    336         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    337         * @og.rev 6.2.0.0 (2015/02/27) staticメソッドをインスタンスメソッドに変更
    338         *
    339         * @param       args    コマンド引数配列
    340         */
    341        public static void main( final String[] args ) {
    342                final String usageMsg = "Usage: java org.opengion.fukurou.model.EventReader_XLS 入力ファイル名" ;
    343                if( args.length == 0 ) {
    344                        System.err.println( usageMsg );
    345                        return ;
    346                }
    347
    348                final File file = new File( args[0] );
    349                final EventReader reader = new EventReader_XLS();
    350
    351                reader.eventReader(                                     // 6.2.0.0 (2015/02/27)
    352                        file,
    353                        new TableModelHelper() {
    354                                /**
    355                                 * シートの読み取り開始時にイベントが発生します。
    356                                 *
    357                                 * @param   shtNm  シート名
    358                                 * @param   shtNo  シート番号(0～)
    359                                 * @return  true:シートの読み取り処理を継続します/false:このシートは読み取りません。
    360                                 */
    361                                public boolean startSheet( final String shtNm,final int shtNo ) {
    362                                        System.out.println( "S[" + shtNo + "]=" + shtNm );
    363                                        return super.startSheet( shtNm,shtNo );
    364                                }
    365
    366                //              public void columnNames( final String[] names ) {
    367                //                      System.out.println( "NM=" + java.util.Arrays.toString( names ) );
    368                //              }
    369
    370                //              public void values( final String[] vals,final int rowNo ) {
    371                //                      System.out.println( "V[" + rowNo + "]=" + java.util.Arrays.toString( vals ) );
    372                //              }
    373
    374                //              public boolean isSkip( final int rowNo ) {
    375                //                      super.isSkip( rowNo );
    376                //                      return false;
    377                //              }
    378
    379                                /**
    380                                 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。
    381                                 *
    382                                 * @param   val     文字列値
    383                                 * @param   rowNo   行番号(0～)
    384                                 * @param   colNo   列番号(0～)
    385                                 * @return  読み取りするかどうか(true:読み取りする/false:読み取りしない)
    386                                 */
    387                                public boolean value( final String val,final int rowNo,final int colNo ) {
    388                                        System.out.println( "R[" + rowNo + "],C[" + colNo + "]=" + val );
    389                                        return super.value( val,rowNo,colNo );
    390                                }
    391                        }
    392                );
    393        }
    394}