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