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 java.util.Map ; 019import java.util.LinkedHashMap ; 020import java.util.List ; 021import java.util.ArrayList ; 022import java.io.File; 023 024import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 025import org.opengion.fukurou.system.LogWriter; 026import org.opengion.fukurou.util.Argument; 027import org.opengion.fukurou.util.StringUtil ; 028import org.opengion.fukurou.util.FileUtil; // 6.4.5.2 (2016/05/06) 029import org.opengion.fukurou.model.ExcelModel; // 6.0.2.0 (2014/09/19) 030 031/** 032 * Process_GrepChangeExcel は、上流から受け取った FileLineModelから、語句を 033 * 置換する、ChainProcess インターフェースの実装クラスです。 034 * 035 * Process_GrepChange との違いは、入力元のファイルが、テキストファイルなのか、 036 * ネイティブEXCELファイルなのかの違いです。 037 * 038 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、 039 * 対象とする語句をセル単位に置換します。 040 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、 041 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。 042 * ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース 043 * が前後に存在している場合は、ご注意ください。 044 * 置換文字(値)は、\t と \n の特殊文字が使用できます。 045 * この GrepChangeExcel では、語句に、正規表現は使用できません。正規表現のキーワード 046 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。 047 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、 048 * 置き換えた結果も、同じファイルにセーブします。 049 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。 050 * -inEncode は、keywordFileのエンコード指定になります。 051 * 初期値は、互換性を持つため、System.getProperty("file.encoding") ですが、 052 * 明示的に UTF-8 などを指定して統一しておいたほうが良いでしょう。 053 * 054 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト 055 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを 056 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し 057 * できれば、使用可能です。 058 * 059 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 060 * 引数文字列の 『=』 の前後には、スペースは挟めません。必ず、-key=value の様に 061 * 繋げてください。 062 * 063 * Process_GrepChangeExcel -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8 064 * 065 * -keywordFile=キーワード :置換する語句を含むキーと値のペアー(タブ区切り) 066 * [-ignoreCase=大文字小文字 ] :検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する]) 067 * [-isChange=置換可否 ] :置換処理を実施する(true)かどうか(初期値:true[置換する]) 068 * [-inEncode=入力エンコード ] :keywordFileのエンコード 069 * [-display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 070 * [-debug=[false/true] ] :デバッグ用に実行内容を表示するかどうかを指定(初期値:false[表示しない]) 071 * 072 * @og.rev 5.5.1.7 (2012/04/16) 新規追加 073 * @og.rev 6.0.2.0 (2014/09/19) fukurou.model.ExcelModel を使用するように変更 074 * @version 4.0 075 * @author Kazuhiko Hasegawa 076 * @since JDK5.0, 077 */ 078public class Process_GrepChangeExcel extends AbstractProcess implements ChainProcess { 079 private String[] keyword ; 080 private String[] change ; 081 private boolean ignoreCase ; 082 private boolean isChange = true; // 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする 083 private boolean display ; // false:表示しない 084 private boolean debug ; // false:表示しない 085 086 private int inCount ; 087 private int findCount ; 088 private int cngCount ; 089 090 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 091 private static final Map<String,String> MUST_PROPARTY ; // [プロパティ]必須チェック用 Map 092 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 093 private static final Map<String,String> USABLE_PROPARTY ; // [プロパティ]整合性チェック Map 094 095 static { 096 MUST_PROPARTY = new LinkedHashMap<>(); 097 MUST_PROPARTY.put( "keywordFile", "置換する語句を含むキーと値のペアー(タブ区切り)(必須)" ); 098 099 USABLE_PROPARTY = new LinkedHashMap<>(); 100 USABLE_PROPARTY.put( "ignoreCase", "検索時に大文字小文字を区別しない(true)かどうか。" + 101 CR + "(初期値:区別する[false])" ); 102 USABLE_PROPARTY.put( "isChange", "置換処理を実施する(true)かどうか" + 103 CR + "(初期値:置換する[true])" ); 104 USABLE_PROPARTY.put( "inEncode", "keywordFileのエンコード" ); 105 USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 106 CR + "(初期値:false:表示しない)" ); 107 USABLE_PROPARTY.put( "debug", "デバッグ用に実行内容を表示するかどうかを指定" + 108 CR + "(初期値:false:表示しない)" ); 109 } 110 111 /** 112 * デフォルトコンストラクター。 113 * このクラスは、動的作成されます。デフォルトコンストラクターで、 114 * super クラスに対して、必要な初期化を行っておきます。 115 * 116 */ 117 public Process_GrepChangeExcel() { 118 super( "org.opengion.fukurou.process.Process_GrepChangeExcel",MUST_PROPARTY,USABLE_PROPARTY ); 119 } 120 121 /** 122 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 123 * 初期処理(ファイルオープン、DBオープン等)に使用します。 124 * 125 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 126 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 127 * 128 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 129 */ 130 public void init( final ParamProcess paramProcess ) { 131 final Argument arg = getArgument(); 132 133 final String keywordFile = arg.getProparty("keywordFile" ); 134 ignoreCase = arg.getProparty("ignoreCase",ignoreCase); 135 isChange = arg.getProparty("isChange",isChange); // 5.1.2.0 (2010/01/01) 136 final String inEncode = arg.getProparty("inEncode",System.getProperty("file.encoding")); 137 display = arg.getProparty("display",display); 138 debug = arg.getProparty("debug",debug); 139 140 // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 141 final List<String> list = FileUtil.getLineList( keywordFile , inEncode ); // 6.4.5.2 (2016/05/06) 142 final int len = list.size(); // 6.4.5.2 (2016/05/06) 143 if( len == 0 ) { 144 final String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ; 145 throw new OgRuntimeException( errMsg ); 146 } 147 148 println( "keywordFile を、" + len + "件読み取りました。" ); 149 final List<String> keyList = new ArrayList<>( len ); 150 final List<String> cngList = new ArrayList<>( len ); 151 152 for( final String line : list ) { 153 // String line = lines[i].trim(); 154 final int indx = line.indexOf( '\t' ); 155 if( indx <= 0 ) { continue ; } // TAB が先頭や、存在しない行は読み飛ばす。 156 keyList.add( line.substring( 0,indx ).trim() ); 157 String cng = line.substring( indx+1 ).trim(); 158 cng = StringUtil.replace( cng,"\\n",CR ); 159 cng = StringUtil.replace( cng,"\\t","\t" ); 160 cngList.add( cng ); 161 } 162// keyword = keyList.toArray( new String[keyList.size()] ); 163// change = cngList.toArray( new String[cngList.size()] ); 164 keyword = keyList.toArray( new String[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 165 change = cngList.toArray( new String[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 166 } 167 168 /** 169 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 170 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 171 * 172 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 173 */ 174 public void end( final boolean isOK ) { 175 // ここでは処理を行いません。 176 } 177 178 /** 179 * 引数の LineModel を処理するメソッドです。 180 * 変換処理後の LineModel を返します。 181 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 182 * null データを返します。つまり、null データは、後続処理を行わない 183 * フラグの代わりにも使用しています。 184 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 185 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 186 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 187 * 各処理ごとに自分でコピー(クローン)して下さい。 188 * 189 * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 190 * @og.rev 6.0.2.0 (2014/09/19) org.opengion.fukurou.model.ExcelModel を使うように変更。 191 * @og.rev 6.3.9.0 (2015/11/06) ExcelModel#getValues(int) では、nullは返さない。 192 * 193 * @param data オリジナルのLineModel 194 * 195 * @return 処理変換後のLineModel 196 */ 197 @Override // ChainProcess 198 public LineModel action( final LineModel data ) { 199 inCount++ ; 200 final FileLineModel fileData ; 201 if( data instanceof FileLineModel ) { 202 fileData = (FileLineModel)data ; 203 } 204 else { 205 final String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ; 206 throw new OgRuntimeException( errMsg ); 207 } 208 209 final File org = fileData.getFile() ; 210 if( ! org.isFile() ) { return data; } 211 212 boolean nextFlag = false; 213 final File orgFile = org.getAbsoluteFile(); // 6.2.0.0 (2015/02/27) 214 final ExcelModel excel = new ExcelModel( orgFile ); // 6.2.0.0 (2015/02/27) 215 216 final int maxStNo = excel.getNumberOfSheets(); // 6.0.2.0 (2014/09/19) 217 for( int stNo=0; stNo<maxStNo; stNo++ ) { 218 final String sheetName = excel.getSheetName( stNo ); // ここで、ExcelModel内部に処理Sheetをセットします。 219 if( display ) { println( orgFile + ":" + sheetName ); } 220 final int nFirstRow = excel.getFirstRowNum(); 221 final int nLastRow = excel.getLastRowNum(); 222 for( int rowNo=nFirstRow; rowNo<=nLastRow; rowNo++ ) { 223 final String[] vals = excel.getValues( rowNo ); 224 // 6.3.9.0 (2015/11/06) ExcelModel#getValues(int) では、nullは返さない。 225 for( int colNo=0; colNo<vals.length; colNo++ ) { 226 final String orgText = vals[colNo]; 227 if( orgText != null && orgText.length() > 0 ) { 228 if( debug ) { println( "\tDATA: [" + rowNo + "," + colNo + "]=" + orgText ); } 229 final String strText = changeString( orgText ); // 文字列変換。無変換の場合は、null が返る。 230 if( strText != null ) { 231 if( display ) { println( "\tFIND: [" + rowNo + "," + colNo + "]=" + orgText + "→" + strText ); } 232 excel.setCellValue( strText,colNo ); // 書き戻し。行は先ほど getValues(int) した行 233 nextFlag = true; 234 findCount++; // 5.5.2.4 (2012/05/16) 235 } 236 } 237 } 238 } 239 240 // シート名も変換対象とする。 241 final String newSheetName = changeString( sheetName ); // 無変換の場合は、null が返る。 242 if( newSheetName != null ) { 243 if( display ) { println( "\tFIND sheetName=" + sheetName + "→" + newSheetName ); } 244 excel.setSheetName( stNo,newSheetName ); 245 nextFlag = true; 246 findCount++; // 5.5.2.4 (2012/05/16) 247 } 248 } 249 250 if( isChange && nextFlag ) { 251 excel.saveFile( orgFile ) ; // 6.2.0.0 (2015/02/27) 252 cngCount = findCount ; // 5.5.2.4 (2012/05/16) 置換時には、findCount を、cngCount にセットしておく。 253 } 254 255 return nextFlag ? data : null ; 256 } 257 258 /** 259 * 引数の文字列から、keyword ファイルを元に文字列変換を行います。 260 * 261 * ここでは、変換が行われたかどうかを判定するため、変換された場合 262 * のみ、値を返します。変換されない場合は、null を返しますので、 263 * ご注意ください。 264 * 265 * @param org 変換前の文字列 266 * 267 * @return 変換後の文字列(変換がなければ、null を返します。) 268 */ 269 public String changeString( final String org ) { 270 if( org == null || org.isEmpty() ) { return null; } 271 272 String tgt = org; 273 for( int i=0; i<keyword.length; i++ ) { 274 tgt = tgt.replaceAll( keyword[i],change[i] ); 275 } 276 277 // 元と同じ場合は、null を返します。 278 if( ignoreCase && org.equalsIgnoreCase( tgt ) || org.equals( tgt ) ) { // 6.9.7.0 (2018/05/14) PMD Useless parentheses. 279 tgt = null; 280 } 281 282 return tgt ; 283 } 284 285 /** 286 * プロセスの処理結果のレポート表現を返します。 287 * 処理プログラム名、入力件数、出力件数などの情報です。 288 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 289 * 形式で出してください。 290 * 291 * @return 処理結果のレポート 292 */ 293 public String report() { 294 // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX' 295 return "[" + getClass().getName() + "]" + CR 296// final String report = "[" + getClass().getName() + "]" + CR 297 + TAB + "Search File Count : " + inCount + CR 298 + TAB + "Key Find Count : " + findCount + CR 299 + TAB + "Key Change Count : " + cngCount ; 300 301// return report ; 302 } 303 304 /** 305 * このクラスの使用方法を返します。 306 * 307 * @return このクラスの使用方法 308 * @og.rtnNotNull 309 */ 310 public String usage() { 311 final StringBuilder buf = new StringBuilder( 1200 ) 312 .append( "Process_GrepChangeExcel は、上流から受け取った FileLineModelから、語句を" ).append( CR ) 313 .append( "置換する、ChainProcess インターフェースの実装クラスです。" ).append( CR ) 314 .append( "Process_GrepChange との違いは、入力元のファイルが、テキストファイルなのか、" ).append( CR ) 315 .append( "ネイティブEXCELファイルなのかの違いです。" ).append( CR ) 316 .append( CR ) 317 .append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、" ).append( CR ) 318 .append( "対象とする語句を置換します。" ).append( CR ) 319 .append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、" ).append( CR ) 320 .append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。" ).append( CR ) 321 .append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース" ).append( CR ) 322 .append( "が前後に存在している場合は、ご注意ください。" ).append( CR ) 323 .append( "置換文字(値)は、\t と \n の特殊文字が使用できます。" ).append( CR ) 324 .append( "この GrepChangeExcel では、語句に、正規表現は使用できません。" ).append( CR ) 325 .append( "正規表現のキーワードや文字列を複数行の文字列と置き換える場合は、Process_Grep" ).append( CR ) 326 .append( "を使用して下さい。" ).append( CR ) 327 .append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、" ).append( CR ) 328 .append( "置き換えた結果も、同じファイルにセーブします。" ).append( CR ) 329 .append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。" ).append( CR ) 330 .append( "-inEncode は、keywordFileのエンコード指定になります。" ).append( CR ) 331 .append( "初期値は、互換性を持つため、System.getProperty(\"file.encoding\") ですが、" ).append( CR ) 332 .append( "明示的に UTF-8 などを指定して統一しておいたほうが良いでしょう。" ).append( CR ) 333 .append( CR ) 334// .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト" ).append( CR ) 335// .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを" ).append( CR ) 336// .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し" ).append( CR ) 337// .append( "できれば、使用可能です。" ).append( CR ) 338 .append( CHAIN_FILE_USAGE ) // 8.5.6.1 (2024/03/29) 継承元使用 339 .append( CR ) 340// .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 341// .append( "引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 342// .append( "繋げてください。" ).append( CR ) 343 .append( PROCESS_PARAM_USAGE ) // 8.5.6.1 (2024/03/29) 継承元使用 344 .append( CR ).append( CR ) 345 .append( getArgument().usage() ).append( CR ); 346 347 return buf.toString(); 348 } 349 350 /** 351 * このクラスは、main メソッドから実行できません。 352 * 353 * @param args コマンド引数配列 354 */ 355 public static void main( final String[] args ) { 356 LogWriter.log( new Process_GrepChangeExcel().usage() ); 357 } 358}