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