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.process; 017 018import org.opengion.fukurou.util.Argument; 019import org.opengion.fukurou.util.FileString; 020import org.opengion.fukurou.util.Closer ; 021import org.opengion.fukurou.util.StringUtil ; 022import org.opengion.fukurou.util.LogWriter; 023 024import org.opengion.fukurou.model.ExcelModel; // 6.0.2.0 (2014/08/29) 025 026// import org.apache.poi.ss.usermodel.Cell; 027// import org.apache.poi.ss.usermodel.RichTextString; 028// import org.apache.poi.ss.usermodel.Row; 029// import org.apache.poi.ss.usermodel.Sheet; 030// import org.apache.poi.ss.usermodel.Workbook; 031// import org.apache.poi.ss.usermodel.WorkbookFactory; 032// import org.apache.poi.openxml4j.exceptions.InvalidFormatException; 033 034import java.util.Map ; 035import java.util.LinkedHashMap ; 036import java.util.List ; 037import java.util.ArrayList ; 038 039import java.io.File; 040// import java.io.FileInputStream; 041// import java.io.FileOutputStream; 042// import java.io.IOException; 043 044/** 045 * Process_GrepChangeExcel は、上流から受け取った FileLineModelから、語句を 046 * 置換する、ChainProcess インターフェースの実装クラスです。 047 * 048 * Process_GrepChange との違いは、入力元のファイルが、テキストファイルなのか、 049 * ネイティブEXCELファイルなのかの違いです。 050 * 051 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、 052 * 対象とする語句をセル単位に置換します。 053 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、 054 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。 055 * ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース 056 * が前後に存在している場合は、ご注意ください。 057 * 置換文字(値)は、\t と \n の特殊文字が使用できます。 058 * この GrepChangeExcel では、語句に、正規表現は使用できません。正規表現のキーワード 059 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。 060 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、 061 * 置き換えた結果も、同じファイルにセーブします。 062 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。 063 * -inEncode は、keywordFileのエンコード指定になります。 064 * 初期値は、互換性を持つため、System.getProperty("file.encoding") ですが、 065 * 明示的に UTF-8 などを指定して統一しておいたほうが良いでしょう。 066 * 067 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト 068 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを 069 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し 070 * できれば、使用可能です。 071 * 072 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 073 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 074 * 繋げてください。 075 * 076 * Process_GrepChangeExcel -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8 077 * 078 * -keywordFile=キーワード :置換する語句を含むキーと値のペアー(タブ区切り) 079 * [-ignoreCase=大文字小文字 ] :検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する]) 080 * [-isChange=置換可否 ] :置換処理を実施する(true)かどうか(初期値:true[置換する]) 081 * [-inEncode=入力エンコード ] :keywordFileのエンコード 082 * [-display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 083 * [-debug=[false/true] ] :デバッグ用に実行内容を表示するかどうかを指定(初期値:false[表示しない]) 084 * 085 * @og.rev 5.5.1.7 (2012/04/16) 新規追加 086 * @og.rev 6.0.2.0 (2014/08/29) fukurou.model.ExcelModel を使用するように変更 087 * @version 4.0 088 * @author Kazuhiko Hasegawa 089 * @since JDK5.0, 090 */ 091public class Process_GrepChangeExcel extends AbstractProcess implements ChainProcess { 092 private String[] keyword = null; 093 private String[] change = null; 094 private boolean ignoreCase = false; 095 private boolean isChange = true; // 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする 096 private boolean display = false; // 表示しない 097 private boolean debug = false; // 表示しない 098 099 private int inCount = 0; 100 private int findCount = 0; 101 private int cngCount = 0; 102 103 private static final Map<String,String> mustProparty ; // [プロパティ]必須チェック用 Map 104 private static final Map<String,String> usableProparty ; // [プロパティ]整合性チェック Map 105 106 static { 107 mustProparty = new LinkedHashMap<String,String>(); 108 mustProparty.put( "keywordFile", "置換する語句を含むキーと値のペアー(タブ区切り)(必須)" ); 109 110 usableProparty = new LinkedHashMap<String,String>(); 111 usableProparty.put( "ignoreCase", "検索時に大文字小文字を区別しない(true)かどうか。" + 112 CR + "(初期値:区別する[false])" ); 113 usableProparty.put( "isChange", "置換処理を実施する(true)かどうか" + 114 CR + "(初期値:置換する[true])" ); 115 usableProparty.put( "inEncode", "keywordFileのエンコード" ); 116 usableProparty.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 117 CR + "(初期値:false:表示しない)" ); 118 usableProparty.put( "debug", "デバッグ用に実行内容を表示するかどうかを指定" + 119 CR + "(初期値:false:表示しない)" ); 120 } 121 122 /** 123 * デフォルトコンストラクター。 124 * このクラスは、動的作成されます。デフォルトコンストラクターで、 125 * super クラスに対して、必要な初期化を行っておきます。 126 * 127 */ 128 public Process_GrepChangeExcel() { 129 super( "org.opengion.fukurou.process.Process_GrepChangeExcel",mustProparty,usableProparty ); 130 } 131 132 /** 133 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 134 * 初期処理(ファイルオープン、DBオープン等)に使用します。 135 * 136 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 137 */ 138 public void init( final ParamProcess paramProcess ) { 139 Argument arg = getArgument(); 140 141 String keywordFile = arg.getProparty("keywordFile" ); 142 ignoreCase = arg.getProparty("ignoreCase",ignoreCase); 143 isChange = arg.getProparty("isChange",isChange); // 5.1.2.0 (2010/01/01) 144 String inEncode = arg.getProparty("inEncode",System.getProperty("file.encoding")); 145 display = arg.getProparty("display",display); 146 debug = arg.getProparty("debug",debug); 147 148 FileString fs = new FileString(); 149 fs.setFilename( keywordFile ); 150 fs.setEncode( inEncode ); 151 String[] lines = fs.getValue( "\n" ); 152 int len = lines.length; 153 if( len == 0 ) { 154 String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ; 155 throw new RuntimeException( errMsg ); 156 } 157 158 println( "keywordFile を、" + len + "件読み取りました。" ); 159 List<String> keyList = new ArrayList<String>( len ); 160 List<String> cngList = new ArrayList<String>( len ); 161 162 for( int i=0; i<len; i++ ) { 163 // String line = lines[i].trim(); 164 String line = lines[i]; 165 int indx = line.indexOf( '\t' ); 166 if( indx <= 0 ) { continue ; } // TAB が先頭や、存在しない行は読み飛ばす。 167 keyList.add( line.substring( 0,indx ).trim() ); 168 String cng = line.substring( indx+1 ).trim(); 169 cng = StringUtil.replace( cng,"\\n",CR ); 170 cng = StringUtil.replace( cng,"\\t","\t" ); 171 cngList.add( cng ); 172 } 173 keyword = keyList.toArray( new String[keyList.size()] ); 174 change = cngList.toArray( new String[cngList.size()] ); 175 } 176 177 /** 178 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 179 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 180 * 181 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 182 */ 183 public void end( final boolean isOK ) { 184 // ここでは処理を行いません。 185 } 186 187 /** 188 * 引数の LineModel を処理するメソッドです。 189 * 変換処理後の LineModel を返します。 190 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 191 * null データを返します。つまり、null データは、後続処理を行わない 192 * フラグの代わりにも使用しています。 193 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 194 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 195 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 196 * 各処理ごとに自分でコピー(クローン)して下さい。 197 * 198 * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 199 * @og.rev 6.0.2.0 (2014/08/29) org.opengion.fukurou.model.ExcelModel を使うように変更。 200 * 201 * @param data オリジナルのLineModel 202 * 203 * @return 処理変換後のLineModel 204 */ 205 public LineModel action( final LineModel data ) { 206 inCount++ ; 207 final FileLineModel fileData ; 208 if( data instanceof FileLineModel ) { 209 fileData = (FileLineModel)data ; 210 } 211 else { 212 String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ; 213 throw new RuntimeException( errMsg ); 214 } 215 216 File org = fileData.getFile() ; 217 if( ! org.isFile() ) { return data; } 218 219 boolean nextFlag = false; 220 String orgFilename = org.getAbsolutePath(); // 6.0.2.0 (2014/08/29) 221 ExcelModel excel = new ExcelModel( orgFilename ); // 6.0.2.0 (2014/08/29) 222 223 int maxStNo = excel.getNumberOfSheets(); // 6.0.2.0 (2014/08/29) 224 for( int stNo=0; stNo<maxStNo; stNo++ ) { 225 String sheetName = excel.getSheetName( stNo ); // ここで、ExcelModel内部に処理Sheetをセットします。 226 if( display ) { println( orgFilename + ":" + sheetName ); } 227 int nFirstRow = excel.getFirstRowNum(); 228 int nLastRow = excel.getLastRowNum(); 229 for( int rowNo = nFirstRow; rowNo <= nLastRow; rowNo++ ) { 230 String[] vals = excel.getValues( rowNo ); 231 if( vals == null || vals.length == 0 ) { continue; } 232 for( int colNo=0; colNo<vals.length; colNo++ ) { 233 String orgText = vals[colNo]; 234 if( orgText != null && orgText.length() > 0 ) { 235 if( debug ) { println( "\tDATA: [" + rowNo + "," + colNo + "]=" + orgText ); } 236 String strText = changeString( orgText ); // 文字列変換。無変換の場合は、null が返る。 237 if( strText != null ) { 238 if( display ) { println( "\tFIND: [" + rowNo + "," + colNo + "]=" + orgText + "→" + strText ); } 239 excel.setCellValue( strText,colNo ); // 書き戻し。行は先ほど getValues(int) した行 240 nextFlag = true; 241 findCount++; // 5.5.2.4 (2012/05/16) 242 } 243 } 244 } 245 } 246 247 // シート名も変換対象とする。 248 String newSheetName = changeString( sheetName ); // 無変換の場合は、null が返る。 249 if( newSheetName != null ) { 250 if( display ) { println( "\tFIND sheetName=" + sheetName + "→" + newSheetName ); } 251 excel.setSheetName( stNo,newSheetName ); 252 nextFlag = true; 253 findCount++; // 5.5.2.4 (2012/05/16) 254 } 255 } 256 257 if( isChange && nextFlag ) { 258 excel.saveFile( orgFilename ) ; 259 cngCount = findCount ; // 5.5.2.4 (2012/05/16) 置換時には、findCount を、cngCount にセットしておく。 260 } 261 262// FileInputStream in = null; 263// Workbook wb = null; 264// Sheet sheet = null; 265// int stNo = -1 , rowNo = -1 , cellNo = -1 ; // エラー発生時に場所を特定する為の情報 266// String sheetName = null; // エラー発生時に場所を特定する為の情報 267// try { 268// in = new FileInputStream(org); 269// wb = WorkbookFactory.create(in); // HSSFとXSSFの違いをPOIが吸収してくれる 270// 271// for( stNo=0; stNo<wb.getNumberOfSheets(); stNo++ ) { 272// sheet = wb.getSheetAt(stNo); 273// sheetName = sheet.getSheetName(); 274// if( display ) { println( org.getPath() + ":" + sheetName ); } 275// 276// int nFirstRow = sheet.getFirstRowNum(); 277// int nLastRow = sheet.getLastRowNum(); 278// for( rowNo = nFirstRow; rowNo <= nLastRow; rowNo++) { 279// Row oRow = sheet.getRow(rowNo); 280// if( oRow == null ) { continue; } 281// int nFirstCell = oRow.getFirstCellNum(); 282// int nLastCell = oRow.getLastCellNum(); 283// for( cellNo = nFirstCell; cellNo <= nLastCell; cellNo++) { 284// Cell oCell = oRow.getCell( cellNo ); 285// if( oCell != null ) { 286// int nCellType = oCell.getCellType(); 287// if( nCellType == Cell.CELL_TYPE_STRING ) { 288// RichTextString richText = oCell.getRichStringCellValue(); 289// if( richText != null ) { 290// String orgText = richText.getString(); 291// if( debug ) { println( "DEBUG: [" + rowNo + "," + cellNo + "]=" + orgText ); } 292// 293// String strText = changeString( orgText ); // 文字列変換。無変換の場合は、null が返る。 294// if( strText != null ) { 295// if( display ) { println( "CHANGE: [" + rowNo + "," + cellNo + "]=" + orgText + "→" + strText ); } 296// oCell.setCellValue( strText ); // Cell に書き戻し(RichTextStringでないが大丈夫?) 297// nextFlag = true; 298// findCount++; // 5.5.2.4 (2012/05/16) 299// } 300// } 301// } 302// } 303// } 304// } 305// 306// // シート名も変換対象とする。 307// String newSheetName = changeString( sheetName ); // 無変換の場合は、null が返る。 308// if( newSheetName != null ) { 309// if( display ) { println( " sheetName=" + sheetName + "→" + newSheetName ); } 310// wb.setSheetName(stNo, newSheetName); 311// nextFlag = true; 312// findCount++; // 5.5.2.4 (2012/05/16) 313// } 314// } 315// } 316// catch ( IOException ex ) { 317// String errMsg = "処理中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR 318// + org.toString() + CR 319// + "Sheet=[" + sheetName + "],SheetNo=[" + stNo + "],rowNo=[" + rowNo + "],cellNo=[" + cellNo + "]" + CR 320// + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 321// throw new RuntimeException( errMsg,ex ); 322// } 323// catch ( InvalidFormatException ex ) { 324// String errMsg = "読み込みファイルの形式エラーが発生しました。[" + data.getRowNo() + "]件目" + CR 325// + org.toString() + CR 326// + "Sheet=[" + sheetName + "],SheetNo=[" + stNo + "],rowNo=[" + rowNo + "],cellNo=[" + cellNo + "]" + CR 327// + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 328// throw new RuntimeException( errMsg,ex ); 329// } 330// finally { 331// Closer.ioClose( in ); 332// } 333// 334// if( isChange && nextFlag ) { 335// FileOutputStream fileOut = null ; 336// try { 337// fileOut = new FileOutputStream( org ); 338// wb.write(fileOut); 339// cngCount = findCount ; // 5.5.2.4 (2012/05/16) 置換時には、findCount を、cngCount にセットしておく。 340// } 341// catch( IOException ex ) { 342// String errMsg = "ファイルへ書込み中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR 343// + org.toString() + CR 344// + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 345// throw new RuntimeException( errMsg,ex ); 346// } 347// finally { 348// Closer.ioClose( fileOut ); 349// } 350// } 351 352 return nextFlag ? data : null ; 353 } 354 355 /** 356 * 引数の文字列から、keyword ファイルを元に文字列変換を行います。 357 * 358 * ここでは、変換が行われたかどうかを判定するため、変換された場合 359 * のみ、値を返します。変換されない場合は、null を返しますので、 360 * ご注意ください。 361 * 362 * @param org 変換前の文字列 363 * 364 * @return 変換後の文字列(変換がなければ、null を返します。) 365 */ 366 public String changeString( final String org ) { 367 if( org == null || org.isEmpty() ) { return null; } 368 369 String tgt = org; 370 for( int i=0; i<keyword.length; i++ ) { 371 tgt = tgt.replaceAll( keyword[i],change[i] ); 372 } 373 374 // 元と同じ場合は、null を返します。 375 if( org.equals( tgt ) || (ignoreCase && org.equalsIgnoreCase( tgt )) ) { 376 tgt = null; 377 } 378 379 return tgt ; 380 } 381 382 /** 383 * プロセスの処理結果のレポート表現を返します。 384 * 処理プログラム名、入力件数、出力件数などの情報です。 385 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 386 * 形式で出してください。 387 * 388 * @return 処理結果のレポート 389 */ 390 public String report() { 391 String report = "[" + getClass().getName() + "]" + CR 392 + TAB + "Search File Count : " + inCount + CR 393 + TAB + "Key Find Count : " + findCount + CR 394 + TAB + "Key Change Count : " + cngCount ; 395 396 return report ; 397 } 398 399 /** 400 * このクラスの使用方法を返します。 401 * 402 * @return このクラスの使用方法 403 */ 404 public String usage() { 405 StringBuilder buf = new StringBuilder(); 406 407 buf.append( "Process_GrepChangeExcel は、上流から受け取った FileLineModelから、語句を" ).append( CR ); 408 buf.append( "置換する、ChainProcess インターフェースの実装クラスです。" ).append( CR ); 409 buf.append( "Process_GrepChange との違いは、入力元のファイルが、テキストファイルなのか、" ).append( CR ); 410 buf.append( "ネイティブEXCELファイルなのかの違いです。" ).append( CR ); 411 buf.append( CR ); 412 buf.append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、" ).append( CR ); 413 buf.append( "対象とする語句を置換します。" ).append( CR ); 414 buf.append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、" ).append( CR ); 415 buf.append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。" ).append( CR ); 416 buf.append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース" ).append( CR ); 417 buf.append( "が前後に存在している場合は、ご注意ください。" ).append( CR ); 418 buf.append( "置換文字(値)は、\t と \n の特殊文字が使用できます。" ).append( CR ); 419 buf.append( "この GrepChangeExcel では、語句に、正規表現は使用できません。正規表現のキーワード" ).append( CR ); 420 buf.append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。" ).append( CR ); 421 buf.append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、" ).append( CR ); 422 buf.append( "置き換えた結果も、同じファイルにセーブします。" ).append( CR ); 423 buf.append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。" ).append( CR ); 424 buf.append( "-inEncode は、keywordFileのエンコード指定になります。" ).append( CR ); 425 buf.append( "初期値は、互換性を持つため、System.getProperty(\"file.encoding\") ですが、" ).append( CR ); 426 buf.append( "明示的に UTF-8 などを指定して統一しておいたほうが良いでしょう。" ).append( CR ); 427 buf.append( CR ); 428 buf.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト" ).append( CR ); 429 buf.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを" ).append( CR ); 430 buf.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し" ).append( CR ); 431 buf.append( "できれば、使用可能です。" ).append( CR ); 432 buf.append( CR ); 433 buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ); 434 buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ); 435 buf.append( "繋げてください。" ).append( CR ); 436 buf.append( CR ).append( CR ); 437 438 buf.append( getArgument().usage() ).append( CR ); 439 440 return buf.toString(); 441 } 442 443 /** 444 * このクラスは、main メソッドから実行できません。 445 * 446 * @param args コマンド引数配列 447 */ 448 public static void main( final String[] args ) { 449 LogWriter.log( new Process_GrepChangeExcel().usage() ); 450 } 451}