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}