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