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;   // 最初に見つけたときに、++ するので初期値は −1にしておく
146                private String  shtNm                   ;               // BOFRecord でキャッシュしておきます。
147                private boolean isNextRecord    ;               // FormulaRecord で、次のレコードに値があるかどうかの判定
148                private boolean isReadSheet             = true; // シートの読み取りを行うかどうか
149
150                private int             rcdLvl  ;                               // BOFRecord で+1、EOFRecord で−1 して、シートの 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 + "/" + isReadSheet ); }
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( "A EOFRecord" + record ); }
215                                        }
216                                        else {
217                                                rcdLvl--;                                               // シート以外の終了
218                                        }
219                                        break;
220                                case BoundSheetRecord.sid:              // シート一覧(一括で最初にイベントが発生する)
221                                        if( useDebug ) { System.out.println( "B 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( "C 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( "D 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( "E 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}