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.util.StringUtil; // 6.2.0.0 (2015/02/27) 019import org.opengion.fukurou.util.FileInfo; // 6.2.0.0 (2015/02/27) 020 021import java.util.List; 022import java.util.ArrayList; 023import java.util.Map; // 6.1.0.0 (2014/12/26) ConstData を Mapで管理 024import java.util.HashMap; // 6.1.0.0 (2014/12/26) ConstData を Mapで管理 025import java.util.Arrays; // 6.2.0.0 (2015/02/27) 026import java.io.File; // 6.2.0.0 (2015/02/27) 027 028/** 029 * EXCELやテキストファイルを、イベント方式に準拠して、読み込み処理を行います。 030 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。 031 * ※ openGion形式のEXCEL/テキストファイルとは、#NAME 列に、カラム名があり、#で始まる 032 * レコードは、コメントとして判断し、読み飛ばす処理の事です。 033 * 034 * このイベントクラスは、サブクラスを作成し、EXCEL関連の EventReader_XLS、EventReader_XLSX 035 * クラスや、EventReader_TEXT などのテキスト関連のクラスで、eventReader メソッドの引数に指定します。 036 * EventReader_XLS と、EventReader_XLSX は、対象のEXCEL形式が異なりますが、実際は、 037 * POIUtil#eventReader( String , TableModelHelper ) を使用すれば、拡張子に応じて使用するクラスを 038 * 選択します。 039 * 040 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 041 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReaderEvent → TableModelHelper) 042 * @og.group ファイル入力 043 * 044 * @version 6.0 045 * @author Kazuhiko Hasegawa 046 * @since JDK7.0, 047 */ 048public class TableModelHelper { 049 private int nowRowNo = -1; // 現在の行番号 050 private boolean isColSkip ; // カラムスキップ中かどうか(true:スキップ中) 051 private boolean isNowName ; // カラム名配列の収集中かどうか(true:収集中) 052 private boolean isReadBreak ; // 読み取り処理を途中で中止するかどうか(true:中止) 053 private String nullBreakClm; // データがnullまたはゼロ文字列の場合に、Sheet読み取りを停止 054 private String nullSkipClm ; // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加 055 056 private int skipRowCnt ; // 読み飛ばす行数(読み飛ばし中でも ContDataは取得する) 057 private int nBrkClmNo = -1; // nullBreakClmの列番号 058 private int nSkpClmNo = -1; // 6.2.3.0 (2015/05/01) nullSkipClmの列番号 059 060 private int clmSize = -1 ; // カラムサイズ(-1は未設定) 061 private String[] names ; // カラム名配列 062 private String[] vals ; // 値の文字列配列(1行分) 063 private boolean useVals ; // 値配列に設定されたか? 064 065 private List<Integer> colList ; // カラム番号:name が null かゼロ文字列の場合は、飛ばします。 066 private List<String> nmsList ; // カラム番号に対応したカラム名配列 067 068 private ConstData cnstData ; 069 070 private boolean useDebug ; // 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加 071 072 /** 073 * ファイルの読み取り開始時にイベントが発生します。 074 * 075 * 新しいファイルの読み取り開始毎に、1回呼ばれます。 076 * 戻り値が、true の場合は、そのファイルの読み取りを継続します。 077 * false の場合は、そのファイルの読み取りは行われません。 078 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、 079 * super.startFile(String,int) してください。 080 * 初期実装では、true を返します。 081 * 082 * @og.rev 6.2.0.0 (2015/02/27) 新規作成 083 * 084 * @param file 読み取りファイル 085 * @return 読取継続するか [true:継続/false:読取らない] 086 */ 087 public boolean startFile( final File file ) { 088 if( useDebug ) { System.out.println( "startFile=[" + file + "]" ); } 089 // ファイル属性を設定する。 090 if( cnstData != null ) { cnstData.putConstFile( file ); } 091 return true; 092 } 093 094 /** 095 * ファイルの読み取り終了時にイベントが発生します。 096 * 097 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、 098 * super.endFile(File) してください。 099 * 100 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 101 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加 102 * 103 * @param file 読み取りファイル 104 */ 105 public void endFile( final File file ) { 106 if( useDebug ) { System.out.println( "endFile=[" + file + "]" ); } 107 isReadBreak = false; // 読み取り再開 108 endRow(); // 行終了処理 109 if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_FILE ); } 110 } 111 112 /** 113 * シートの読み取り開始時にイベントが発生します。 114 * 115 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。 116 * 117 * 新しいシートの読み取り開始毎に、1回呼ばれます。 118 * 戻り値が、true の場合は、そのシートの読み取りを継続します。 119 * false の場合は、そのシートの読み取りは行わず、次のシートまで 120 * イベントは発行されません。 121 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、 122 * super.startSheet(String,int) してください。 123 * 初期実装では、true を返します。 124 * 125 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 126 * 127 * @param shtNm シート名 128 * @param shtNo シート番号(0〜) 129 * @return 読取継続するか [true:継続/false:読取らない] 130 */ 131 public boolean startSheet( final String shtNm,final int shtNo ) { 132 if( useDebug ) { System.out.println( "startSheet=[" + shtNm + "], No=[" + shtNo + "]" ); } 133 // シート名を設定する。 134 if( cnstData != null ) { cnstData.putConstSheet( shtNm ); } 135 return true; 136 } 137 138 /** 139 * シートの読み取り終了時にイベントが発生します。 140 * 141 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。 142 * 143 * #columnNames( String[] ) や、#values( String[] ,int ) などは、行の処理が完了した時点で 144 * イベントが呼ばれるため、一番最後のレコードの終了条件が判りません。 145 * そこで、このイベントを呼ぶことで、シートの終了(=最終行の終了)処理を行うことができます。 146 * 147 * 引数のシート番号は、参考情報で、#startSheet( String,int ) で呼ばれたシート番号と 148 * 比較できるようにしています。 149 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、 150 * super.endSheet(int) してください。 151 * 152 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 153 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加 154 * 155 * @param shtNo シート番号(0〜) 156 */ 157 public void endSheet( final int shtNo ) { 158 if( useDebug ) { System.out.println( "endSheet No=[" + shtNo + "]" ); } 159 isReadBreak = false; // 読み取り再開 160 endRow(); // 行終了処理 161 if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_SHEET ); } 162 } 163 164 /** 165 * 読み取り状態の時に、rowNo にある行データを引数にイベントが発生します。 166 * 167 * ※ 主に、行データの読み込み処理で使用されるメソッドです。 168 * このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの 169 * 処理の過程で、設定されます。 170 * このメソッドから、各種イベントが発行されます。 171 * 172 * 行データをセパレータで分解したのち、value( String ,int ,int ) メソッドを呼びます。 173 * ただし、行データが、null、空文字列、#で始まる場合は、呼ばれません。 174 * 175 * @og.rev 6.2.0.0 (2015/02/27) 新規作成 176 * @og.rev 6.2.1.0 (2015/03/13) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除 177 * @og.rev 6.2.5.0 (2015/06/05) デバック時に1行単位に出力するのを止めます。 178 * 179 * @param line 行データ 180 * @param rowNo 行番号(0〜) 181 * @param sepa セパレータ 182 */ 183 protected void value( final String line,final int rowNo,final char sepa ) { 184 nowRowNo = rowNo; // 現在行の設定 185 // 値が存在する場合のみ、処理する。 186 if( line!=null && !line.isEmpty() ) { 187 // 6.2.5.0 (2015/06/05) デバック時に1行単位に出力するのを止めます。 188 // if( useDebug ) { System.out.println( "rowNo[" + rowNo + "], line=[" + line + "]" ); } 189 190 // 先頭カラムのコメント判断と、#NAME列判断 191 if( line.charAt(0)=='#' ) { 192 // 互換性の問題。#NAME は、大文字小文字両方対応できないといけない。( 5 == "#NAME".length() の事) 193 if( clmSize <= 0 && line.length() >= 5 && "#NAME".equalsIgnoreCase( line.substring( 0,5 ) ) ) { 194 195 colList = new ArrayList() ; // 初期化 196 nmsList = new ArrayList() ; // 初期化 197 final String[] tmpNm = StringUtil.csv2Array( line ,sepa ); 198 for( int colNo=1; colNo<tmpNm.length; colNo++ ) { // #NAME を無視(colNo=1〜) 199 final String nm = tmpNm[colNo]; 200 // #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処) 201 if( nm != null && !nm.isEmpty() ) { 202 colList.add( Integer.valueOf( colNo ) ); 203 nmsList.add( nm ); 204 } 205 } 206 isNowName = true; 207 } 208 } 209 // clmSize > 0 は、#NAME が設定済みという意味 210 else if( clmSize > 0 && skipRowCnt <= rowNo ) { // skipRowCnt は、値セットだけ影響する。 211 final String[] tmpVals = StringUtil.csv2Array( line ,sepa ); 212 // if( cnstData != null ) { tmpVals = cnstData.getConstVals( tmpVals ); } 213 // int min = Math.min( clmSize,tmpVals.length ); 214 215 for( int i=0; i<clmSize; i++ ) { 216 final int indx = colList.get( i ); // カラム番号の位置が 値配列の配列番号 217 if( indx >= 0 && indx < tmpVals.length ) { 218 vals[i] = StringUtil.csvOutQuote( tmpVals[indx] ); // 6.2.1.0 (2015/03/13) 219 } 220 } 221 useVals = true; 222 // values( vals , rowNo ); // イベント発行(現在行) 223 } 224 225 endRow(); // 行末処理実行。名前設定処理が走ります。 226 } 227 } 228 229 /** 230 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。 231 * 232 * ※ 主に、XCEL関係の読み取り処理で使用されるメソッドです。 233 * このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの 234 * 処理の過程で、設定されます。 235 * このメソッドから、各種イベントが発行されます。 236 * 237 * 戻り値が、true の場合は、その行の読み取りを継続します。 238 * false の場合は、その行の読み取りは行わず、次の行まで 239 * イベントは発行されません。 240 * 241 * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、# で 242 * 始まる場合は、その行はスキップします。 243 * ここでの return は、#isSkip( int ) と逆になりますので、ご注意ください。 244 * 初期実装では、#NAME処理、行スキップ、行終了処理等を実行します。 245 * オーバーライドする場合は、super.value(String,int,int) してください。 246 * 247 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 248 * @og.rev 6.2.2.0 (2015/03/27) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除 249 * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加 250 * 251 * @param val 文字列値 252 * @param rowNo 行番号(0〜) 253 * @param colNo 列番号(0〜) 254 * @return 読み取りするかどうか(true:読み取りする/false:読み取りしない) 255 * @see #isSkip( int ) 256 */ 257 protected boolean value( final String val,final int rowNo,final int colNo ) { 258 // if( useDebug ) { System.out.println( "R[" + rowNo + "," + colNo + "]=" + val ); } 259 // 値が存在する場合のみ、処理する。 260 if( val!=null && val.length()>0 ) { 261 // 行番号が異なった場合は、行の終了処理を実行する。 262 if( nowRowNo != rowNo ) { 263 endRow(); // 行終了処理 264 nowRowNo = rowNo; // 現在行の設定 265 } 266 // 固定文字の設定なので、コメントもスキップも無関係 267 if( cnstData != null ) { cnstData.putConstValue( val,rowNo,colNo ); } 268 269 // 先頭カラムのコメント判断と、#NAME列判断 270 if( colNo==0 && val.charAt(0)=='#' ) { 271 if( "#NAME".equalsIgnoreCase( val ) && clmSize <= 0 ) { // clmSize <= 0 は、#NAME未設定という意味 272 isNowName = true ; 273 colList = new ArrayList() ; // 初期化 274 nmsList = new ArrayList() ; // 初期化 275 } 276 isColSkip = !isNowName; // #NAME の場合は、false(SKIPしない) 277 } 278 else if( isNowName ) { // #NAME処理中 279 // #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処) 280 colList.add( Integer.valueOf( colNo ) ); 281 nmsList.add( val ); 282 } 283 // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加 284 else if( nSkpClmNo >= 0 && StringUtil.isNull( vals[nSkpClmNo] ) ) { // nullSkipClm 処理 285 isColSkip = true; // Skip する 286 } 287 // clmSize > 0 は、#NAME が設定済みという意味 288 else if( clmSize > 0 && skipRowCnt <= rowNo ) { // skipRowCnt は、値セットだけ影響する。 289 final int indx = colList.indexOf( Integer.valueOf( colNo ) ); // カラム番号の位置が 値配列の配列番号 290 if( indx >= 0 ) { 291 vals[indx] = StringUtil.csvOutQuote( val ); // 6.2.2.0 (2015/03/27) 292 useVals = true; 293 } 294 } 295 } 296 297 return !isColSkip; 298 } 299 300 /** 301 * rowNo を元に、この行をスキップするかどうか判定のイベントが発生します。 302 * 303 * ※ EXCEL関係の列毎にイベントが発生する場合の列処理をスキップするのが目的です。 304 * 行単位に読み込む場合や、行のループに入れても、意味はありません。 305 * 引数から、行のループに入れてしまいそうですが、行列のループに入れてください。 306 * 307 * ※ 主に、EXCEL関係の読み取り処理で使用されるメソッドです。 308 * このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの 309 * 処理の過程で、設定されます。 310 * このメソッドから、各種イベントが発行されます。 311 * 312 * 戻り値が、true の場合は、その行の読み取りをスキップします。 313 * false の場合は、読み取り処理を継続します。 314 * スキップ中は、行に関するイベントは発行されません。 315 * 316 * 値(val)を求める前に、行情報を入手し、スキップ中の現在行と同じ場合に、スキップ(=true)と判定します。 317 * スキップ中かどうかは、#value( String,int,int ) で、判定します。 318 * 初期実装では、スキップ中 かつ 現在行と同じ行の場合、または、シートブレーク中の場合に、true を返します。 319 * 320 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 321 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加 322 * 323 * @param rowNo 行番号(0〜) 324 * @return スキップするかどうか(true:スキップする/false:スキップしない) 325 * @see #value( String,int,int ) 326 */ 327 protected boolean isSkip( final int rowNo ) { 328 return isColSkip && nowRowNo == rowNo || isReadBreak ; 329 } 330 331 /** 332 * 行の終了時に実行されます。 333 * 334 * ここでは、rowNo がブレークするか、シート終了時に呼ぶことで、 335 * 一番最後の行の処理を行います。 336 * 337 * #NAME 処理中の場合(isNowName == true) は、カラム名配列を作成して、 338 * columnNames イベントを呼び出します。 339 * 値配列が設定されている場合(useVals == true) は、values イベントを 340 * 呼び出します。 341 * #NAME 処理が完了している場合は、値配列の初期化を行います。 342 * そのうえで、スキップの解除(isColSkip = false)と行データ 343 * 未設定(useVals = false)に、初期化します。 344 * 345 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 346 * @og.rev 6.2.2.0 (2015/03/27) Overflow処理は、Tag側に戻す。 347 * 348 * @see #columnNames( String[] ) 349 * @see #values( String[],int ) 350 */ 351 private void endRow() { 352 if( isNowName ) { // #NAME 処理中の場合 353 clmSize = colList.size(); 354 names = nmsList.toArray( new String[clmSize] ); 355 if( nullBreakClm != null ) { 356 for( int i=0; i<clmSize; i++ ) { 357 if( nullBreakClm.equalsIgnoreCase( names[i] ) ) { 358 nBrkClmNo = i; 359 break; 360 } 361 } 362 } 363 // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加 364 if( nullSkipClm != null ) { 365 for( int i=0; i<clmSize; i++ ) { 366 if( nullSkipClm.equalsIgnoreCase( names[i] ) ) { 367 nSkpClmNo = i; 368 break; 369 } 370 } 371 } 372 373 if( cnstData != null ) { cnstData.setColumns( names ); } 374 columnNames( names.clone() ); // イベント 375 isNowName = false; 376 nmsList = null ; // 不要 377 } 378 else if( useVals ) { // 値配列が設定されている場合 379 if( nBrkClmNo >= 0 && StringUtil.isNull( vals[nBrkClmNo] ) ) { // nullBreakClm 処理 380 isReadBreak = true; // ReadBreak する。 381 } 382 else { 383 if( cnstData != null ) { vals = cnstData.getConstVals( vals ); } 384 values( vals , nowRowNo ); // イベント発行(現在行) 385 } 386 } 387 if( clmSize > 0 ) { // clmSize > 0 は、#NAME が設定済みという意味 388 vals = new String[clmSize]; // 値配列の初期化 389 } 390 391 isColSkip = false; // スキップの解除 392 useVals = false; // 行データ未設定にする。 393 } 394 395 /** 396 * シート数のイベントが発生します。 397 * 398 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。 399 * 400 * 処理の開始前に、シート数のイベントが発生します。 401 * これを元に、処理するシート番号の選別が可能です。 402 * 403 * @og.rev 6.1.0.0 (2014/12/26) シートの数のイベント 404 * 405 * @param size シート数 406 */ 407 public void sheetSize( final int size ) { 408 if( useDebug ) { System.out.println( "sheetSize=[" + size + "]" ); } 409 } 410 411 /** 412 * カラム名配列がそろった段階で、イベントが発生します。 413 * 414 * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、#NAME 415 * で始まるレコードを、名前配列として認識します。 416 * #value( String,int,int ) で、この #NAME だけは、継続処理されます。 417 * その上で、#NAME レコードが終了した時点で、カラム名配列が完成するので 418 * そこで初めて、このメソッドが呼ばれます。(EXCEL関係の場合) 419 * 420 * 外部から設定する、#setNames( String , boolean ) 時も同様に呼ばれます。 421 * 422 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 423 * 424 * @param names カラム名配列 425 * @see #value( String,int,int ) 426 * @see #setNames( String , boolean ) 427 */ 428 public void columnNames( final String[] names ) { 429 if( useDebug ) { System.out.println( "columnNames=[" + Arrays.toString(names) + "]" ); } 430 } 431 432 /** 433 * row にあるセルのオブジェクト値がそろった段階で、イベントが発生します。 434 * 435 * 初期実装は、何もありません。 436 * 437 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 438 * 439 * @param vals 文字列値の1行分の配列 440 * @param rowNo 行番号(0〜) 441 */ 442 public void values( final String[] vals,final int rowNo ) { 443 if( useDebug ) { System.out.println( "values[" + rowNo + "]=[" + Arrays.toString(vals) + "]" ); } 444 } 445 446 /** 447 * 外部からCSV形式のカラム名文字列を設定します。 448 * 449 * ※ イベント処理実行前の初期化処理です。 450 * 451 * カラム名配列を、#NAME で始まるレコードではなく、外部から指定します。 452 * ここで設定した場合、#columnNames( String[] )イベントも発生します。 453 * null か、長さゼロのカラム名は、設定されません。 454 * 455 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応 456 * 457 * @param clms CSV形式のカラム名文字列 458 * @param useNumber 行番号情報 [true:使用している/false:していない] 459 * @see #columnNames( String[] ) 460 */ 461 public void setNames( final String clms , final boolean useNumber ) { 462 if( clms != null && clms.length() > 0 ) { 463 if( useDebug ) { System.out.println( "setNames=[" + clms + "]" ); } 464 465 colList = new ArrayList() ; // 初期化 466 nmsList = new ArrayList() ; // 初期化 467 468 final String[] nms = StringUtil.csv2Array( clms ); 469 final int adrs = useNumber ? 1:0 ; // useNumber =true の場合は、1件目(No)は読み飛ばす。 470 for( int i=0; i<nms.length; i++ ) { 471 // null か、長さゼロのカラム名は、設定されません。 472 final String nm = nms[i]; 473 if( nm != null && nm.length() > 0 ) { 474 colList.add( Integer.valueOf( i+adrs ) ); 475 nmsList.add( nm ); 476 } 477 } 478 479 isNowName = true; // 名前設定中 480 useVals = false; // データ未設定 481 endRow(); // 行末処理実行。名前設定処理が走ります。 482 483 } 484 } 485 486 /** 487 * カラム名配列が、設定されたかどうか、返します。 488 * 489 * カラム名配列は、#NAME で始まるレコードか、#setNames( String,boolean )メソッドを 490 * 使用して、外部から指定します。 491 * カラム名配列が未設定の場合は、データもセットできないので、処理の最後の判定に使います。 492 * 493 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応 494 * 495 * @return カラム名配列が、設定されたかどうか(true:設定済み/false:未設定) 496 * @see #setNames( String,boolean ) 497 */ 498 public boolean isNameSet() { 499 return clmSize > 0; // clmSize > 0 は、#NAME が設定済みという意味 500 } 501 502 /** 503 * 以降のデータを読み飛ばすかどうかを指定します(初期値:false)。 504 * 505 * データを読み込む途中で、それ以降のデータを読み込む必要がなくなった場合に、 506 * true に設定すると、以降のデータを今のシート/ファイルが終了するまで、スキップします。 507 * 例えば、読み込むべき必須カラムで判定する、nullBreakClm 機能を実現できます。 508 * 初期値は、false:読み飛ばさない です。 509 * 510 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加 511 * 512 * @param flag 以降のデータを読み飛ばすかどうか [true:読み飛ばす/false:読み飛ばさない] 513 * @see #isSkip( int ) 514 */ 515 public void setReadBreak( final boolean flag ) { 516 isReadBreak = flag ; 517 } 518 519 /** 520 * 先頭データの読み飛ばし件数を設定します。 521 * 522 * ※ イベント処理実行前の初期化処理です。 523 * 524 * データの読み始めの行を指定します。 525 * シート/ファイルの先頭行が、0行としてカウントしますので、設定値は、読み飛ばす 526 * 件数になります。(1と指定すると、1件読み飛ばし、2件目から読み込みます。) 527 * 読み飛ばしは、コメント行などは、無視しますので、実際の行数分読み飛ばします。 528 * #NAME属性や、columns 属性は、読み飛ばし中でも処理対象行になります。 529 * 530 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 531 * 532 * @param count 読み始めの初期値(0なら、読み飛ばしなし) 533 */ 534 public void setSkipRowCount( final int count ) { 535 skipRowCnt = count; 536 } 537 538 /** 539 * ここに指定されたカラム列に NULL/ゼロ文字列 が現れた時点でSheetの読み取りを中止します。 540 * 541 * これは、指定のカラムは必須という事を条件に、そのレコードだけを読み取る処理を行います。 542 * 複数Sheetの場合は、次のSheetを読みます。 543 * Sheetが存在しない場合(通常のテキスト等)では、読み取り処理の終了になります。 544 * 545 * @og.rev 6.2.0.0 (2015/02/27) 新規追加 546 * 547 * @param clm カラム列 548 */ 549 public void setNullBreakClm( final String clm ) { 550 nullBreakClm = clm; 551 } 552 553 /** 554 * ここに指定されたカラム列に NULL が現れたレコードは読み飛ばします。 555 * 556 * 例えば、更新対象カラムで、null の場合は、何もしない、などのケースで使用できます。 557 * 複数カラムの場合は、AND条件やOR条件などが、考えられるため、 558 * カラムを一つにまとめて、指定してください。 559 * 560 * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加 561 * 562 * @param clm カラム列 563 */ 564 public void setNullSkipClm( final String clm ) { 565 nullSkipClm = clm; 566 } 567 568 /** 569 * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。 570 * 571 * ※ イベント処理実行前の初期化処理です。 572 * 573 * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。 574 * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。 575 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。 576 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として 577 * 設定することができます。 578 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。 579 * 580 * 5.7.6.3 (2014/05/23) より、 581 * @EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。 582 * なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A〜Zまで) 583 * EXCEL2007形式で列数が拡張されていますが、列数は制限しています。 584 * A処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。 585 * 6.2.0.0 (2015/02/27) より、 586 * BEXCEL以外では、"FILE","NAME","SUFIX" が使えます。(EXCEL時にも使えます。) 587 * NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC) 588 * 例えば、sheetConstKeys="CLM,LANG,NAME" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、 589 * NAMEカラムには、シート名を読み込むことができます。 590 * これは、内部処理の簡素化のためです。 591 * 592 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。 593 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1))) 594 * 595 * @og.rev 5.5.8.2 (2012/11/09) 新規追加 596 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応 597 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応 598 * 599 * @param constKeys 固定値となるカラム名(CSV形式) 600 * @param constAdrs 固定値となるアドレス(行-列,行-列...) or(A1,B3...) 601 */ 602 public void setConstData( final String constKeys,final String constAdrs ) { 603 if( constKeys != null && !constKeys.isEmpty() && constAdrs != null && !constAdrs.isEmpty() ) { 604 cnstData = new ConstData( constKeys,constAdrs ); 605 // setNames( String , boolean ) と、このメソッドの呼び出し順に影響がないようにするため。 606 if( names != null ) { cnstData.setColumns( names ); } 607 } 608 } 609 610 /** 611 * デバッグ情報を出力するかどうか[true:する/false:しない]を指定します。 612 * 613 * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、 614 * どのシートなのか、判らなくなります。 615 * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。 616 * 通常は使用しませんので、設定を無視します。 617 * 初期値は、false:デバッグ情報を出力しない です。 618 * 619 * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加 620 * 621 * @param useDebug デバッグ出力するか [true:する/false:しない] 622 */ 623 public void setDebug( final boolean useDebug ) { 624 this.useDebug = useDebug; 625 } 626 627 /** 628 * デバッグ情報を出力するかどうか[true:する/false:しない]を取得します。 629 * 630 * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、 631 * どのシートなのか、判らなくなります。 632 * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。 633 * 634 * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加 635 * 636 * @return デバッグ出力 [true:する/false:しない] 637 */ 638 protected boolean isDebug() { 639 return useDebug ; 640 } 641 642 /** 643 * EXCELファイルの所定の位置から、固定値を取り出す処理を管理します。 644 * 645 * この固定値の取出しは、内部処理に、非常に依存しているため、今は、 646 * TableModelHelper クラスに含めていますが、将来的には、分ける予定です。 647 * 648 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 649 * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)追加 650 * 651 * @version 6.0 652 * @author Kazuhiko Hasegawa 653 * @since JDK7.0, 654 */ 655 private static final class ConstData { 656 /** 内部情報のクリア方法の指定 {@value} */ 657 public static final int END_FILE = 1 ; 658 /** 内部情報のクリア方法の指定 {@value} */ 659 public static final int END_SHEET = 2 ; 660 661 // 6.2.0.0 (2015/02/27) 特殊記号処理 追加 (SHEET,FILE,NAME,SUFIX) 662 private static final String KEYS = ",SHEET,FILE,NAME,SUFIX,"; 663 664 // @cnstMap は、cnstKey をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション) 665 private final Map<String,String> cnstMap = new HashMap(); // 6.1.0.0 (2014/12/26) Mapで管理 666 // ArowcolMap は、rowcol をキーに、アドレスを持っています。 667 private final Map<String,Integer> rowcolMap = new HashMap(); // 6.1.0.0 (2014/12/26) Mapで管理 668 // BvalsMap は、アドレス をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション) 669 private final Map<Integer,String> valsMap = new HashMap(); // 6.1.0.0 (2014/12/26) Mapで管理 670 671 private int maxRow = -1 ; // 最大値持っておき、判定処理を早める。 672 673 // ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。 674 private boolean isNameSet ; 675 private File tmpFile ; 676 private String tmpShtNm ; 677 678 /** 679 * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。 680 * 681 * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。 682 * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。 683 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。 684 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として 685 * 設定することができます。 686 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。 687 * 688 * 5.7.6.3 (2014/05/23) より、 689 * @EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。 690 * なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A〜Zまで) 691 * EXCEL2007形式で列数が拡張されていますが、列数は制限しています。 692 * A処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。 693 * 6.2.0.0 (2015/02/27) より、 694 * BEXCEL以外では、"FILE","NAME","SUFIX" が使えます。 695 * NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC) 696 * 例えば、sheetConstKeys="CLM,LANG,SHT" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、 697 * SHTカラムには、シート名を読み込むことができます。 698 * これは、内部処理の簡素化のためです。 699 * 700 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。 701 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1))) 702 * 703 * @og.rev 5.5.8.2 (2012/11/09) 新規追加 704 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応 705 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応 706 * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)の追加対応 707 * 708 * @param constKeys 固定値となるカラム名(CSV形式) 709 * @param constAdrs 固定値となるアドレス(行-列,行-列...) or(A1,B3...) 710 */ 711 ConstData( final String constKeys,final String constAdrs ) { 712 final String[] cnstKeys = constKeys.split( "," ); 713 final String[] row_col = constAdrs.split( "," ); 714 715 if( cnstKeys.length != row_col.length ) { 716 final String errMsg = "キーに対するアドレスの個数が不一致です。Keys=[" + constKeys + "]" 717 + " , Adrs=[" + constAdrs + "]" ; 718 throw new RuntimeException( errMsg ); 719 } 720 721 // 初期の カラムとアドレス(キーワード)の関連付け 722 for( int j=0; j<cnstKeys.length; j++ ) { 723 final String cnstKey = cnstKeys[j].trim(); // 前後の不要なスペースを削除 724 if( !cnstKey.isEmpty() ) { 725 String rowcol = row_col[j].trim(); // 前後の不要なスペースを削除 726 if( !rowcol.isEmpty() ) { 727 // 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応 728 final int sep = rowcol.indexOf( '-' ); 729 if( sep > 0 ) { 730 final int row = Integer.parseInt( rowcol.substring( 0,sep ) ); 731 // int col = Integer.parseInt( rowcol.substring( sep+1 ) ); 732 if( maxRow < row ) { maxRow = row; } 733 // rowcol = String.valueOf( (char)('A' + col) ) + String.valueOf( row + 1 ) ; // row-col 形式なので、不要 734 } 735 // 6.2.0.0 (2015/02/27) 特殊記号(SHEET,FILE,NAME,SUFIX)の追加対応 736 else if( !KEYS.contains( ',' + rowcol + ',' ) ) { 737 final int row = Integer.parseInt( rowcol.substring( 1 ) ) -1; // C6 の場合、rowは、6-1=5 738 final int col = rowcol.charAt(0) - 'A' ; // C6 の場合、colは、'C'-'A'=2 739 if( maxRow < row ) { maxRow = row; } 740 rowcol = row + "-" + col ; 741 } 742 cnstMap.put( cnstKey , rowcol ); // 6.1.0.0 (2014/12/26) cnstMap に行列情報を設定する 743 } 744 } 745 } 746 } 747 748 /** 749 * カラム名配列を元に、固定値カラムのアドレスを求めます。 750 * カラム名配列は、順番に、指定する必要があります。 751 * 752 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応 753 * 754 * @param names カラム列配列(可変長引数) 755 */ 756 void setColumns( final String... names ) { 757 // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 758 if( names != null && names.length > 0 ) { 759 // 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。 760 for( int i=0; i<names.length; i++ ) { 761 final String rowcol = cnstMap.get( names[i] ); // cnstKey があれば、rowcol が取得できるはず 762 if( rowcol != null ) { 763 rowcolMap.put( rowcol , Integer.valueOf( i ) ); 764 } 765 } 766 isNameSet = true; // 名前設定がされないと、FILEやSHEET キーワードが使えない。 767 768 if( tmpFile != null ) { putConstFile( tmpFile ); } 769 if( tmpShtNm != null ) { putConstSheet( tmpShtNm ); } 770 } 771 } 772 773 /** 774 * 読み取り時に、rowNo,colNo にあるセルの値を、固定値となるカラム名に関連付けます。 775 * 776 * イベントモデルでは、固定値の指定アドレス(rowNo,colNo)をピンポイントで取得することが 777 * できないため、イベント発生毎に、チェックする必要があります。 778 * そのため、固定値を使用すると、処理速度が低下します。 779 * 780 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 781 * 782 * @param val 文字列値(null またはゼロ文字列は、不可) 783 * @param rowNo 行番号(0〜) 784 * @param colNo 列番号(0〜) 785 */ 786 void putConstValue( final String val,final int rowNo,final int colNo ) { 787 if( rowNo <= maxRow ) { 788 final String rowcol = rowNo + "-" + colNo ; 789 final Integer adrs = rowcolMap.get( rowcol ); 790 if( adrs != null ) { 791 valsMap.put( adrs,val ); 792 } 793 } 794 } 795 796 /** 797 * ファイル系特殊記号(FILE,NAME,SUFIX)を指定します。 798 * 799 * ../AAA/BBB/CCC.XLS というファイルオブジェクトに対して、 800 * FILE : CCC.XLS ファイル名 801 * NAME : CCC 拡張子なしのファイル名 802 * SUFIX : xls ピリオド無しの拡張子(小文字に統一) 803 * 804 * これは、新しいファイルの読み取り開始時に、設定します。 805 * 806 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。 807 * 808 * @og.rev 6.2.0.0 (2015/02/27) 新規作成 809 * 810 * @param filNm 指定ファイル 811 */ 812 void putConstFile( final File filNm ) { 813 if( filNm != null ) { 814 tmpFile = filNm; // 名前設定がされるまで、一時保管する。(順番があるので呼ばれればセットしておく) 815 if( isNameSet ) { // 名前設定がされないと、FILEやSHEET キーワードが使えない。 816 final FileInfo info = new FileInfo( filNm ); 817 818 for( final String key : FileInfo.KEYS ) { 819 final Integer adrs = rowcolMap.get( key ); 820 if( adrs != null ) { 821 final String val = info.getValue( key ); 822 if( val != null ) { 823 valsMap.put( adrs,val ); 824 } 825 } 826 } 827 } 828 } 829 } 830 831 /** 832 * シート名を外部から指定します。 833 * アドレス指定の特殊系として、"SHEET" 文字列を指定できます。 834 * これは、新しいシートの読み取り開始時に、設定します。 835 * 836 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。 837 * 838 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 839 * 840 * @param shtNm シート名 841 */ 842 void putConstSheet( final String shtNm ) { 843 if( shtNm != null ) { 844 tmpShtNm = shtNm; // 名前設定がされるまで、一時保管する。 845 if( isNameSet ) { // 名前設定がされないと、FILEやSHEET キーワードが使えない。 846 final Integer adrs = rowcolMap.get( "SHEET" ); 847 if( adrs != null ) { 848 valsMap.put( adrs,shtNm ); 849 } 850 } 851 } 852 } 853 854 /** 855 * 内部の valsMap を初期化します。 856 * 固定値の読み取りは、シートごとに行います。 857 * 新しいシートにデータが設定されていない場合、前のシートの値が残ります。 858 * ここでは、シート呼出しごとに、毎回クリアします。 859 * 引数に応じて、クリアする範囲を限定します。 860 * END_FILE 時は、すべてクリア。END_SHEET 時は、ファイル情報は残します。 861 * 862 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 863 * 864 * @param type 内部情報のクリア方法の指定(END_FILE or END_SHEET) 865 */ 866 void clearValsMap( final int type ) { 867 valsMap.clear(); 868 if( type == END_SHEET ) { putConstFile( tmpFile ); } // 全削除後にファイル情報を再設定 869 } 870 871 /** 872 * 値配列のデータに、固定値を設定します。 873 * 引数の文字列配列に、固定値を設定しますので、配列オブジェクト自体を更新します。 874 * 875 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 876 * 877 * @param vals 文字列値の1行分の配列 878 * @return 固定値を設定された1行分の文字列配列 879 */ 880 String[] getConstVals( final String[] vals ) { 881 if( vals != null && vals.length > 0 ) { 882 for( final Map.Entry<Integer,String> entry : valsMap.entrySet() ) { 883 final int adrs = entry.getKey().intValue(); // Autoボクシングでよい? 884 final String cnst = entry.getValue(); 885 if( adrs < vals.length ) { 886 vals[adrs] = cnst; 887 } 888 } 889 } 890 return vals ; 891 } 892 } 893}