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.xml; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.BufferedReader; 021import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 022import java.util.concurrent.ConcurrentHashMap; // 6.4.3.3 (2016/03/04) 023 024import org.opengion.fukurou.util.FileUtil; 025// import org.opengion.fukurou.system.Closer; // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 026import org.opengion.fukurou.system.LogWriter; 027import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 028import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 029 030/** 031 * このクラスは、jspファイルのXSLT変換に特化した、Readerオブジェクトを作成するクラスです。 032 * jspファイル に記述される、jsp:directive.include を見つけて、そのファイル属性に 033 * 記述されているファイルを、インクルードします。 034 * Tomcat の特性上、インクルード時のファイルは、&等のエスケープを処理しておく 035 * 必要があります。 036 * エスケープの前処理は、jsp:root タグのあるなしで判定します。 037 * 現時点では、 &amp; , < , <= , > , >= を前処理します。 038 * 039 * JSP では、og:head タグで、<html> を出力したり、htmlend.jsp インクルードで 040 * </body></html> を出力していますが、フレームや、フォワードなど、整合性が 041 * 取れないケースがありますので、XML処理用として、<html> を出力していません。 042 * 変換結果を、正式な HTML ファイルとして再利用される場合は、ご注意ください。 043 * 044 * なお、このクラスは、マルチスレッド対応されていません。 045 * 046 * @og.rev 4.0.0.2 (2007/12/10) 新規追加 047 * 048 * @version 4.0 049 * @author Kazuhiko Hasegawa 050 * @since JDK5.0, 051 */ 052public class JspIncludeReader { 053 /** 6.4.3.3 (2016/03/04) includeファイルは、共通ファイルなので、データ量は多いが数は少ない。 */ 054 private static final ConcurrentMap<String,String> INCLUDE_FILES = new ConcurrentHashMap<>(); // 6.4.3.3 (2016/03/04) 055 056 /** 5.6.7.1 (2013/08/09) デバッグ用にincludeしたファイルを保存しておきます。 */ 057 private final StringBuilder incFiles = new StringBuilder( BUFFER_MIDDLE ); 058 059 /** 5.7.6.2 (2014/05/16) realPath で、/jsp/common/以下に、実ファイルが存在しない場合の代替取得先を指定します。 */ 060 private String realPath ; 061 062 // タグの属性の値のみを抜き出しています。特に、<>& を含む場合。 063 // 5.2.1.0 (2010/10/01) 仮廃止 064 // private static final Pattern ptn = Pattern.compile( "=[ \t]*\"([^\"]*[<>&].[^\"]*)\"" ); 065 066 /** 067 * デフォルトコンストラクター 068 * 069 * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません 070 */ 071 public JspIncludeReader() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 072 073 /** 074 * JSP のインクルードを考慮した、JSPファイルを、String で返します。 075 * このメソッドは、内部で再帰定義されています。つまり、jsp:directive.include 076 * 文字列が見つかった場合は、その代わりに、ファイル名を取出して、もう一度 077 * このメソッドを呼び出します。インクルードファイルとの関連をチェックする為に 078 * ダミーのspanタグを入れておきます。 079 * <span type="jsp:directive" include="ファイル名"><!-- --></span> 080 * ただし、ソースチェック時に、 081 * Ver4 以降で、インクルードファイルに、XML宣言と、jsp:root を付与するケースがあります。 082 * 擬似的に取り込むときには、XML宣言は削除します。 083 * 084 * @og.rev 5.2.1.0 (2010/10/01) directive.include で、XMLタグとroot タグは取り込まない。 085 * @og.rev 5.2.1.0 (2010/10/01) エスケープ処理の引数を廃止します。 086 * @og.rev 5.6.5.2 (2013/06/21) 小細工内容の変更。replaceAll にするのと、スペースまたはタブを使用します。 087 * @og.rev 5.6.7.1 (2013/08/09) コメントの処理のバグ修正。includeファイル名保存。 088 * @og.rev 5.6.7.1 (2013/08/09) includeファイルが存在しない場合は、gf共有から取得する。 089 * @og.rev 5.6.7.2 (2013/08/16) includeファイルを取り込む場合、代わりのspanタグを出力しておきます。 090 * @og.rev 5.6.7.4 (2013/08/30) includeファイルの先頭のpageEncoding指定のチェック用 span タグの出力 091 * @og.rev 5.7.6.2 (2014/05/16) realPath で、/jsp/common/以下に、実ファイルが存在しない場合の代替取得先を指定します。 092 * @og.rev 6.3.9.1 (2015/11/27) htmlend.jsp をインクルード処理しない箇所で、判定方法を、htmlend を含むかどうかに変更。 093 * @og.rev 6.4.3.2 (2016/02/19) /jsp/*** で始まるファイルのみ、キャッシュします。 094 * @og.rev 8.1.1.2 (2022/02/25) 1行に複数コメントの不具合対応 (例: --> XXX <!-- YYY -->) 095 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 096 * 097 * @param file JSPファイル 098 * @param encode ファイルのエンコード 099 * 100 * @return インクルードを考慮した、JSPファイル 101 * @og.rtnNotNull 102 */ 103 public String getString( final File file,final String encode ) { 104 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) ; 105// final BufferedReader reader = FileUtil.getBufferedReader( file,encode ); 106 107 // ファイルが、jsp 直下かどうかを判断します。 108 final String parentFile = file.getParent() ; 109 final boolean isUnder = parentFile.endsWith( "\\jsp" ); 110 111 int cmntIn = -1; 112 int cmntOut = -1; 113 boolean isCmnt = false; // true:コメント内 false:通常 114 boolean isEscape = true; // エスケープするかどうか(true:する/false:しない) 115 // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 116// try { 117 try ( BufferedReader reader = FileUtil.getBufferedReader( file,encode ) ) { 118 String line ; 119 cmntA: while((line = reader.readLine()) != null) { 120 // 5.2.1.0 (2010/10/01) directive.include で、XMLタグは取り込まない。 121 if( line.indexOf( "<?xml" ) >= 0 && line.indexOf( "?>" ) >= 0 ) { continue; } 122 // jsp:root があれば、エスケープ処理を行わない 123 if( line.indexOf( "<jsp:root" ) >= 0 ) { isEscape = false; } 124 125 // コメントの削除 126 while(true) { // 8.1.1.2 (2022/02/21) Add 127 cmntIn = line.indexOf( "<!--" ); 128 cmntOut = line.indexOf( "-->" ); 129 // コメント開始&終了有り 8.1.1.2 (2022/02/21) Modify 130 if( cmntIn >= 0 && cmntOut >= 0 ) { 131 // コメント終了(複数行) 132 if( isCmnt) { 133 line = line.substring( cmntOut+3 ); // 5.6.7.1 (2013/08/09) コメントの処理のバグ修正 134 // コメント開始/終了(1行) 135 } else { 136 line = line.substring( 0,cmntIn ) + line.substring( cmntOut+3 ); // 5.6.7.1 (2013/08/09) コメントの処理のバグ修正 137 } 138 isCmnt = false; // コメント部分削除済 139 } 140 // コメント開始有り&終了無し(複数行のコメント開始) 141 else if( cmntIn >= 0 && cmntOut < 0 ) { 142 line = line.substring( 0,cmntIn ); 143 isCmnt = true; 144 break; // 8.1.1.2 (2022/02/21) Add 145 } 146 // コメント開始無し&終了有り(複数行のコメント終了) 147 else if( cmntIn < 0 && cmntOut >= 0 ) { 148 line = line.substring( cmntOut+3 ); // 5.6.7.1 (2013/08/09) コメントの処理のバグ修正 149 isCmnt = false; 150 } 151 // コメント開始&終了無し 152 else { 153 if( isCmnt ) { continue cmntA; } // コメント内 154 else { break; } // 8.1.1.2 (2022/02/21) Add 155 } 156 } // 8.1.1.2 (2022/02/21) Add 157 158 // 特殊処理:og:head で html タグを出力している。 159 // if( line.indexOf( "<og:head" ) >= 0 ) { 160 // buf.append( "<html>" ); 161 // } 162 163 if( isEscape ) { 164 // 5.6.5.2 (2013/06/21) 小細工内容の変更。replaceAll にするのと、スペースまたはタブを使用します。 165 // & , < , <= , > , >= を前処理します。 166 line = line.replaceAll( "&" ,"&" ); // ちょっと小細工 167 line = line.replaceAll( "[ \\t]<[ \\t]"," < " ); // ちょっと小細工 168 line = line.replaceAll( "[ \\t]>[ \\t]"," > " ); // ちょっと小細工 169 line = line.replaceAll( "[ \\t]<="," <=" ); // ちょっと小細工 170 line = line.replaceAll( "[ \\t]>="," >=" ); // ちょっと小細工 171 // 5.2.1.0 (2010/10/01) 仮廃止 172 // Matcher mtch = ptn.matcher( line ); 173 // int adrs = 0; 174 // StringBuilder buf2 = new StringBuilder(); 175 // while( mtch.find(adrs) ) { 176 // String grp = mtch.group(1); 177 // String htm = StringUtil.htmlFilter( grp ); 178 // int in = mtch.start(1); 179 // buf2.append( line.substring( adrs,in ) ).append( htm ); 180 // adrs = mtch.end(1); 181 // } 182 // buf2.append( line.substring( adrs ) ); 183 // line = buf2.toString(); 184 } 185 186 final int st = line.indexOf( "<jsp:directive.include" ); 187 if( st < 0 ) { buf.append( line ); } // include が無ければ、そのまま追加 188 else { 189 buf.append( line.substring( 0,st ) ); 190 final int fin = line.indexOf( '\"',st ); // ファイルの最初 191 final int fout= line.indexOf( '\"',fin+1 ); // ファイルの最後 192 final String fname = line.substring( fin+1,fout ); // ファイル名 193 194 // 5.6.7.2 (2013/08/16) includeファイルを取り込む場合、代わりのspanタグを出力しておきます。 195 buf.append( "<span type=\"jsp:directive\" include=\"" ) 196 .append( fname ).append( "\" ><!-- --></span>" ) ; 197 198 // htmlend.jsp の インクルードは行わない。 199 // if( fname.endsWith( "htmlend.jsp" ) ) { 200 if( fname.contains( "htmlend" ) ) { // 6.3.9.1 (2015/11/27) 201 if( buf.indexOf( "<body" ) >= 0 && buf.indexOf( "</body>" ) < 0 ) { 202 buf.append( "</body>" ); 203 } 204 205 // if( buf.indexOf( "<html" ) >= 0 ) { 206 // buf.append( "</html>" ); 207 // } 208 } 209 else { 210 // 5.6.7.1 (2013/08/09) デバッグ用にincludeしたファイルを保存しておきます。 211 if( incFiles.length() > 0 ) { incFiles.append( " , " ); } 212 incFiles.append( fname ); 213 214 // 5.6.7.1 (2013/08/09) includeしたファイルをキャッシュから検索します。 215 String fileData = INCLUDE_FILES.get( fname ); // キャッシュを検索(fname がキー) 216 if( fileData == null ) { 217 // ちょっと小細工 218 String fname2 = fname ; 219 // include するファイルは、/jsp/ からの絶対パス。 220 // jsp 直下の場合は、./、それ以外は、../ と置き換えます。 221 if( isUnder ) { fname2 = fname2.replace( "/jsp/","./" ); } 222 else { fname2 = fname2.replace( "/jsp/","../" ); } 223 // 5.6.7.1 (2013/08/09) includeファイルが存在しない場合は、gf共有から取得する。(jar圧縮対応) 224 File newfile = new File( parentFile,fname2 ); 225 // 8.5.4.2 (2024/01/12) PMD 7.0.0 CollapsibleIfStatements 226// if( !newfile.exists() ) { 227// if( fname2.contains( "/common/" ) || fname2.contains( "/menu/" ) ) { 228 if( !newfile.exists() 229 && ( fname2.contains( "/common/" ) || fname2.contains( "/menu/" ) ) ) { 230 if( realPath == null ) { 231 // 本当は classPathから、取得すべき。 232 // 今は、実行環境の相対パスの位置に、gf/jsp/common,menu のファイルが必要。 233 fname2 = isUnder 234 ? ( "./../../gf/jsp/" + fname2.substring( 2 ) ) 235 : ( "../../../gf/jsp/" + fname2.substring( 3 ) ) ; 236 newfile = new File( parentFile,fname2 ); // ここでなければ、エラーになる。 237 } 238 else { 239 // 5.7.6.2 (2014/05/16) realPath で、/jsp/common/以下に、実ファイルが存在しない場合の代替取得先を指定します。 240 newfile = new File( realPath,fname ); // 稼働している gf の common 等を使用します。 241 } 242// } 243 } 244 fileData = getString( newfile,encode ); 245 246 // 5.6.7.4 (2013/08/30) includeファイルの先頭のpageEncoding指定のチェック用 span タグの出力 247 // インクルードファイルの先頭には、pageEncoding="UTF-8" 宣言が必要(UTF-8かどうかは未チェック) 248 if( ! fileData.startsWith( "<jsp:directive.page pageEncoding" ) ) { 249 // チェック用のspanタグを出力しておきます。 250 buf.append( "<span type=\"jsp:directive\" pageEncoding=\"non\" file=\"" ) 251 .append( fname ).append( "\" ><!-- --></span>" ) ; 252 } 253 // 6.4.3.2 (2016/02/19) /jsp/*** で始まるファイルのみ、キャッシュします。 254 // 5.6.7.1 (2013/08/09) includeしたファイルをキャッシュしておきます。 255 if( fname.startsWith( "/jsp/" ) ) { 256 INCLUDE_FILES.put( fname,fileData ); // includeファイルをキャッシュ(fname がキー) 257 } 258 } 259 buf.append( fileData ); 260 } 261 final int tagout = line.indexOf( "/>",fout+1 ); // タグの最後 XML なので、このまま。 262 263 buf.append( line.substring( tagout+2 ) ); 264 } 265 266 // og:commonForward を見つけた場合は、最後に html タグを出力する。 267 // if( line.indexOf( "<og:commonForward" ) >= 0 ) { 268 // buf.append( "</html>" ); 269 // } 270 271 buf.append( CR ); 272 } 273 } 274 catch( final IOException ex ) { 275 LogWriter.log( ex ); 276 } 277// finally { 278// Closer.ioClose( reader ); 279// } 280 return buf.toString(); 281 } 282 283 /** 284 * jspInclude=true 時に、/jsp/common/** 等の include ファイルが存在しない場合の共有取得場所を指定します。 285 * 286 * 引数の処理対象ファイル(transformの引数ファイル)が、『.jsp』で、かつ、jspInclude=true の場合、 287 * そのファイルを INCLUDE するのですが、/jsp/common/** 等の include ファイルは、 288 * エンジン共通として、jspCommon6.x.x.x.jar で提供しています。 289 * 従来は、処理対象jspの相対パスで、../../../gf/jsp/commom/** を取り込んでいましたが、 290 * Tomcat起動フォルダ以外のシステムのJSPチェックなどを行う場合は、gf フォルダが存在しない 291 * ケースがあります。 292 * そこで、確実にgf が存在する、処理をキックしている環境の gf を使用するように変更します。 293 * その環境とは、つまり、エンジン内部変数の REAL_PATH ですが、jsp などが実行していないと取得できません。 294 * 295 * @param path /jsp/common/** 等の include ファイルの共有取得場所 296 */ 297 public void setRealPath( final String path ) { 298 realPath = path ; 299 } 300 301 /** 302 * インクルードしたファイル名(相対パス)のリスト文字列を返します。 303 * 通常は、XSLT変換処理でエラーが発生した場合は、includeファイルの整合性が 304 * おかしい場合が多いので、デバッグ情報として利用します。 305 * ただし、エラー発生時の位置特定まではできません。 306 * 307 * この内部変数は、インスタンス変数ですので、includeファイルのキャッシュとは寿命が異なります。 308 * 309 * @og.rev 5.6.7.1 (2013/08/09) 新規追加 310 * 311 * @return includeファイル名のリスト文字列 312 * @og.rtnNotNull 313 */ 314 public String getIncludeFiles() { 315 return incFiles.toString(); 316 } 317 318 /** 319 * インクルードしたファイルのキャッシュをクリアします。 320 * キャッシュは、インスタンスではなく、スタティック変数で管理しています。 321 * よって、一連の処理の初めと最後にクリアしておいてください。 322 * 323 * @og.rev 5.6.7.1 (2013/08/09) 新規追加 324 */ 325 public static void cacheClear() { 326 INCLUDE_FILES.clear(); 327 } 328 329 /** 330 * テスト用の main メソッド。 331 * 332 * Usage: org.opengion.fukurou.xml.JspIncludeReader inFile [outFile] 333 * 334 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 335 * 336 * @param args コマンド引数配列 337 */ 338 public static void main( final String[] args ) { 339 final JspIncludeReader reader = new JspIncludeReader(); 340 final String xml = reader.getString( new File( args[0] ),"UTF-8" ); 341 342 if( args.length > 1 ) { 343// final java.io.PrintWriter writer = FileUtil.getPrintWriter( new File( args[1] ),"UTF-8" ); 344// writer.print( xml ); 345// Closer.ioClose( writer ); 346 try ( java.io.PrintWriter writer = FileUtil.getPrintWriter( new File( args[1] ),"UTF-8" ) ) { 347 writer.print( xml ); 348 } 349 } 350 else { 351 System.out.println( xml ); 352 } 353 } 354}