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     */
016    package org.opengion.fukurou.process;
017    
018    import org.opengion.fukurou.util.Argument;
019    import org.opengion.fukurou.util.FileString;
020    import org.opengion.fukurou.util.Closer ;
021    import org.opengion.fukurou.util.StringUtil ;
022    import org.opengion.fukurou.util.LogWriter;
023    
024    import org.apache.poi.ss.usermodel.Cell;
025    import org.apache.poi.ss.usermodel.RichTextString;
026    import org.apache.poi.ss.usermodel.Row;
027    import org.apache.poi.ss.usermodel.Sheet;
028    import org.apache.poi.ss.usermodel.Workbook;
029    import org.apache.poi.ss.usermodel.WorkbookFactory;
030    import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
031    
032    import java.util.Map ;
033    import java.util.LinkedHashMap ;
034    import java.util.List ;
035    import java.util.ArrayList ;
036    
037    import java.io.File;
038    import java.io.FileInputStream;
039    import java.io.FileOutputStream;
040    import 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     */
088    public 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             * 初期処?ファイルオープン??オープン?に使用します?
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    
146                    FileString fs = new FileString();
147                    fs.setFilename( keywordFile );
148                    fs.setEncode( inEncode );
149                    String[] lines = fs.getValue( "\n" );
150                    int len = lines.length;
151                    if( len == 0 ) {
152                            String errMsg = "keywordFile の??読み取れませんでした?" + keywordFile + "]" ;
153                            throw new RuntimeException( errMsg );
154                    }
155    
156                    println( "keywordFile を?" + len + "件読み取りました? );
157                    List<String> keyList = new ArrayList<String>( len );
158                    List<String> cngList = new ArrayList<String>( len );
159    
160                    for( int i=0; i<len; i++ ) {
161            //              String line = lines[i].trim();
162                            String line = lines[i];
163                            int indx = line.indexOf( '\t' );
164                            if( indx <= 0 ) { continue ; }       // TAB が?頭??存在しな??読み飛?す?
165                            keyList.add( line.substring( 0,indx ).trim() );
166                            String cng = line.substring( indx+1 ).trim();
167                            cng = StringUtil.replace( cng,"\\n",CR );
168                            cng = StringUtil.replace( cng,"\\t","\t" );
169                            cngList.add( cng );
170                    }
171                    keyword = keyList.toArray( new String[keyList.size()] );
172                    change  = cngList.toArray( new String[cngList.size()] );
173            }
174    
175            /**
176             * プロセスの終?行います??に??、呼び出されます?
177             * 終???ファイルクローズ??クローズ?に使用します?
178             *
179             * @param   isOK ト?タルで、OK?たかど?[true:成功/false:失敗]
180             */
181            public void end( final boolean isOK ) {
182                    // ここでは処?行いません?
183            }
184    
185            /**
186             * 引数の LineModel を??るメソ?です?
187             * 変換処?? LineModel を返します?
188             * 後続??行わな?????タのフィルタリングを行う場?は?
189             * null ??タを返します?つまり?null ??タは、後続??行わな?
190             * フラグの代わりにも使用して?す?
191             * なお?変換処?? LineModel と、オリジナルの LineModel が?
192             * 同?、コピ?(クローン)か?、各処?ソ??決めて?す?
193             * ドキュメントに明記されて???合?、副作用が問題になる?合??
194             * ???とに自?コピ?(クローン)して下さ??
195             *
196             * @param   data        オリジナルのLineModel
197             *
198             * @return      処?換後?LineModel
199             */
200            public LineModel action( final LineModel data ) {
201                    inCount++ ;
202                    final FileLineModel fileData ;
203                    if( data instanceof FileLineModel ) {
204                            fileData = (FileLineModel)data ;
205                    }
206                    else {
207                            String errMsg = "??タ?FileLineModel オブジェクトではありません? + CR ;
208                            throw new RuntimeException( errMsg );
209                    }
210    
211                    File org = fileData.getFile() ;
212                    if( ! org.isFile() ) { return data; }
213    
214                    boolean nextFlag  = false;
215    
216                    FileInputStream in        = null;
217                    Workbook                wb        = null;
218                    Sheet                   sheet = null;
219                    int  stNo = -1 , rowNo = -1 , cellNo = -1 ;     // エラー発生時に場?特定する為の??
220                    String sheetName = null;                // エラー発生時に場?特定する為の??
221                    try {
222                            in = new FileInputStream(org);
223                            wb = WorkbookFactory.create(in);        // HSSFとXSSFの違いをPOIが吸収してくれ?
224    
225                            for( stNo=0; stNo<wb.getNumberOfSheets(); stNo++ ) {
226                                    sheet = wb.getSheetAt(stNo);
227                                    sheetName = sheet.getSheetName();
228                                    if( display ) { println( org.getPath() + ":" + sheetName ); }
229    
230                                    int nFirstRow = sheet.getFirstRowNum();
231                                    int nLastRow  = sheet.getLastRowNum();
232                                    for( rowNo = nFirstRow; rowNo <= nLastRow; rowNo++) {
233                                            Row oRow = sheet.getRow(rowNo);
234                                            if( oRow == null ) { continue; }
235                                            int nFirstCell = oRow.getFirstCellNum();
236                                            int nLastCell  = oRow.getLastCellNum();
237                                            for( cellNo = nFirstCell; cellNo <= nLastCell; cellNo++) {
238                                                    Cell oCell = oRow.getCell( cellNo );
239                                                    if( oCell != null ) {
240                                                            int nCellType = oCell.getCellType();
241    //                                                      switch(nCellType) {
242    //                                                              case Cell.CELL_TYPE_STRING:
243                                                            if( nCellType == Cell.CELL_TYPE_STRING ) {
244                                                                            RichTextString richText = oCell.getRichStringCellValue();
245                                                                            if( richText != null ) {
246                                                                                    String orgText = richText.getString();
247                                                                                    if( debug ) { println( "DEBUG:  [" + rowNo + "," + cellNo + "]=" + orgText ); }
248    
249                                                                                    String strText = changeString( orgText );               // ??変換。無変換の場合?、null が返る?
250                                                                                    if( strText != null ) {
251                                                                                            if( display ) { println( "CHANGE: [" + rowNo + "," + cellNo + "]=" + orgText + "? + strText ); }
252                                                                                            oCell.setCellValue( strText );                          // Cell に書き戻?RichTextStringでな?大丈夫??
253                                                                                            nextFlag = true;
254                                                                                            findCount++;                    // 5.5.2.4 (2012/05/16)
255                                                                                    }
256                                                                            }
257    //                                                                      break;
258    //                                                              default :
259    //                                                                      break;
260                                                            }
261                                                    }
262                                            }
263                                    }
264    
265                                    // シート名も変換対象とする?
266                                    String newSheetName = changeString( sheetName );        // 無変換の場合?、null が返る?
267                                    if( newSheetName != null ) {
268                                            if( display ) { println( "  sheetName=" + sheetName + "? + newSheetName ); }
269                                            wb.setSheetName(stNo, newSheetName);
270                                            nextFlag = true;
271                                            findCount++;                    // 5.5.2.4 (2012/05/16)
272                                    }
273                            }
274                    }
275                    catch ( IOException ex ) {
276                            String errMsg = "処?にエラーが発生しました?" + data.getRowNo() + "]件目" + CR
277                                                    + org.toString() + CR
278                                                    + "Sheet=[" + sheetName + "],SheetNo=[" + stNo + "],rowNo=[" + rowNo + "],cellNo=[" + cellNo + "]" ;
279                            throw new RuntimeException( errMsg,ex );
280                    }
281                    catch ( InvalidFormatException ex ) {
282                            String errMsg = "読み込みファイルの形式エラーが発生しました?" + data.getRowNo() + "]件目" + CR
283                                                    + org.toString() + CR
284                                                    + "Sheet=[" + sheetName + "],SheetNo=[" + stNo + "],rowNo=[" + rowNo + "],cellNo=[" + cellNo + "]" ;
285                            throw new RuntimeException( errMsg,ex );
286                    }
287                    finally {
288                            Closer.ioClose( in );
289                    }
290    
291                    if( isChange && nextFlag ) {
292                            FileOutputStream fileOut = null ;
293                            try {
294                                    fileOut = new FileOutputStream( org );
295                                    wb.write(fileOut);
296                                    cngCount = findCount ;  // 5.5.2.4 (2012/05/16) 置換時には、findCount を?cngCount にセ?しておく?
297                            }
298                            catch( IOException ex ) {
299                                    String errMsg = "ファイルへ書込み中にエラーが発生しました?" + data.getRowNo() + "]件目" + CR
300                                                            + org.toString() ;
301                                    throw new RuntimeException( errMsg,ex );
302                            }
303                            finally {
304                                    Closer.ioClose( fileOut );
305                            }
306                    }
307    
308                    return (nextFlag) ? data : null ;
309            }
310    
311            /**
312             * 引数の??から、keyword ファイルを?に??変換を行います?
313             *
314             * ここでは、変換が行われたかど?を判定するため?変換された??
315             * のみ、?を返します?変換されな??合?、null を返します?で?
316             * ご注意く???
317             *
318             * @param       org     変換前???
319             *
320             * @return   変換後???(変換がなければ、null を返します?)
321             */
322            public String changeString( final String org ) {
323                    if( org == null || org.isEmpty() ) { return null; }
324    
325                    String tgt = org;
326                    for( int i=0; i<keyword.length; i++ ) {
327                            tgt = tgt.replaceAll( keyword[i],change[i] );
328                    }
329    
330                    // ?同じ場合?、null を返します?
331                    if( org.equals( tgt ) || (ignoreCase && org.equalsIgnoreCase( tgt )) ) {
332                            tgt = null;
333                    }
334    
335                    return tgt ;
336            }
337    
338            /**
339             * プロセスの処?果のレポ?ト表現を返します?
340             * 処??ログラ?、?力件数、?力件数などの??です?
341             * こ???をそのまま、標準?力に出すことで、結果レポ?トと出来るよ?
342             * 形式で出してください?
343             *
344             * @return   処?果のレポ??
345             */
346            public String report() {
347                    String report = "[" + getClass().getName() + "]" + CR
348                                    + TAB + "Search File Count : " + inCount    + CR
349                                    + TAB + "Key Find    Count : " + findCount  + CR
350                                    + TAB + "Key Change  Count : " + cngCount ;
351    
352                    return report ;
353            }
354    
355            /**
356             * こ?クラスの使用方法を返します?
357             *
358             * @return      こ?クラスの使用方?
359             */
360            public String usage() {
361                    StringBuilder buf = new StringBuilder();
362    
363                    buf.append( "Process_GrepChangeExcel は、上流から受け取っ?FileLineModelから、語句?                 ).append( CR );
364                    buf.append( "置換する?ChainProcess インターフェースの実?ラスです?"                                              ).append( CR );
365                    buf.append( "Process_GrepChange との違いは、?力?のファイルが??ストファイルなのか?"       ).append( CR );
366                    buf.append( "ネイ?ブEXCELファイルなのか?違いです?"                                                                                ).append( CR );
367                    buf.append( CR );
368                    buf.append( "keywordFile より、置換する語句を含?ーと値のペアー(タブ区?)を読取り?  ).append( CR );
369                    buf.append( "対象とする語句を置換します?"                                                                                                  ).append( CR );
370                    buf.append( "keywordFile に、タブが含まれな?や、?頭にタブが存在して?場合??               ).append( CR );
371                    buf.append( "そ?行を読み飛?します?また?区?タブ?何?存在しても構いません?                    ).append( CR );
372                    buf.append( "ただし?タブで区?た前(キー)と後ろ(値)は、trim() されます?で、スペ?ス"            ).append( CR );
373                    buf.append( "が前後に存在して?場合?、ご注意く???"                                                                        ).append( CR );
374                    buf.append( "置換文?値)は、\t と \n の特殊文字が使用できます?"                                                      ).append( CR );
375                    buf.append( "こ? GrepChangeExcel では、語句に、正規表現は使用できません。正規表現のキーワー?      ).append( CR );
376                    buf.append( "?字?を?行???と置き換える場合?、Process_Grep を使用して下さ??"   ).append( CR );
377                    buf.append( "こ?プログラ?は、上流から受け取っ?FileLineModel のファイルに対して?           ).append( CR );
378                    buf.append( "置き換えた結果も?同じファイルにセーブします?"                                                            ).append( CR );
379                    buf.append( "??ファイルを保存したい場合?、予めバックア??を取得しておいてください?      ).append( CR );
380                    buf.append( "-inEncode は、keywordFileのエンコード指定になります?"                                                  ).append( CR );
381                    buf.append( "初期値は、互換性を持つため、System.getProperty(\"file.encoding\") ですが?       ).append( CR );
382                    buf.append( "明示? UTF-8 などを指定して統?ておいたほ?良?しょ??"                             ).append( CR );
383                    buf.append( CR );
384                    buf.append( "上流?ロセスでは、Name 属?として、?File』を持ち、?は、Fileオブジェク?            ).append( CR );
385                    buf.append( "である、Process_FileSearch を使用するのが?便利です?それ以外?クラス?           ).append( CR );
386                    buf.append( "使用する場合でも?Name属?と、File オブジェクトを持つ LineModel を受け渡?  ).append( CR );
387                    buf.append( "できれば、使用可能です?"                                                                                                            ).append( CR );
388                    buf.append( CR );
389                    buf.append( "引数??中に空白を含??合?、ダブルコー??ション(\"\") で括って下さ??" ).append( CR );
390                    buf.append( "引数??の ?』?前後には、空白は挟めません。??key=value の様に"             ).append( CR );
391                    buf.append( "繋げてください?                                                                                                                              ).append( CR );
392                    buf.append( CR ).append( CR );
393    
394                    buf.append( getArgument().usage() ).append( CR );
395    
396                    return buf.toString();
397            }
398    
399            /**
400             * こ?クラスは、main メソ?から実行できません?
401             *
402             * @param       args    コマンド引数配?
403             */
404            public static void main( final String[] args ) {
405                    LogWriter.log( new Process_GrepChangeExcel().usage() );
406            }
407    }