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