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 org.opengion.hayabusa.common.HybsSystem; 019import org.opengion.hayabusa.common.HybsSystemException; 020import org.opengion.hayabusa.db.DBTableModelUtil; 021import org.opengion.hayabusa.io.AbstractTableReader; 022import org.opengion.fukurou.util.StringUtil; 023 024import jxl.Workbook; 025import jxl.WorkbookSettings; 026import jxl.Sheet; 027import jxl.Cell; 028import jxl.read.biff.BiffException; 029 030import java.io.File; 031import java.io.BufferedReader; 032import java.io.IOException; 033 034/** 035 * JExcelによるEXCELバイナリファイルを読み取る実装クラスです。 036 * 037 * ファイル名、シート名を指定して、データを読み取ることが可能です。 038 * 第一カラムが # で始まる行は、コメント行なので、読み飛ばします。 039 * カラム名の指定行で、カラム名が null の場合は、その列は読み飛ばします。 040 * 041 * @og.rev 3.5.4.8 (2004/02/23) 新規作成 042 * @og.group ファイル入力 043 * 044 * @version 4.0 045 * @author Kazuhiko Hasegawa 046 * @since JDK5.0, 047 */ 048public class TableReader_JExcel extends AbstractTableReader { 049 //* このプログラムのVERSION文字列を設定します。 {@value} */ 050 private static final String VERSION = "5.5.7.2 (2012/10/09)" ; 051 052 private String sheetName = null; // 3.5.4.2 (2003/12/15) 053 private String sheetNos = null; // 5.5.7.2 (2012/10/09) 054 private String filename = null; // 3.5.4.3 (2004/01/05) 055 056 /** 057 * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。 058 * コメント/空行を除き、最初の行は、必ず項目名が必要です。 059 * それ以降は、コメント/空行を除き、データとして読み込んでいきます。 060 * このメソッドは、EXCEL 読み込み時に使用します。 061 * 062 * @og.rev 4.0.0.0 (2006/09/31) 新規追加 063 * @og.rev 5.1.6.0 (2010/05/01) columns 処理 追加 064 * @og.rev 5.1.6.0 (2010/05/01) skipRowCount , useNumber の追加 065 * @og.rev 5.2.1.0 (2010/10/01) setTableColumnValues メソッドを経由して、テーブルにデータをセットする。 066 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート 067 * 068 * @see #isExcel() 069 */ 070 @Override 071 public void readDBTable() { 072 Workbook wb = null; 073 try { 074 WorkbookSettings settings = new WorkbookSettings(); 075 // System.gc()「ガベージコレクション」の実行をOFFに設定 076 settings.setGCDisabled(true); 077 wb = Workbook.getWorkbook(new File(filename),settings); 078 079 Sheet[] sheets ; // 5.5.7.2 (2012/10/09) 配列に変更 080 081 // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。 082 if( sheetNos != null && sheetNos.length() > 0 ) { 083 String[] sheetList = StringUtil.csv2ArrayExt( sheetNos , wb.getNumberOfSheets()-1 ); // 最大シート番号は、シート数-1 084 sheets = new Sheet[sheetList.length]; 085 for( int i=0; i<sheetList.length; i++ ) { 086 sheets[i] = wb.getSheet( Integer.parseInt( sheetList[i] ) ); 087 } 088 } 089 else if( sheetName != null && sheetName.length() > 0 ) { 090 Sheet sheet = wb.getSheet( sheetName ); 091 if( sheet == null ) { 092 String errMsg = "対応するシートが存在しません。 Sheet=[" + sheetName + "]" ; 093 throw new HybsSystemException( errMsg ); 094 } 095 sheets = new Sheet[] { sheet }; 096 } 097 else { 098 Sheet sheet = wb.getSheet(0); 099 sheets = new Sheet[] { sheet }; 100 } 101 102 boolean nameNoSet = true; 103 table = DBTableModelUtil.newDBTable(); 104 105 int numberOfRows = 0; 106 JxlHeaderData data = new JxlHeaderData(); 107 108 // 5.1.6.0 (2010/05/01) columns 処理 109 data.setUseNumber( isUseNumber() ); 110 if( data.setColumns( columns ) ) { 111 nameNoSet = false; 112 table.init( data.getColumnSize() ); 113 setTableDBColumn( data.getNames() ) ; 114 } 115 116 // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 117 for( int i=0; i<sheets.length; i++ ) { // 5.5.7.2 (2012/10/09) シート配列を処理します。 118 Sheet sheet = sheets[i] ; // 5.5.7.2 (2012/10/09) 119 int rowCnt = sheet.getRows(); 120 int skip = getSkipRowCount(); // 5.1.6.0 (2010/05/01) 121 // for( int nIndexRow = 0; nIndexRow < rowCnt; nIndexRow++) { 122 for( int nIndexRow = skip; nIndexRow < rowCnt; nIndexRow++) { 123 Cell[] cells = sheet.getRow( nIndexRow ); 124 if( data.isSkip( cells ) ) { continue; } 125 if( nameNoSet ) { 126 nameNoSet = false; 127 table.init( data.getColumnSize() ); 128 setTableDBColumn( data.getNames() ) ; 129 } 130 131 if( numberOfRows < getMaxRowCount() ) { 132 setTableColumnValues( data.toArray( cells ) ); // 5.2.1.0 (2010/10/01) 133 // table.addColumnValues( data.toArray( cells ) ); 134 numberOfRows ++ ; 135 } 136 else { 137 table.setOverflow( true ); 138 } 139 } 140 141 // 最後まで、#NAME が見つから無かった場合 142 if( nameNoSet ) { 143 String errMsg = "最後まで、#NAME が見つかりませんでした。" 144 + HybsSystem.CR 145 + "ファイルが空か、もしくは損傷している可能性があります。" 146 + HybsSystem.CR ; 147 throw new HybsSystemException( errMsg ); 148 } 149 } 150 } 151 catch (IOException ex) { 152 String errMsg = "ファイル読込みエラー[" + filename + "]" ; 153 throw new HybsSystemException( errMsg,ex ); 154 } 155 catch (BiffException ex) { 156 String errMsg = "ファイル読込みエラー。データ形式が不正です[" + filename + "]" ; 157 throw new HybsSystemException( errMsg,ex ); 158 } 159 finally { 160 if( wb != null ) { wb.close(); } 161 } 162 } 163 164 /** 165 * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。 166 * コメント/空行を除き、最初の行は、必ず項目名が必要です。 167 * それ以降は、コメント/空行を除き、データとして読み込んでいきます。 168 * 169 * @og.rev 3.5.4.3 (2004/01/05) 引数に、BufferedReader を受け取る要に変更します。 170 * @og.rev 4.0.0.0 (2006/09/31) UnsupportedOperationException を発行します。 171 * 172 * @param reader 各形式のデータ(使用していません) 173 */ 174 @Override 175 public void readDBTable( final BufferedReader reader ) { 176 String errMsg = "このクラスでは実装されていません。"; 177 throw new UnsupportedOperationException( errMsg ); 178 } 179 180 /** 181 * DBTableModelのデータとしてEXCELファイルを読み込むときのシート名を設定します。 182 * これにより、複数の形式の異なるデータを順次読み込むことや、シートを指定して 183 * 読み取ることが可能になります。 184 * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。 185 * のでご注意ください。 186 * 187 * @og.rev 3.5.4.2 (2003/12/15) 新規追加 188 * 189 * @param sheetName シート名 190 */ 191 @Override 192 public void setSheetName( final String sheetName ) { 193 this.sheetName = sheetName; 194 } 195 196 /** 197 * EXCELファイルを読み込むときのシート番号を指定します(初期値:0)。 198 * 199 * EXCEL読み込み時に複数シートをマージして取り込みます。 200 * シート番号は、0 から始まる数字で表します。 201 * ヘッダーは、最初のシートのカラム位置に合わせます。(ヘッダータイトルの自動認識はありません。) 202 * よって、指定するシートは、すべて同一レイアウトでないと取り込み時にカラムのずれが発生します。 203 * 204 * シート番号の指定は、カンマ区切りで、複数指定できます。また、N-M の様にハイフンで繋げることで、 205 * N 番から、M 番のシート範囲を一括指定可能です。また、"*" による、全シート指定が可能です。 206 * これらの組み合わせも可能です。( 0,1,3,5-8,10-* ) 207 * ただし、"*" に関しては例外的に、一文字だけで、すべてのシートを表すか、N-* を最後に指定するかの 208 * どちらかです。途中には、"*" は、現れません。 209 * シート番号は、重複(1,1,2,2)、逆転(3,2,1) での指定が可能です。これは、その指定順で、読み込まれます。 210 * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。 211 * このメソッドは、isExcel() == true の場合のみ利用されます。 212 * 213 * 初期値は、0(第一シート) です。 214 * 215 * ※ このクラスでは実装されていません。 216 * 217 * @og.rev 5.5.7.2 (2012/10/09) 新規追加 218 * 219 * @param sheetNos EXCELファイルのシート番号(0から始まる) 220 * @see #setSheetName( String ) 221 */ 222 @Override 223 public void setSheetNos( final String sheetNos ) { 224 this.sheetNos = sheetNos; 225 } 226 227 /** 228 * このクラスが、EXCEL対応機能を持っているかどうかを返します。 229 * 230 * EXCEL対応機能とは、シート名のセット、読み込み元ファイルの 231 * Fileオブジェクト取得などの、特殊機能です。 232 * 本来は、インターフェースを分けるべきと考えますが、taglib クラス等の 233 * 関係があり、問い合わせによる条件分岐で対応します。 234 * 235 * @og.rev 3.5.4.3 (2004/01/05) 新規追加 236 * 237 * @return EXCEL対応機能を持っているかどうか(常にtrue) 238 */ 239 @Override 240 public boolean isExcel() { 241 return true; 242 } 243 244 /** 245 * 読み取り元ファイル名をセットします。(DIR + Filename) 246 * これは、EXCEL追加機能として実装されています。 247 * 248 * @og.rev 3.5.4.3 (2004/01/05) 新規作成 249 * 250 * @param filename 読み取り元ファイル名 251 */ 252 @Override 253 public void setFilename( final String filename ) { 254 this.filename = filename; 255 if( filename == null ) { 256 String errMsg = "ファイル名が指定されていません。" ; 257 throw new HybsSystemException( errMsg ); 258 } 259 } 260} 261 262/** 263 * EXCEL ネイティブのデータを処理する ローカルクラスです。 264 * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、 265 * 行情報(HSSFRow)から、カラムの配列の取得などを行います。 266 * 267 * @og.rev 3.5.4.8 (2004/02/23) 新規追加 268 * @og.group ファイル入力 269 * 270 * @version 4.0 271 * @author 儲 272 * @since JDK5.0, 273 */ 274class JxlHeaderData { 275 private String[] names ; 276 private int[] index; 277 private int columnSize = 0; 278 private boolean nameNoSet = true; 279 private boolean useNumber = true; 280 281 /** 282 * 行番号情報を使用するかどうか[true/false]を指定します(初期値:true)。 283 * 284 * 初期値は、true(使用する) です。 285 * 286 * @og.rev 5.1.6.0 (2010/05/01) 新規作成 287 * 288 * @param useNumber 行番号情報 [true:使用している/false:していない] 289 */ 290 void setUseNumber( final boolean useNumber ) { 291 this.useNumber = useNumber ; 292 } 293 294 /** 295 * カラム名を外部から指定します。 296 * カラム名が、NULL でなければ、#NAME より、こちらが優先されます。 297 * カラム名は、順番に、指定する必要があります。 298 * 299 * @og.rev 5.1.6.0 (2010/05/01) 新規作成 300 * 301 * @param columns EXCELのカラム列(CSV形式) 302 * 303 * @return true:処理実施/false:無処理 304 */ 305 boolean setColumns( final String columns ) { 306 if( columns != null && columns.length() > 0 ) { 307 names = StringUtil.csv2Array( columns ); 308 columnSize = names.length ; 309 index = new int[columnSize]; 310 int adrs = useNumber ? 1:0 ; // useNumber =true の場合は、1件目(No)は読み飛ばす。 311 for( int i=0; i<columnSize; i++ ) { index[i] = adrs++; } 312 nameNoSet = false; 313 314 return true; 315 } 316 return false; 317 } 318 319 /** 320 * EXCEL ネイティブのデータを処理する ローカルクラスです。 321 * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、 322 * 行情報(HSSFRow)から、カラムの配列の取得などを行います。 323 * 324 * @param cells Cell[] EXCELのセル配列(行) 325 * 326 * @return true:コメント行/false:通常行 327 */ 328 boolean isSkip( final Cell[] cells ) { 329 int size = cells.length ; 330 if( size == 0 ) { return true; } 331 332 String strText = cells[0].getContents(); 333 if( strText != null && strText.length() > 0 ) { 334 if( nameNoSet ) { 335 if( "#Name".equalsIgnoreCase( strText ) ) { 336 makeNames( cells ); 337 nameNoSet = false; 338 return true; 339 } 340 else if( strText.charAt( 0 ) == '#' ) { 341 return true; 342 } 343 else { 344 String errMsg = "#NAME が見つかる前にデータが見つかりました。" 345 + HybsSystem.CR 346 + "可能性として、ファイルが、ネイティブExcelでない事が考えられます。" 347 + HybsSystem.CR ; 348 throw new HybsSystemException( errMsg ); 349 } 350 } 351 else { 352 if( strText.charAt( 0 ) == '#' ) { 353 return true; 354 } 355 } 356 } 357 358 return nameNoSet ; 359 } 360 361 /** 362 * EXCEL ネイティブの行のセル配列からカラム名情報を取得します。 363 * 364 * @og.rev 5.1.6.0 (2010/05/01) useNumber(行番号情報を、使用している(true)/していない(false)を指定) 365 * 366 * @param cells Cell[] EXCELの行のセル配列 367 */ 368 private void makeNames( final Cell[] cells ) { 369 int maxCnt = cells.length; 370 String[] names2 = new String[maxCnt]; 371 int[] index2 = new int[maxCnt]; 372 373 // 先頭カラムは、#NAME 属性行である。 374 // 先頭カラムは、#NAME 属性行であるかどうかを、useNumber で判定しておく。 375 int nFirstCell = useNumber ? 1:0 ; 376 for( int nIndexCell = nFirstCell; nIndexCell < maxCnt; nIndexCell++) { 377 String strText = cells[nIndexCell].getContents(); 378 379 if( strText != null && strText.length() > 0 ) { 380 names2[columnSize] = strText; 381 index2[columnSize] = nIndexCell; 382 columnSize++; 383 } 384 } 385 386 // #NAME を使用しない場合:no欄が存在しないケース 387 if( maxCnt == columnSize ) { 388 names = names2; 389 index = index2; 390 } 391 else { 392 names = new String[columnSize]; 393 index = new int[columnSize]; 394 System.arraycopy(names2, 0, names, 0, columnSize); 395 System.arraycopy(index2, 0, index, 0, columnSize); 396 } 397 } 398 399 /** 400 * カラム名情報を返します。 401 * ここでは、内部配列をそのまま返します。 402 * 403 * @return String[] カラム列配列情報 404 */ 405 String[] getNames() { 406 return names; 407 } 408 409 /** 410 * カラムサイズを返します。 411 * 412 * @return カラムサイズ 413 */ 414 int getColumnSize() { 415 return columnSize; 416 } 417 418 /** 419 * カラム名情報を返します。 420 * 421 * @param cells Cell[] EXCELの行のセル配列 422 * 423 * @return String[] カラム列配列情報 424 */ 425 String[] toArray( final Cell[] cells ) { 426 if( nameNoSet ) { 427 String errMsg = "#NAME が見つかる前にデータが見つかりました。"; 428 throw new HybsSystemException( errMsg ); 429 } 430 431 int cellSize = cells.length; 432 String[] data = new String[columnSize]; 433 for( int i=0;i<columnSize; i++ ) { 434 int indx = index[i]; 435 if( indx < cellSize ) { 436 data[i] = cells[indx].getContents(); 437 } 438 else { 439 data[i] = null; 440 } 441 } 442 443 return data; 444 } 445}