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 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 * 180 * @param record イベント時に設定されるレコード 181 * @see org.apache.poi.hssf.eventusermodel.HSSFListener 182 */ 183 @Override 184 @SuppressWarnings(value={"deprecation"}) // poi-3.15 185 public void processRecord( final Record record ) { 186 if( record instanceof CellRecord ) { 187 final CellRecord crec = (CellRecord)record; 188 rowNo = crec.getRow() ; 189 if( helper.isSkip( rowNo ) ) { return; } // 行のスキップ判定 190 colNo = crec.getColumn(); 191 } 192 193 String val = null; 194 switch( record.getSid() ) { 195 // the BOFRecord can represent either the beginning of a sheet or the workbook 196 case BOFRecord.sid: // Beginning Of File 197 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 198 if( record instanceof BOFRecord && ((BOFRecord)record).getType() == BOFRecord.TYPE_WORKSHEET ) { 199 // 6.1.0.0 (2014/12/26) シートの数のイベント 200 // シート一覧の読み取り後、最初のレコードの判定に、shtNo を使います。 201 if( shtNo < 0 ) { helper.sheetSize( shtNms.size() ); } 202 203 shtNo++ ; // 現在のシート番号。初期値が、-1 してあるので、先に ++ する。 204 shtNm = shtNms.get( shtNo ) ; // 現在のシート名。 205 rcdLvl = 0; // シートの開始 206 isReadSheet = helper.startSheet( shtNm,shtNo ); 207 if( useDebug ) { System.out.println( "① BOFRecord:" + record ); } 208 } 209 else { 210 rcdLvl++; // シート以外の開始 211 } 212 break; 213 case EOFRecord.sid: // End Of File record 214 if( rcdLvl == 0 ) { // シートの終了 215 helper.endSheet( shtNo ); 216 isReadSheet = true; 217 if( useDebug ) { System.out.println( "② EOFRecord" + record ); } 218 } 219 else { 220 rcdLvl--; // シート以外の終了 221 } 222 break; 223 case BoundSheetRecord.sid: // シート一覧(一括で最初にイベントが発生する) 224 if( useDebug ) { System.out.println( "③ BoundSheetRecord" ); } 225 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 226 if( record instanceof BoundSheetRecord ) { 227 shtNms.add( ((BoundSheetRecord)record).getSheetname() ); 228 } 229 break; 230 case SSTRecord.sid: // Static String Table Record 231 if( useDebug ) { System.out.println( "④ SSTRecord" ); } 232 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 233 if( record instanceof SSTRecord ) { 234 sstrec = (SSTRecord)record; // LabelSSTRecord のインデックスに対応した文字列配列 235 } 236 // for( int k = 0; k < sstrec.getNumUniqueStrings(); k++ ) { 237 // System.out.println("table[" + k + "]=" + sstrec.getString(k)); 238 // } 239 break; 240 // case RowRecord.sid: // stores the row information for the sheet 241 // if( useDebug ) { System.out.println( "⑤ RowRecord" ); } 242 // RowRecord rowrec = (RowRecord) record; 243 // System.out.println("Row=[" + rowrec.getRowNumber() + "],Col=[" 244 // + rowrec.getFirstCol() + "]-[" + rowrec.getLastCol() + "]" ); 245 // break; 246 247 // NumberRecord の XFIndex が、ExtendedFormatRecord の 番号になり、その値が、FormatIndex = FormatRecordのIndexCode 248 case ExtendedFormatRecord.sid: 249 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 250 if( record instanceof ExtendedFormatRecord ) { 251 format.addExtFmtRec( (ExtendedFormatRecord)record ); 252 } 253 break; 254 255 // IndexCode をキーに、FormatString を取り出す。 256 case FormatRecord.sid: 257 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 258 if( record instanceof FormatRecord ) { 259 format.addFmtRec( (FormatRecord)record ); 260 } 261 break; 262 263 case NumberRecord.sid: // extend CellRecord 264 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 265 if( isReadSheet && record instanceof NumberRecord ) { 266 val = format.getNumberValue( (NumberRecord)record ); 267 } 268 break; 269 // SSTRecords store a array of unique strings used in Excel. 270 case LabelSSTRecord.sid: // extend CellRecord 271 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 272 if( isReadSheet && record instanceof LabelSSTRecord ) { 273 final LabelSSTRecord lrec = (LabelSSTRecord)record; 274 val = sstrec.getString(lrec.getSSTIndex()).getString(); 275 } 276 break; 277 case BoolErrRecord.sid: // extend CellRecord 278 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 279 if( isReadSheet && record instanceof BoolErrRecord ) { 280 final BoolErrRecord berec = (BoolErrRecord)record; 281 final byte errVal = berec.getErrorValue(); 282 val = errVal == 0 ? Boolean.toString( berec.getBooleanValue() ) 283 // 6.3.1.0 (2015/06/28) 284 : FormulaError.forInt( errVal ).getString(); 285 } 286 break; 287 case FormulaRecord.sid: // extend CellRecord 288 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 289 if( isReadSheet && record instanceof FormulaRecord ) { 290 final FormulaRecord frec = (FormulaRecord)record; 291 // switch (frec.getCachedResultType()) { // 6.5.0.0 (2016/09/30) poi-3.12 292 switch ( CellType.forInt( frec.getCachedResultType() ) ) { // 6.5.0.0 (2016/09/30) poi-3.15 293 // case Cell.CELL_TYPE_NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.12 294 case NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.15 295 final double num = frec.getValue(); 296 if( Double.isNaN(num) ) { 297 // Formula result is a string 298 // This is stored in the next record 299 isNextRecord = true; 300 } 301 else { 302 val = NumberToTextConverter.toText( num ); 303 } 304 break; 305 // case Cell.CELL_TYPE_BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.12 306 case BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.15 307 val = Boolean.toString(frec.getCachedBooleanValue()); 308 break; 309 // case Cell.CELL_TYPE_ERROR: // 6.5.0.0 (2016/09/30) poi-3.12 310 case ERROR: // 6.5.0.0 (2016/09/30) poi-3.15 311 // 6.3.1.0 (2015/06/28) 312 val = FormulaError.forInt( frec.getCachedErrorValue() ).getString(); 313 break; 314 // case Cell.CELL_TYPE_STRING: // 6.5.0.0 (2016/09/30) poi-3.12 315 case STRING: // 6.5.0.0 (2016/09/30) poi-3.15 316 isNextRecord = true; 317 break; 318 default : break; 319 } 320 } 321 break; 322 case StringRecord.sid: // FormulaRecord の場合の次のレコードに値が設定されている 323 // 6.3.9.0 (2015/11/06) 未チェック/未確認のキャスト対応(findbugs) 324 if( isReadSheet && isNextRecord && record instanceof StringRecord ) { 325 // String for formula 326 final StringRecord srec = (StringRecord)record; 327 val = srec.getString(); 328 isNextRecord = false; 329 } 330 break; 331 // case TextObjectRecord.sid: // 6.2.5.0 (2015/06/05) TextBox などの、非セルテキスト 332 // if( isReadSheet ) { 333 // if( useDebug ) { System.out.println( "⑥ TextObjectRecord" ); } 334 // final TextObjectRecord txrec = (TextObjectRecord)record; 335 // val = txrec.getStr().getString(); 336 // } 337 // break; 338 default : 339 break ; 340 } 341 if( val != null ) { 342 // 値 行(Row) 列(Col) 343 helper.value( val, rowNo, colNo ); // イベント処理 344 } 345 } 346 } 347 348 /** 349 * アプリケーションのサンプルです。 350 * 351 * 入力ファイル名 は必須で、第一引数固定です。 352 * 353 * Usage: java org.opengion.fukurou.model.EventReader_XLS 入力ファイル名 354 * 355 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 356 * @og.rev 6.2.0.0 (2015/02/27) staticメソッドをインスタンスメソッドに変更 357 * 358 * @param args コマンド引数配列 359 */ 360 public static void main( final String[] args ) { 361 final String usageMsg = "Usage: java org.opengion.fukurou.model.EventReader_XLS 入力ファイル名" ; 362 if( args.length == 0 ) { 363 System.err.println( usageMsg ); 364 return ; 365 } 366 367 final File file = new File( args[0] ); 368 final EventReader reader = new EventReader_XLS(); 369 370 reader.eventReader( // 6.2.0.0 (2015/02/27) 371 file, 372 new TableModelHelper() { 373 /** 374 * シートの読み取り開始時にイベントが発生します。 375 * 376 * @param shtNm シート名 377 * @param shtNo シート番号(0~) 378 * @return true:シートの読み取り処理を継続します/false:このシートは読み取りません。 379 */ 380 public boolean startSheet( final String shtNm,final int shtNo ) { 381 System.out.println( "S[" + shtNo + "]=" + shtNm ); 382 return super.startSheet( shtNm,shtNo ); 383 } 384 385 // public void columnNames( final String[] names ) { 386 // System.out.println( "NM=" + java.util.Arrays.toString( names ) ); 387 // } 388 389 // public void values( final String[] vals,final int rowNo ) { 390 // System.out.println( "V[" + rowNo + "]=" + java.util.Arrays.toString( vals ) ); 391 // } 392 393 // public boolean isSkip( final int rowNo ) { 394 // super.isSkip( rowNo ); 395 // return false; 396 // } 397 398 /** 399 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。 400 * 401 * @param val 文字列値 402 * @param rowNo 行番号(0~) 403 * @param colNo 列番号(0~) 404 * @return 読み取りするかどうか(true:読み取りする/false:読み取りしない) 405 */ 406 public boolean value( final String val,final int rowNo,final int colNo ) { 407 System.out.println( "R[" + rowNo + "],C[" + colNo + "]=" + val ); 408 return super.value( val,rowNo,colNo ); 409 } 410 } 411 ); 412 } 413}