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