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.util; 017 018import java.io.BufferedReader; 019import java.io.PrintWriter; 020import java.io.File; 021import java.io.IOException; 022import java.util.List; // 6.3.1.1 (2015/07/10) 023import java.util.Arrays; // 6.3.1.1 (2015/07/10) 024import java.nio.charset.CharacterCodingException; // 6.3.1.0 (2015/06/28) 025import java.util.Locale; // 6.4.0.2 (2015/12/11) 026 027import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 028import org.opengion.fukurou.system.OgCharacterException ; // 6.5.0.1 (2016/10/21) 029import org.opengion.fukurou.system.Closer; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 030 031import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 032 033/** 034 * CommentLineParser.java は、ファイルを行単位に処理して、コメントを除去するクラスです。 035 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 036 * 037 * ブロックコメントの状態や、コメント除外の状態を管理しています。 038 * オブジェクト作成後、line( String ) メソッドに、ファイルから読み取った1行分の文字列を渡せば、 039 * コメントが除外された形で返されます。 040 * 041 * コメントが除去された行は、rTrim しますが、行の削除は行いません。 042 * これは、Grep等で、文字列を発見した場合に、ファイルの行番号がずれるのを防ぐためです。 043 * 逆に、Diff等で、複数のコメント行は、1行の空行にしたい場合や、空行自体をなくして 044 * 比較したい場合は、戻ってきた行が、空行かどうかで判定して呼び出し元で処理してください。 045 * 046 * 引数の行文字列が、null の場合は、null を返します。(読み取り行がなくなった場合) 047 * 048 * 文字列くくり指定 は、例えば、ラインコメント(//) が、文字列指定("//") や、"http://xxxx" などの 049 * プログラム本文で使用する場合のエスケープ処理になります。 050 * つまり、文字列くくり指定についても、IN-OUT があり、その範囲内は、コメント判定外になります。 051 * 052 * ※ 6.3.1.1 (2015/07/10) 053 * コメントセットを、add で、追加していく機能を用意します。 054 * 現状では、Java,ORACLE,HTML のコメントを意識せず処理したいので、すべてを 055 * 処理することを前提に考えておきます。 056 * 057 * ※ 6.4.0.2 (2015/12/11) 058 * 行コメントが先頭行のみだったのを修正します。 059 * og:comment タグを除外できるようにします。そのため、 060 * 終了タグに、OR 条件を加味する必要があるため、CommentSet クラスを見直します。 061 * 可変長配列を使うため、文字列くくり指定を前に持ってきます。 062 * 063 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 064 * @og.rev 6.3.1.1 (2015/07/10) 内部構造大幅変更 065 * @og.group ユーティリティ 066 * 067 * @version 6.0 068 * @author Kazuhiko Hasegawa 069 * @since JDK7.0, 070 */ 071public class CommentLineParser { 072 private final List<CommentSet> cmntSetList ; 073 074 /** 075 * 処理するコメントの種類を拡張子で指定するコンストラクターです。 076 * これは、ORACLE系のラインコメント(--)が、Java系の演算子(i--;など)と 077 * 判定されるため、ひとまとめに処理できません。 078 * ここで指定する拡張子に応じて、CommentSet を割り当てます。 079 * 080 * ・sql , tri , spc は、ORACLE系を使用。 081 * ・xml , htm , html , は、Java,C,JavaScript系 + HTML,XML系を使用。 082 * ・jsp は、 Java,C,JavaScript系 + HTML,XML系 + ORACLE系 + openGion JSP系 を使用。 083 * ・それ以外は、Java,C,JavaScript系を使用。 084 * css は、それ以外になりますが、//(ラインコメント)はありませんが、コメントアウトされます。 085 * 086 * @og.rev 6.4.0.2 (2015/12/11) sufix によるコメント処理方法の変更。 087 * @og.rev 6.4.1.0 (2016/01/09) comment="***"のコメント処理方法の追加。 088 * @og.rev 6.4.1.1 (2016/01/16) sufixを小文字化。 089 * @og.rev 6.8.1.7 (2017/10/13) COMMENT ON で始まる行(大文字限定)は、コメントとして扱う 090 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 091 * 092 * @param sufix 拡張子 093 */ 094 public CommentLineParser( final String sufix ) { 095 final String type = sufix == null ? "null" : sufix.toLowerCase( Locale.JAPAN ); 096 097 if( "sql , tri , spc".contains( type ) ) { 098 cmntSetList = Arrays.asList( 099 new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 100 , new CommentSet( "COMMENT ON" , null , (String)null ) // 大文字のみ除外する。 101 ); 102 } 103 else if( "xml , htm , html".contains( type ) ) { 104 cmntSetList = Arrays.asList( 105 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 106 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 107 ); 108 } 109 else if( "jsp".contains( type ) ) { 110 cmntSetList = Arrays.asList( 111 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 112 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 113 , new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 114 , new CommentSet( null , "<og:comment" , "/>" , "</og:comment>" ) // openGion JSP系 XML なので、このまま。 115 , new CommentSet( null , "comment=\"" , "\"" ) // openGion comment="***" 6.4.1.0 (2016/01/09) 116 ); 117 } 118 else { 119 cmntSetList = Arrays.asList( 120 // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 121// new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 122 new CommentSet( "//" , "/*" , "*/" ).useEsc() // Java,C,JavaScript系 123 ); 124 } 125 } 126 127 /** 128 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 129 * 行として存在しない場合は、null を返します。 130 * 131 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 132 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 133 * 134 * @param inLine 1行の文字列 135 * @return コメント削除後の1行の文字列 136 */ 137 public String line( final String inLine ) { 138 139 String outLine = inLine ; 140 for( final CommentSet cmntSet : cmntSetList ) { 141 outLine = line( outLine,cmntSet ); 142 } 143 return outLine ; 144 } 145 146 /** 147 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 148 * 行として存在しない場合は、null を返します。 149 * 150 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 151 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 152 * 153 * @param inLine 1行の文字列 154 * @param cmntSet コメントを管理するオブジェクト 155 * @return コメント削除後の1行の文字列 156 */ 157 private String line( final String inLine , final CommentSet cmntSet ) { 158 if( inLine == null ) { return null; } 159 160 final int size = inLine.length(); 161 162 final StringBuilder buf = new StringBuilder( size ); 163 164 for( int st=0; st<size; st++ ) { 165 final char ch = inLine.charAt(st); 166 167 if( !cmntSet.checkEsc( ch ) ) { // エスケープ文字でないなら、判定処理を進める 168 // ブロックコメント継続中か、先頭がブロックコメント 169 if( cmntSet.isBlockIn( inLine,st ) ) { 170 final int ed = cmntSet.blockOut( inLine,st ) ; // 終了を見つける 171 if( ed >= 0 ) { // 終了があれば、そこまで進める。 172 st = ed; 173 continue; // ブロックコメント脱出。再読み込み 174 } 175 break; // ブロックコメント継続中。次の行へ 176 } 177 178 // ラインコメント発見。次の行へ 179 if( cmntSet.isLineCmnt( inLine,st ) ) { break; } 180 } 181 182 // 通常の文字なので、追加する。 183 buf.append( ch ); 184 } 185 186 // rTrim() と同等の処理 187 int len = buf.length(); 188 while( 0 < len && buf.charAt(len-1) <= ' ' ) { 189 len--; 190 } 191 buf.setLength( len ); 192 193 return buf.toString() ; 194 } 195 196 /** 197 * コメントセットを管理する内部クラスです。 198 * 199 * コメントの種類を指定します。 200 * 201 * Java,C,JavaScript系、// , /* , */ 202 * HTML,XML系、 // , <!-- , --> 203 * ORACLE系 -- , /* , */ 204 * openGion JSP系 null , <og:comment , /> ,</og:comment> 205 * 206 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 207 * @og.rev 6.4.0.2 (2015/12/11) CommentSet の見直し。 208 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 209 */ 210 private static final class CommentSet { 211 private final String LINE_CMNT ; // ラインコメント 212 private final String BLOCK_CMNT1 ; // ブロックコメントの開始 213 private final String[] BLOCK_CMNT2 ; // ブロックコメントの終了 214 215 private static final char ESC_CHAR1 = '"'; ; // コメント判定除外("") 216 private static final char ESC_CHAR2 = '\'' ; // コメント判定除外('') 217 private static final char CHAR_ESC = '\\' ; // エスケープ文字('\\') 218 219 private boolean useCharEsc ; // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 220 221 private boolean escIn1 ; // コメント判定除外中かどうか("") 222 private boolean escIn2 ; // コメント判定除外中かどうか('') 223 private boolean chEsc ; // コメント除外のエスケープ処理中かどうか 224 225 private boolean isBlkIn ; // ブロックコメントが継続しているかどうか 6.4.1.1 (2016/01/16) refactoring isBlockIn → isBlkIn 226 227 /** 228 * コメントの種類を指定するコンストラクタです。 229 * 230 * Java,C,JavaScript系、// , /* , */ 231 * HTML,XML系、 // , <!-- , --> 232 * ORACLE系 -- , /* , */ 233 * openGion JSP系 null , <og:comment , /> ,</og:comment> 234 * 235 * @param lineCmnt ラインコメント 236 * @param blockCmnt1 ブロックコメントの開始 237 * @param blockCmnt2 ブロックコメントの終了(可変長配列) 238 */ 239 CommentSet( final String lineCmnt,final String blockCmnt1,final String... blockCmnt2 ) { 240 LINE_CMNT = lineCmnt ; // ラインコメント 241 BLOCK_CMNT1 = blockCmnt1 ; // ブロックコメントの開始 242 BLOCK_CMNT2 = blockCmnt2 ; // ブロックコメントの終了 243 } 244 245 /** 246 * エスケープ文字を使用する設定を行います。 247 * 248 * 本来は、コンストラクタで、指示すべきですが、String... 定義を使っているため、 249 * 最後に、引数を追加できません。かといって、途中や先頭に入れるのも、嫌です。 250 * とりあえず、メソッドを作成して、自分自身を返せば、List にセットする処理は、 251 * そのまま使用できるため、このような形にしています。 252 * 253 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 254 * 255 * @return 自分自身 256 */ 257 /* default */ CommentSet useEsc() { 258 useCharEsc = true; 259 return this; 260 } 261 262 /** 263 * ブロック外で、エスケープ文字の場合は、内外反転します。 264 * 265 * @og.rev 6.4.1.1 (2016/01/16) Avoid if (x != y) ..; else ..; refactoring 266 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 267 * 268 * @param ch チェックするコメント除外char 269 * @return エスケープ文字中の場合は、true 270 */ 271 /* default */ boolean checkEsc( final char ch ) { 272 if( isBlkIn || chEsc ) { 273 chEsc = false; 274 } 275 else { 276// chEsc = CHAR_ESC == ch; 277 chEsc = useCharEsc && CHAR_ESC == ch; // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 278 if( !escIn2 && ESC_CHAR1 == ch ) { escIn1 = !escIn1 ; } // escIn2 でない場合に、escIn1 の判定を行う。 279 if( !escIn1 && ESC_CHAR2 == ch ) { escIn2 = !escIn2 ; } // 同様にその逆 280 } 281 return escIn1 || escIn2; // どちらかで、エスケープ中 282 } 283 284 /** 285 * ブロックコメント中かどうかの判定を行います。 286 * 287 * @og.rev 6.4.5.1 (2016/04/28) ブロックコメントを指定しないケースに対応。 288 * 289 * @param line チェックする行データ 290 * @param st チェック開始文字数 291 * @return ブロックコメント中の場合は、true を返します。 292 */ 293 /* default */ boolean isBlockIn( final String line , final int st ) { 294 if( !isBlkIn && BLOCK_CMNT1 != null ) { isBlkIn = line.startsWith( BLOCK_CMNT1,st ); } 295 296 return isBlkIn ; 297 } 298 299 /** 300 * ラインコメントかどうかの判定を行います。 301 * 302 * @param line チェックする行データ 303 * @param st チェック開始文字数 304 * @return ラインコメントの場合は、true を返します。 305 */ 306 /* default */ boolean isLineCmnt( final String line , final int st ) { 307 return LINE_CMNT != null && line.startsWith( LINE_CMNT,st ) ; 308 } 309 310 /** 311 * ブロックコメントの終了を見つけます。 312 * 終了は、複数指定でき、それらのもっとも最初に現れる方が有効です。 313 * 例:XMLタグで、BODYが、あるなしで、終了条件が異なるケースなど。 314 * この処理では、ブロックコメントが継続中かどうかの判定は行っていないため、 315 * 外部(呼び出し元)で、判定処理してください。 316 * 317 * ※ このメソッドは、ブロックコメント中にしか呼ばれないため、 318 * ブロックコメントを指定しないケース(BLOCK_CMNT1==null)では呼ばれません。 319 * 320 * @og.rev 8.0.0.0 (2021/09/30) ブロックコメントの終了処理を修正 321 * 322 * @param line チェックする行データ 323 * @param st チェック開始文字数 324 * @return ブロックコメントの終了の位置。なければ、-1 325 */ 326 /* default */ int blockOut( final String line , final int st ) { 327 int ed = line.length(); 328 for( final String key : BLOCK_CMNT2 ) { 329 // final int tmp = line.indexOf( key,st + BLOCK_CMNT1.length() ); // 6.4.1.0 (2016/01/09) 開始位置の計算ミス 330 // if( tmp >= 0 && tmp < ed ) { // 存在して、かつ小さい方を選ぶ。 331 // ed = tmp + key.length(); // アドレスは、終了コメント記号の後ろまで。 332 // final int tmp = line.indexOf( key,st ); // 8.0.0.0 (2021/09/30) 開始位置の計算ミス 333 // if( tmp >= 0 ) { // 存在した場合は、 334 // ed = Math.min(ed,tmp+key.length()); // 小さい方を選ぶ。 335 // isBlkIn = false; // 見つかった事をキープしておく 336 337 // 8.0.0.0 (2021/09/30) 開始位置の計算ミス 338 final int tmp; 339 // チェックする行データにブロックコメントの開始が存在する 340 if ( line.indexOf( BLOCK_CMNT1,st ) >= 0 ) { 341 tmp = line.indexOf( key,st + BLOCK_CMNT1.length() ); 342 // チェックする行データにブロックコメントの開始が存在しない 343 } else { 344 tmp = line.indexOf( key,st ); 345 } 346 347 // ブロックコメントの終了が存在する 348 if ( tmp >= 0 ) { 349 ed = Math.min(ed,tmp+key.length()); // 小さい方を選ぶ 350 isBlkIn = false; // 見つかった事をキープしておく 351 } 352 } 353 354 return isBlkIn ? -1 : ed ; // 見つからない(継続中)場合は、-1 を返す。 355 } 356 } 357 358 /** 359 * このクラスの動作確認用の、main メソッドです。 360 * 361 * Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim] 362 * 363 * -rowTrim を指定すると、空行も削除します。これは、コメントの増減は、ソースレベルで比較する場合に 364 * 関係ないためです。デフォルトは、空行は削除しません。grep 等で検索した場合、オリジナルの 365 * ソースの行数と一致させるためです。 366 * 367 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 368 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 369 * 370 * @param args コマンド引数配列 371 */ 372 public static void main( final String[] args ) { 373 if( args.length < 2 ) { 374 System.out.println( "Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim]" ); 375 } 376 377 final File inFile = new File( args[0] ); 378 final File outFile = new File( args[1] ); 379 String encode = "UTF-8" ; 380 boolean rowTrim = false; 381 for( int i=2; i<args.length; i++ ) { 382 if( "-rowTrim".equalsIgnoreCase( args[i] ) ) { rowTrim = true; } 383 else { encode = args[i]; } 384 } 385 386 final BufferedReader reader = FileUtil.getBufferedReader( inFile ,encode ); 387 final PrintWriter writer = FileUtil.getPrintWriter( outFile ,encode ); 388 389 final CommentLineParser clp = new CommentLineParser( FileInfo.getSUFIX( inFile ) ); 390 391 try { 392 String line1; 393 while((line1 = reader.readLine()) != null) { 394 line1 = clp.line( line1 ); 395 if( !rowTrim || !line1.isEmpty() ) { 396 writer.println( line1 ); 397 } 398 } 399 } 400 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 401 catch( final CharacterCodingException ex ) { 402 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 403 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 404 + " [" + inFile.getPath() + "] , Encode=[" + encode + "]" ; 405 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 406 } 407 catch( final IOException ex ) { 408 final String errMsg = "ファイルコピー中に例外が発生しました。\n" 409 + " inFile=[" + inFile + "] , outFile=[" + outFile + "]\n" ; 410 throw new OgRuntimeException( errMsg,ex ); 411 } 412 finally { 413 Closer.ioClose( reader ) ; 414 Closer.ioClose( writer ) ; 415 } 416 } 417}