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