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    //              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             * 終äº??ç�?ファイルクローズã€?¼¤?¢クローズç­?に使用しますã?
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    }