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.plugin.io; 017 018import java.io.BufferedReader; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.text.DecimalFormat; 024import java.text.NumberFormat; 025 026import org.apache.poi.openxml4j.exceptions.InvalidFormatException; 027import org.apache.poi.ss.usermodel.Cell; 028import org.apache.poi.ss.usermodel.DateUtil; 029import org.apache.poi.ss.usermodel.RichTextString; 030import org.apache.poi.ss.usermodel.Row; 031import org.apache.poi.ss.usermodel.Sheet; 032import org.apache.poi.ss.usermodel.Workbook; 033import org.apache.poi.ss.usermodel.WorkbookFactory; 034import org.apache.poi.ss.usermodel.CreationHelper; 035import org.apache.poi.ss.usermodel.FormulaEvaluator; 036import org.opengion.fukurou.model.EventReader_XLS; 037import org.opengion.fukurou.model.EventReader_XLSX; 038import org.opengion.fukurou.model.TableModelHelper; 039import org.opengion.fukurou.util.Closer; 040import org.opengion.fukurou.util.FileInfo; 041import org.opengion.fukurou.util.StringUtil; 042import org.opengion.fukurou.util.HybsDateUtil; 043import org.opengion.hayabusa.common.HybsSystem; 044import org.opengion.hayabusa.common.HybsSystemException; 045import org.opengion.hayabusa.db.DBTableModelUtil; 046 047/** 048 * POI による、EXCELバイナリファイルを読み取る実装クラスです。 049 * 050 * ファイル名、シート名を指定して、データを読み取ることが可能です。 051 * 第一カラムが # で始まる行は、コメント行なので、読み飛ばします。 052 * カラム名の指定行で、カラム名が null の場合は、その列は読み飛ばします。 053 * 054 * 入力形式は、openXML形式にも対応しています。 055 * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に 056 * 自動判定されます。 057 * 058 * @og.rev 3.5.4.8 (2004/02/23) 新規作成 059 * @og.rev 4.3.6.7 (2009/05/22) ooxml形式対応 060 * @og.rev 5.9.0.0 (2015/09/04) EventReaderを利用する対応 061 * @og.group ファイル入力 062 * 063 * @version 4.0 064 * @author Kazuhiko Hasegawa 065 * @since JDK5.0, 066 */ 067public class TableReader_Excel extends TableReader_Default { 068 //* このプログラムのVERSION文字列を設定します。 {@value} */ 069 private static final String VERSION = "5.5.8.2 (2012/11/09)" ; 070 071 private String filename = null; // 3.5.4.3 (2004/01/05) 072 private String sheetName = null; // 3.5.4.2 (2003/12/15) 073 private String sheetNos = null; // 5.5.7.2 (2012/10/09) 074 075 private String constKeys = null; // 5.5.8.2 (2012/11/09) 固定値となるカラム名(CSV形式) 076 private String constAdrs = null; // 5.5.8.2 (2012/11/09) 固定値となるアドレス(行-列,行-列,・・・) 077 private String nullBreakClm = null; // 5.5.8.2 (2012/11/09) 取込み条件/Sheet BREAK条件 078 079 /** 080 * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。 081 * コメント/空行を除き、最初の行は、必ず項目名が必要です。 082 * それ以降は、コメント/空行を除き、データとして読み込んでいきます。 083 * このメソッドは、EXCEL 読み込み時に使用します。 084 * 085 * @og.rev 4.0.0.0 (2006/09/31) 新規追加 086 * @og.rev 5.1.6.0 (2010/05/01) columns 処理 追加 087 * @og.rev 5.1.6.0 (2010/05/01) skipRowCountの追加 088 * @og.rev 5.1.8.0 (2010/07/01) Exception をきちっと記述(InvalidFormatException) 089 * @og.rev 5.2.1.0 (2010/10/01) setTableColumnValues メソッドを経由して、テーブルにデータをセットする。 090 * @og.rev 5.5.1.2 (2012/04/06) HeaderData を try の上にだし、エラーメッセージを取得できるようにする。 091 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート 092 * @og.rev 5.5.8.2 (2012/11/09) HeaderData に デバッグフラグを渡します。 093 * @og.rev 5.9.0.0 (2015/09/04) EventReader利用のため、ロジックをV6風に書き換えます 094 * @og.rev 5.9.7.1 (2016/04/06) setNullBreakClm実行個所を変更 095 * @og.rev 5.9.9.2 (2016/06/17) エラーメッセージ変更 096 * 097 * @see #isExcel() 098 */ 099 @Override 100/* 101 public void readDBTable() { 102 InputStream in = null; 103 HeaderData data = null; // 5.5.1.2 (2012/04/06) 104 try { 105 boolean isDebug = isDebug(); // 5.5.7.2 (2012/10/09) デバッグ情報 106 107 if( isDebug ) { System.out.println( " Filename=" + filename ) ; } 108 109 in = new FileInputStream(filename); 110 111 Workbook wb = WorkbookFactory.create(in); 112 Sheet[] sheets ; // 5.5.7.2 (2012/10/09) 配列に変更 113 114 if( isDebug ) { wb = ExcelUtil.activeWorkbook( wb ); } // デバッグモード時には、エクセルのアクティブセル領域のみにシュリンクを行う 115 116 // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。 117 if( sheetNos != null && sheetNos.length() > 0 ) { 118 String[] sheetList = StringUtil.csv2ArrayExt( sheetNos , wb.getNumberOfSheets()-1 ); // 最大シート番号は、シート数-1 119 sheets = new Sheet[sheetList.length]; 120 for( int i=0; i<sheetList.length; i++ ) { 121 sheets[i] = wb.getSheetAt( Integer.parseInt( sheetList[i] ) ); 122 } 123 } 124 else if( sheetName != null && sheetName.length() > 0 ) { 125 Sheet sheet = wb.getSheet( sheetName ); 126 if( sheet == null ) { 127 String errMsg = "対応するシートが存在しません。 Sheet=[" + sheetName + "]" ; 128 throw new HybsSystemException( errMsg ); 129 } 130 sheets = new Sheet[] { sheet }; 131 } 132 else { 133 Sheet sheet = wb.getSheetAt(0); 134 sheets = new Sheet[] { sheet }; 135 } 136 137 boolean nameNoSet = true; 138 table = DBTableModelUtil.newDBTable(); 139 140 int numberOfRows = 0; 141 data = new HeaderData(); // 5.5.1.2 (2012/04/06) 142 143 data.setDebug( isDebug ); // 5.5.8.2 (2012/11/09) 144 145 // 5.1.6.0 (2010/05/01) columns 処理 146 data.setUseNumber( isUseNumber() ); 147 148 // 5.5.8.2 (2012/11/09) 固定値となるカラム名(CSV形式)とアドレス(行-列,行-列,・・・)を設定 149 data.setSheetConstData( constKeys,constAdrs ); 150 151 int nullBreakClmAdrs = -1; // 5.5.8.2 (2012/11/09) nullBreakClm の DBTableModel上のアドレス。-1 は、未使用 152 if( data.setColumns( columns ) ) { 153 nameNoSet = false; 154 table.init( data.getColumnSize() ); 155 setTableDBColumn( data.getNames() ) ; 156 nullBreakClmAdrs = table.getColumnNo( nullBreakClm, false ); // 5.5.8.2 (2012/11/09) カラム番号取得。存在しなければ -1 を返す。 157 } 158 159 int skip = getSkipRowCount(); // 5.1.6.0 (2010/05/01) 160 // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 161 for( int i=0; i<sheets.length; i++ ) { // 5.5.7.2 (2012/10/09) シート配列を処理します。 162 Sheet sheet = sheets[i] ; // 5.5.7.2 (2012/10/09) 163 164 data.setSheetConstValues( sheet ); // 5.5.8.2 (2012/11/09) シート単位に固定カラムの値をキャッシュする。 165 166 int nFirstRow = sheet.getFirstRowNum(); 167 if( nFirstRow < skip ) { nFirstRow = skip; } // 5.1.6.0 (2010/05/01) 168 int nLastRow = sheet.getLastRowNum(); 169 if( isDebug ) { // 5.5.7.2 (2012/10/09) デバッグ情報 170 System.out.println( " Debug: 行連番=" + numberOfRows + " : Sheet= " + sheet.getSheetName() + " , 開始=" + nFirstRow + " , 終了=" + nLastRow ); 171 } 172 for( int nIndexRow = nFirstRow; nIndexRow <= nLastRow; nIndexRow++) { 173 // HSSFRow oRow = sheet.getRow(nIndexRow); 174 Row oRow = sheet.getRow(nIndexRow); 175 if( data.isSkip( oRow ) ) { continue; } 176 if( nameNoSet ) { 177 nameNoSet = false; 178 table.init( data.getColumnSize() ); 179 setTableDBColumn( data.getNames() ) ; 180 nullBreakClmAdrs = table.getColumnNo( nullBreakClm, false ); // 5.5.8.2 (2012/11/09) カラム番号取得。存在しなければ -1 を返す。 181 } 182 183 if( numberOfRows < getMaxRowCount() ) { 184 String[] tblData = data.row2Array( oRow ); // 5.5.8.2 (2012/11/09) nullBreakClm の判定のため、一旦配列に受ける。 185 if( nullBreakClmAdrs >= 0 && ( tblData[nullBreakClmAdrs] == null || tblData[nullBreakClmAdrs].isEmpty() ) ) { 186 break; // nullBreakClm が null の場合は、そのSheet処理を中止する。 187 } 188 setTableColumnValues( tblData ); // 5.5.8.2 (2012/11/09) 189 numberOfRows ++ ; 190 } 191 else { 192 table.setOverflow( true ); 193 } 194 } 195 196 // 最後まで、#NAME が見つから無かった場合 197 if( nameNoSet ) { 198 String errMsg = "最後まで、#NAME が見つかりませんでした。" 199 + HybsSystem.CR 200 + "ファイルが空か、もしくは損傷している可能性があります。" 201 + HybsSystem.CR ; 202 throw new HybsSystemException( errMsg ); 203 } 204 } 205 } 206 catch ( IOException ex ) { 207 String errMsg = "ファイル読込みエラー[" + filename + "]" ; 208 if( data != null ) { errMsg = errMsg + data.getLastCellMsg(); } // 5.5.1.2 (2012/04/06) 209 throw new HybsSystemException( errMsg,ex ); // 3.5.5.4 (2004/04/15) 引数の並び順変更 210 } 211 // 5.1.8.0 (2010/07/01) Exception をきちっと記述 212 catch (InvalidFormatException ex) { 213 String errMsg = "ファイル形式エラー[" + filename + "]" ; 214 if( data != null ) { errMsg = errMsg + data.getLastCellMsg(); } // 5.5.1.2 (2012/04/06) 215 throw new HybsSystemException( errMsg,ex ); 216 } 217 finally { 218 Closer.ioClose( in ); // 4.0.0 (2006/01/31) close 処理時の IOException を無視 219 } 220 } 221*/ 222 223 public void readDBTable() { 224 boolean isDebug = isDebug(); // 5.5.7.2 (2012/10/09) デバッグ情報 225 table = DBTableModelUtil.newDBTable(); 226 227 final TableModelHelper helper = new TableModelHelper() { 228 private boolean[] useShtNo; // 6.1.0.0 (2014/12/26) 読み取り対象のシート管理 229 230 /** 231 * シートの数のイベントが発生します。 232 * 233 * 処理の開始前に、シートの数のイベントが発生します。 234 * これを元に、処理するシート番号の選別が可能です。 235 * 初期実装は、されていません。 236 * 237 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 238 * @og.rev 6.2.6.0 (2015/06/19) #csv2ArrayExt(String,int)の戻り値を、文字列配列から数字配列に変更。 239 * @og.rev 5.9.9.2 (2016/06/17) シート名指定が効果ないため修正 240 * 241 * @param size シートの数 242 */ 243 @Override 244 public void sheetSize( final int size ) { 245 if( isDebug() ) { System.out.println( " sheetSize=" + size ) ; } 246 // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。 247 useShtNo = new boolean[size]; // シート数だけ、配列を作成する。 248 if( sheetNos != null && sheetNos.length() > 0 ) { 249 Integer[] sheetList = StringUtil.csv2ArrayExt2( sheetNos , size-1 ); // 最大シート番号は、シート数-1 250 for( int i=0; i<sheetList.length; i++ ) { 251 useShtNo[sheetList[i]] = true; // 読み取り対象のシート番号のみ、ture にセット 252 } 253 } 254// else { 255 else if ( sheetName == null || sheetName.length() == 0 ) { // 5.9.9.2 (2016/06/17) sheetNameを見ないと必ず先頭が読み込まれる 256 useShtNo[0] = true; // 一番目のシート 257 } 258 } 259 260 /** 261 * シートの読み取り開始時にイベントが発生します。 262 * 263 * 新しいシートの読み取り開始毎に、1回呼ばれます。 264 * 戻り値が、true の場合は、そのシートの読み取りを継続します。 265 * false の場合は、そのシートの読み取りは行わず、次のシートまで 266 * イベントは発行されません。 267 * 268 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 269 * 270 * @param shtNm シート名 271 * @param shtNo シート番号(0〜) 272 * @return true:シートの読み取り処理を継続します/false:このシートは読み取りません。 273 */ 274 @Override 275 public boolean startSheet( final String shtNm,final int shtNo ) { 276 // if( isDebug ) { System.out.println( " Sheet[" + shtNo + "]=" + shtNm ) ; } 277 super.startSheet( shtNm , shtNo ); // cnstData の呼び出しの為。無しで動くようにしなければ… 278 279 return ( useShtNo != null && useShtNo[shtNo] ) || 280 ( sheetName != null && sheetName.equalsIgnoreCase( shtNm ) ) ; 281 } 282 283 /** 284 * カラム名配列がそろった段階で、イベントが発生します。 285 * 286 * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、#NAME 287 * で始まるレコードを、名前配列として認識します。 288 * #value( String,int,int ) で、この #NAME だけは、継続処理されます。 289 * その上で、#NAME レコードが終了した時点で、カラム名配列が完成するので 290 * そこで初めて、このメソッドが呼ばれます。 291 * 292 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 293 * @og.rev 6.1.0.0 (2014/12/26) omitNames 属性を追加 294 * 295 * @param names カラム名配列(可変長引数) 296 * @see #value( String,int,int ) 297 */ 298 @Override 299 public void columnNames( final String[] names ) { 300 setTableDBColumn( names ) ; 301 } 302 303 /** 304 * row にあるセルのオブジェクト値がそろった段階で、イベントが発生します。 305 * 306 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 307 * @og.rev 6.2.1.0 (2015/03/13) setTableColumnValuesに、行番号を引数に追加 308 * 309 * @param vals 文字列値の1行分の配列 310 * @param rowNo 行番号(0〜) 311 */ 312 @Override 313 public void values( final String[] vals,final int rowNo ) { 314 // if( isDebug && rowNo % 100 == 0 ) { System.out.println( " rowNo=" + rowNo ) ; } 315 setTableColumnValues( vals ); // 6.2.1.0 (2015/03/13) 316 } 317 }; 318 319 helper.setDebug( isDebug ); 320 helper.setConstData( constKeys , constAdrs ); // 外部から固定値情報を指定。 321 helper.setNullBreakClm( nullBreakClm ); // 外部からnullBreakClmを指定。 5.9.7.1 (2016/04/06) setNamesの前に移動 322 helper.setNames( columns , isUseNumber() ); // 外部からカラム名配列を指定。 323 helper.setSkipRowCount( getSkipRowCount() ); // 外部からスキップ行数を指定。 324// helper.setNullBreakClm( nullBreakClm ); // 外部からnullBreakClmを指定。 5.9.7.1 (2016/04/08) DEL 325// helper.setNullSkipClm( nullSkipClm ); // 外部からnullSkipClmを指定。 V5未対応 326 327 File file = new File(filename); 328 329 // 6.2.4.2 (2015/05/29) POIUtil を使わず、EventReader_XLS、EventReader_XLSX を直接呼び出します。 330 final String SUFIX = FileInfo.getSUFIX( file ); 331 if( "xls".equalsIgnoreCase( SUFIX ) ) { 332 new EventReader_XLS().eventReader( file,helper ); 333 } 334 else if( "xlsx".equalsIgnoreCase( SUFIX ) || "xlsm".equalsIgnoreCase( SUFIX ) ) { 335 new EventReader_XLSX().eventReader( file,helper ); 336 } 337 else { 338 final String errMsg = "拡張子は、xls,xlsx,xlsm にしてください。[" + file + "]" ; 339 throw new RuntimeException( errMsg ); 340 } 341 342 // 最後まで、#NAME が見つから無かった場合 343 if( !helper.isNameSet() ) { 344 final String errMsg = "最後まで、#NAME が見つかりませんでした。" 345// + "ファイル形式が異なるか、もしくは損傷している可能性があります。" 346 + "シート名の指定ミスか、ファイル形式が異なる、もしくは損傷している可能性があります。" // 5.9.9.2 (2016/06/17) 347 + "Class=[Excel], File=[" + file + "]"; 348 throw new HybsSystemException( errMsg ); 349 } 350 351 if( isDebug ) { System.out.println( " TableReader End." ) ; } 352 } 353 354 /** 355 * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。 356 * コメント/空行を除き、最初の行は、必ず項目名が必要です。 357 * それ以降は、コメント/空行を除き、データとして読み込んでいきます。 358 * 359 * @og.rev 3.5.4.3 (2004/01/05) 引数に、BufferedReader を受け取る要に変更します。 360 * @og.rev 4.0.0.0 (2006/09/31) UnsupportedOperationException を発行します。 361 * 362 * @param reader 各形式のデータ(使用していません) 363 */ 364 @Override 365 public void readDBTable( final BufferedReader reader ) { 366 String errMsg = "このクラスでは実装されていません。"; 367 throw new UnsupportedOperationException( errMsg ); 368 } 369 370 /** 371 * DBTableModelのデータとしてEXCELファイルを読み込むときのシート名を設定します。 372 * これにより、複数の形式の異なるデータを順次読み込むことや、シートを指定して 373 * 読み取ることが可能になります。 374 * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。 375 * のでご注意ください。 376 * 377 * @og.rev 3.5.4.2 (2003/12/15) 新規追加 378 * 379 * @param sheetName シート名 380 */ 381 @Override 382 public void setSheetName( final String sheetName ) { 383 this.sheetName = sheetName; 384 } 385 386 /** 387 * EXCELファイルを読み込むときのシート番号を指定します(初期値:0)。 388 * 389 * EXCEL読み込み時に複数シートをマージして取り込みます。 390 * シート番号は、0 から始まる数字で表します。 391 * ヘッダーは、最初のシートのカラム位置に合わせます。(ヘッダータイトルの自動認識はありません。) 392 * よって、指定するシートは、すべて同一レイアウトでないと取り込み時にカラムのずれが発生します。 393 * 394 * シート番号の指定は、カンマ区切りで、複数指定できます。また、N-M の様にハイフンで繋げることで、 395 * N 番から、M 番のシート範囲を一括指定可能です。また、"*" による、全シート指定が可能です。 396 * これらの組み合わせも可能です。( 0,1,3,5-8,10-* ) 397 * ただし、"*" に関しては例外的に、一文字だけで、すべてのシートを表すか、N-* を最後に指定するかの 398 * どちらかです。途中には、"*" は、現れません。 399 * シート番号は、重複(1,1,2,2)、逆転(3,2,1) での指定が可能です。これは、その指定順で、読み込まれます。 400 * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。 401 * このメソッドは、isExcel() == true の場合のみ利用されます。 402 * 403 * 初期値は、0(第一シート) です。 404 * 405 * ※ このクラスでは実装されていません。 406 * 407 * @og.rev 5.5.7.2 (2012/10/09) 新規追加 408 * 409 * @param sheetNos EXCELファイルのシート番号(0から始まる) 410 * @see #setSheetName( String ) 411 */ 412 @Override 413 public void setSheetNos( final String sheetNos ) { 414 this.sheetNos = sheetNos; 415 } 416 417 /** 418 * EXCELファイルを読み込むときのシート単位の固定値を設定するためのカラム名とアドレスを指定します。 419 * カラム名は、カンマ区切りで指定します。 420 * 対応するアドレスを、EXCEL上の行-列を0から始まる整数でカンマ区切りで指定します。 421 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として 422 * 設定することができます。 423 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。 424 * このメソッドは、isExcel() == true の場合のみ利用されます。 425 * 426 * @og.rev 5.5.8.2 (2012/11/09) 新規追加 427 * 428 * @param constKeys 固定値となるカラム名(CSV形式) 429 * @param constAdrs 固定値となるアドレス(行-列,行-列,・・・) 430 */ 431 @Override 432 public void setSheetConstData( final String constKeys,final String constAdrs ) { 433 this.constKeys = constKeys; 434 this.constAdrs = constAdrs; 435 } 436 437 /** 438 * ここに指定されたカラム列に NULL が現れた時点で読み取りを中止します。 439 * 440 * これは、指定のカラムは必須という事を条件に、そのレコードだけを読み取る処理を行います。 441 * 複数Sheetの場合は、次のSheetを読みます。 442 * 現時点では、Excel の場合のみ有効です。 443 * 444 * @og.rev 5.5.8.2 (2012/11/09) 新規追加 445 * 446 * @param clm カラム列 447 */ 448 @Override 449 public void setNullBreakClm( final String clm ) { 450 nullBreakClm = clm; 451 } 452 453 /** 454 * このクラスが、EXCEL対応機能を持っているかどうかを返します。 455 * 456 * EXCEL対応機能とは、シート名のセット、読み込み元ファイルの 457 * Fileオブジェクト取得などの、特殊機能です。 458 * 本来は、インターフェースを分けるべきと考えますが、taglib クラス等の 459 * 関係があり、問い合わせによる条件分岐で対応します。 460 * 461 * @og.rev 3.5.4.3 (2004/01/05) 新規追加 462 * 463 * @return EXCEL対応機能を持っているかどうか(常にtrue) 464 */ 465 @Override 466 public boolean isExcel() { 467 return true; 468 } 469 470 /** 471 * 読み取り元ファイル名をセットします。(DIR + Filename) 472 * これは、EXCEL追加機能として実装されています。 473 * 474 * @og.rev 3.5.4.3 (2004/01/05) 新規作成 475 * 476 * @param filename 読み取り元ファイル名 477 */ 478 @Override 479 public void setFilename( final String filename ) { 480 this.filename = filename; 481 if( filename == null ) { 482 String errMsg = "ファイル名が指定されていません。" ; 483 throw new HybsSystemException( errMsg ); 484 } 485 } 486} 487 488/** 489 * EXCEL ネイティブのデータを処理する ローカルクラスです。 490 * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、 491 * 行情報(Row)から、カラムの配列の取得などを行います。 492 * 493 * @og.rev 3.5.4.8 (2004/02/23) 新規追加 494 * @og.group ファイル入力 495 * 496 * @version 4.0 497 * @author 儲 498 * @since JDK5.0, 499 */ 500class HeaderData { 501 private String[] names ; 502 private int[] index; // 4.3.4.0 (2008/12/01) POI3.2対応 503 private int columnSize = 0; 504 private boolean nameNoSet = true; 505 private boolean useNumber = true; 506 private boolean isDebug = false; // 5.5.8.2 (2012/11/09) 507 508 private String[] orgNames ; // 5.5.1.2 (2012/04/06) オリジナルのカラム名 509 private Cell lastCell = null; // 5.5.1.2 (2012/04/06) 最後に実行しているセルを保持(エラー時に使用する。) 510 511 // 5.5.8.2 (2012/11/09) 固定値のカラム名、DBTableModelのアドレス、Sheetの行-列番号 512 private int cnstLen = 0; // 初期値=0 の場合は、固定値を使わないという事。 513 private String[] cnstKeys ; 514 private int[] cnstIndx ; 515 private int[] cnstRowNo; 516 private int[] cnstClmNo; 517 private String[] cnstVals ; // Sheet単位の固定値のキャッシュ(シートの最初に値を取得して保持しておく) 518 519 /** 520 * デバッグ情報を、出力するかどうか[true/false]を指定します(初期値:false)。 521 * 522 * 初期値は、false(出力しない) です。 523 * 524 * @og.rev 5.5.8.2 (2012/11/09) 新規作成 525 * 526 * @param isDebug デバッグ情報 [true:出力する/false:出力しない] 527 */ 528 void setDebug( final boolean isDebug ) { 529 this.isDebug = isDebug ; 530 } 531 532 /** 533 * 行番号情報を、使用しているかどうか[true/false]を指定します(初期値:true)。 534 * 535 * 初期値は、true(使用する) です。 536 * 537 * @og.rev 5.1.6.0 (2010/05/01) 新規作成 538 * 539 * @param useNumber 行番号情報 [true:使用している/false:していない] 540 */ 541 void setUseNumber( final boolean useNumber ) { 542 this.useNumber = useNumber ; 543 } 544 545 /** 546 * 固定値となるカラム名(CSV形式)と、constAdrs 固定値となるアドレス(行-列,行-列,・・・)を設定します。 547 * 548 * アドレスは、EXCEL上の行-列をカンマ区切りで指定します。 549 * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。 550 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。 551 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として 552 * 設定することができます。 553 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。 554 * このメソッドは、isExcel() == true の場合のみ利用されます。 555 * 556 * 5.7.6.3 (2014/05/23) より、 557 * @EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。 558 * なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A〜Zまで) 559 * A処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。 560 * 例えば、sheetConstKeys="CLM,LANG,NAME" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、 561 * NAMEカラムには、シート名を読み込むことができます。 562 * これは、内部処理の簡素化のためです。 563 * 564 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。 565 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1))) 566 * 567 * @param constKeys 固定値となるカラム名(CSV形式) 568 * @param constAdrs 固定値となるアドレス(行-列,行-列,・・・) 569 * 570 * @og.rev 5.5.8.2 (2012/11/09) 新規追加 571 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応 572 */ 573 void setSheetConstData( final String constKeys,final String constAdrs ) { 574 if( constKeys == null || constKeys.isEmpty() ) { 575 return ; 576 } 577 578 cnstKeys = constKeys.split( "," ); 579 cnstLen = cnstKeys.length; 580 cnstIndx = new int[cnstLen]; 581 cnstRowNo = new int[cnstLen]; 582 cnstClmNo = new int[cnstLen]; 583 584 String[] row_col = constAdrs.split( "," ) ; 585 cnstRowNo = new int[cnstLen]; 586 cnstClmNo = new int[cnstLen]; 587 for( int j=0; j<cnstLen; j++ ) { 588 cnstKeys[j] = cnstKeys[j].trim(); // 前後の不要なスペースを削除 589 String rowcol = row_col[j].trim(); // 前後の不要なスペースを削除 590 591 // 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応 592 int sep = rowcol.indexOf( '-' ); 593 if( sep > 0 ) { 594 cnstRowNo[j] = Integer.parseInt( rowcol.substring( 0,sep ) ); 595 cnstClmNo[j] = Integer.parseInt( rowcol.substring( sep+1 ) ); 596 } 597 else { 598 if( "SHEET".equalsIgnoreCase( rowcol ) ) { // "SHEET" 時は、cnstRowNo をマイナスにしておきます。 599 cnstRowNo[j] = -1 ; 600 cnstClmNo[j] = -1 ; 601 } 602 else if( rowcol.length() >= 2 ) { 603 cnstRowNo[j] = Integer.parseInt( rowcol.substring( 1 ) ) -1; // C6 の場合、RowNoは、6-1=5 604 cnstClmNo[j] = rowcol.charAt(0) - 'A' ; // C6 の場合、'C'-'A'=2 605 } 606 } 607 608 if( isDebug ) { 609 System.out.println( " Debug: constKey=" + cnstKeys[j] + " : RowNo=" + cnstRowNo[j] + " , ClmNo=" + cnstClmNo[j] ); 610 } 611 } 612 } 613 614 /** 615 * カラム名を外部から指定します。 616 * カラム名が、NULL でなければ、#NAME より、こちらが優先されます。 617 * カラム名は、順番に、指定する必要があります。 618 * 619 * @og.rev 5.1.6.0 (2010/05/01) 新規作成 620 * @og.rev 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。 621 * 622 * @param columns EXCELのカラム列(CSV形式) 623 * 624 * @return true:処理実施/false:無処理 625 */ 626 boolean setColumns( final String columns ) { 627 if( columns != null && columns.length() > 0 ) { 628 names = StringUtil.csv2Array( columns ); 629 columnSize = names.length ; 630 index = new int[columnSize]; 631 int adrs = useNumber ? 1:0 ; // useNumber =true の場合は、1件目(No)は読み飛ばす。 632 // 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。 633 for( int i=0; i<columnSize; i++ ) { 634 index[i] = adrs++; 635 for( int j=0; j<cnstLen; j++ ) { 636 if( names[i].equalsIgnoreCase( cnstKeys[j] ) ) { 637 cnstIndx[j] = index[i]; 638 } 639 } 640 } 641 nameNoSet = false; 642 643 return true; 644 } 645 return false; 646 } 647 648 /** 649 * EXCEL ネイティブのデータを処理する ローカルクラスです。 650 * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、 651 * 行情報(Row)から、カラムの配列の取得などを行います。 652 * 653 * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応 654 * 655 * @param oRow Row EXCELの行オブジェクト 656 * 657 * @return true:コメント行/false:通常行 658 */ 659 boolean isSkip( Row oRow ) { 660 if( oRow == null ) { return true; } 661 662 int nFirstCell = oRow.getFirstCellNum(); 663 Cell oCell = oRow.getCell(nFirstCell); 664 String strText = getValue( oCell ); 665 if( strText != null && strText.length() > 0 ) { 666 if( nameNoSet ) { 667 if( "#Name".equalsIgnoreCase( strText ) ) { 668 makeNames( oRow ); 669 nameNoSet = false; 670 return true; 671 } 672 else if( strText.charAt( 0 ) == '#' ) { 673 return true; 674 } 675 else { 676 String errMsg = "#NAME が見つかる前にデータが見つかりました。" 677 + HybsSystem.CR 678 + "可能性として、ファイルが、ネイティブExcelでない事が考えられます。" 679 + HybsSystem.CR ; 680 throw new HybsSystemException( errMsg ); 681 } 682 } 683 else { 684 if( strText.charAt( 0 ) == '#' ) { 685 return true; 686 } 687 } 688 } 689 690 return nameNoSet ; 691 } 692 693 /** 694 * EXCEL ネイティブの行情報(Row)からカラム名情報を取得します。 695 * 696 * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応 697 * @og.rev 5.1.6.0 (2010/05/01) useNumber(行番号情報を、使用している(true)/していない(false)を指定) 698 * @og.rev 5.1.6.0 (2010/05/01) useNumber(行番号情報を、使用している(true)/していない(false)を指定) 699 * @og.rev 5.5.1.2 (2012/04/06) オリジナルのカラム名を取得 700 * @og.rev 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。 701 * 702 * @param oRow Row EXCELの行オブジェクト 703 */ 704 private void makeNames( final Row oRow ) { 705 // 先頭カラムは、#NAME 属性行であるかどうかを、useNumber で判定しておく。 706 short nFirstCell = (short)( useNumber ? 1:0 ); 707 short nLastCell = oRow.getLastCellNum(); 708 709 orgNames = new String[nLastCell+1]; // 5.5.1.2 (2012/04/06) オリジナルのカラム名を取得 710 711 int maxCnt = nLastCell - nFirstCell; 712 String[] names2 = new String[maxCnt]; 713 int[] index2 = new int[maxCnt]; 714 715 // 先頭カラムは、#NAME 属性行である。++ で、一つ進めている。 716 // 先頭カラムは、#NAME 属性行であるかどうかを、useNumber で判定しておく。 717 for( int nIndexCell = nFirstCell; nIndexCell <= nLastCell; nIndexCell++) { 718 Cell oCell = oRow.getCell(nIndexCell); 719 String strText = getValue( oCell ); 720 721 orgNames[nIndexCell] = strText; // 5.5.1.2 (2012/04/06) オリジナルのカラム名を取得 722 723 // #NAME 行が、ゼロ文字列の場合は、読み飛ばす。 724 if( strText != null && strText.length() > 0 ) { 725 names2[columnSize] = strText; 726 index2[columnSize] = nIndexCell; 727 columnSize++; 728 } 729 } 730 731 // #NAME を使用しない場合:no欄が存在しないケース 732 if( maxCnt == columnSize ) { 733 names = names2; 734 index = index2; 735 } 736 else { 737 names = new String[columnSize]; 738 index = new int[columnSize]; 739 System.arraycopy(names2, 0, names, 0, columnSize); 740 System.arraycopy(index2, 0, index, 0, columnSize); 741 } 742 743 // 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。 744 if( cnstLen > 0 ) { 745 for( int i=0; i<columnSize; i++ ) { 746 for( int j=0; j<cnstLen; j++ ) { 747 if( names[i].equalsIgnoreCase( cnstKeys[j] ) ) { 748 cnstIndx[j] = index[i]; 749 } 750 } 751 } 752 } 753 } 754 755 /** 756 * カラム名情報を返します。 757 * ここでは、内部配列をそのまま返します。 758 * 759 * @return String[] カラム列配列情報 760 */ 761 String[] getNames() { 762 return names; 763 } 764 765 /** 766 * カラムサイズを返します。 767 * 768 * @return カラムサイズ 769 */ 770 int getColumnSize() { 771 return columnSize; 772 } 773 774 /** 775 * Sheet単位の固定値のキャッシュ(シートの最初に値を取得して保持しておく)を設定します。 776 * これは、シートチェンジの最初に一度呼び出しておくことで、それ以降の列取得時に 777 * 固定値を利用することで処理速度向上を目指します。 778 * 779 * "SHEET" が指定された場合は、cnstRowNo[j]=-1 が設定されている。 780 * 781 * @og.rev 5.5.8.2 (2012/11/09) 新規作成 782 * @og.rev 5.7.6.3 (2014/05/23) 特殊記号(SHEET)の対応 783 * 784 * @param sheet Sheet EXCELのSheetオブジェクト 785 */ 786 void setSheetConstValues( final Sheet sheet ) { 787 cnstVals = new String[cnstLen]; 788 for( int j=0; j<cnstLen; j++ ) { 789 // 5.7.6.3 (2014/05/23) 特殊記号(SHEET)の対応 790 if( cnstRowNo[j] < 0 ) { 791 cnstVals[j] = sheet.getSheetName() ; 792 } 793 else { 794 Row oRow = sheet.getRow( cnstRowNo[j] ); 795 Cell oCell = oRow.getCell( cnstClmNo[j] ); 796 cnstVals[j] = getValue( oCell ); 797 } 798 799 if( isDebug ) { 800 System.out.println( " Debug: Sheet=" + sheet.getSheetName() + " : RowNo=" + cnstRowNo[j] + " , ClmNo=" + cnstClmNo[j] + " , " + cnstKeys[j] + "=" + cnstVals[j] ); 801 } 802 } 803 } 804 805 /** 806 * カラム名情報を返します。 807 * 808 * @og.rev 5.5.8.2 (2012/11/09) 固定値の設定を行う。 809 * 810 * @param oRow Row EXCELの行オブジェクト 811 * 812 * @return String[] カラム列配列情報 813 */ 814 String[] row2Array( final Row oRow ) { 815 if( nameNoSet ) { 816 String errMsg = "#NAME が見つかる前にデータが見つかりました。"; 817 throw new HybsSystemException( errMsg ); 818 } 819 820 String[] data = new String[columnSize]; 821 for( int i=0;i<columnSize; i++ ) { 822 Cell oCell = oRow.getCell( index[i] ); 823 data[i] = getValue( oCell ); 824 } 825 826 // 5.5.8.2 (2012/11/09) 固定値の設定を行う。 827 for( int j=0; j<cnstLen; j++ ) { 828 data[cnstIndx[j]] = cnstVals[j]; 829 } 830 return data; 831 } 832 833 /** 834 * セルオブジェクト(Cell)から値を取り出します。 835 * 836 * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正 837 * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 838 * 839 * @param oCell Cell EXCELのセルオブジェクト 840 * 841 * @return セルの値 842 */ 843 private String getValue( final Cell oCell ) { 844 lastCell = oCell; // 5.5.1.2 (2012/04/06) 今から実行するセルを取得しておきます。 845 846 if( oCell == null ) { return null; } 847 848 String strText = ""; 849 RichTextString richText; 850 int nCellType = oCell.getCellType(); 851 switch(nCellType) { 852 case Cell.CELL_TYPE_NUMERIC: 853 strText = getNumericTypeString( oCell ); 854 break; 855 case Cell.CELL_TYPE_STRING: 856 // POI3.0 strText = oCell.getStringCellValue(); 857 richText = oCell.getRichStringCellValue(); 858 if( richText != null ) { 859 strText = richText.getString(); 860 } 861 break; 862 case Cell.CELL_TYPE_FORMULA: 863 // POI3.0 strText = oCell.getStringCellValue(); 864 // 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 865 Workbook wb = oCell.getSheet().getWorkbook(); 866 CreationHelper crateHelper = wb.getCreationHelper(); 867 FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator(); 868 869 try { 870 strText = getValue(evaluator.evaluateInCell(oCell)); 871 } 872 catch ( Throwable th ) { 873 String errMsg = "セルフォーマットが解析できません。[" + oCell.getCellFormula() + "]" 874 + getLastCellMsg(); 875 throw new HybsSystemException( errMsg,th ); 876 } 877 break; 878 case Cell.CELL_TYPE_BOOLEAN: 879 strText = String.valueOf(oCell.getBooleanCellValue()); 880 break; 881 case Cell.CELL_TYPE_BLANK : 882 case Cell.CELL_TYPE_ERROR: 883 break; 884 default : 885 break; 886 } 887 return strText.trim(); 888 } 889 890 /** 891 * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。 892 * 893 * @og.rev 3.8.5.3 (2006/08/07) 新規追加 894 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 895 * 896 * @param oCell Cell 897 * 898 * @return 数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。 899 */ 900 private String getNumericTypeString( final Cell oCell ) { 901 final String strText ; 902 903 double dd = oCell.getNumericCellValue() ; 904 if( DateUtil.isCellDateFormatted( oCell ) ) { 905 strText = HybsDateUtil.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" ); // 5.5.7.2 (2012/10/09) HybsDateUtil を利用 906 } 907 else { 908 NumberFormat numFormat = NumberFormat.getInstance(); 909 if( numFormat instanceof DecimalFormat ) { 910 ((DecimalFormat)numFormat).applyPattern( "#.####" ); 911 } 912 strText = numFormat.format( dd ); 913 } 914 return strText ; 915 } 916 917 /** 918 * 最後に実行しているセル情報を返します。 919 * 920 * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。 921 * 922 * @og.rev 5.5.1.2 (2012/04/06) 新規追加 923 * @og.rev 5.5.8.2 (2012/11/09) エラー情報に、シート名も追加 924 * 925 * @return 最後に実行しているセル情報の文字列 926 */ 927 String getLastCellMsg() { 928 String lastMsg = null; 929 930 if( lastCell != null ) { 931 int rowNo = lastCell.getRowIndex(); 932 int celNo = lastCell.getColumnIndex(); 933 int no = lastCell.getColumnIndex(); 934 String shtNm = lastCell.getSheet().getSheetName(); 935 936 937 lastMsg = "Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo ; 938 if( orgNames != null && orgNames.length < no ) { 939 lastMsg = lastMsg + ", NAME=" + orgNames[no] ; 940 } 941 } 942 return lastMsg; 943 } 944}