/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.xml;

import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;

import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.LogWriter;

/**
 * このクラスは、jspファイルのXSLT変換に特化した、Readerオブジェクトを作成するクラスです。
 * jspファイル に記述される、jsp:directive.include を見つけて、そのファイル属性に
 * 記述されているファイルを、インクルードします。
 * Tomcat の特性上、インクルード時のファイルは、＆等のエスケープを処理しておく
 * 必要があります。
 * <del>(つまり、オリジナルは、エスケープ処理なしで、インクルードされるファイルは、
 * 属性部分のみ、エスケープ処理が必要です。)</del>
 * エスケープの前処理は、jsp:root タグのあるなしで判定します。
 * 現時点では、 & , < , <= , > , >= を前処理します。
 *
 * <del>タグのBODY部に記述されている ＆ なども、パースの対象になりますが、
 * 擬似的に処理しています。
 * それは、『&nbsp;』『< 』『<=』について、処理しています。
 * この形以外の処理は、今は入れていません。</del>
 *
 * JSP では、og:head タグで、<html> を出力したり、htmlend.jsp インクルードで
 * </body></html> を出力していますが、フレームや、フォワードなど、整合性が取れない
 * ケースがありますので、XML処理用として、<html> を出力していません。
 * 変換結果を、正式な HTML ファイルとして再利用される場合は、ご注意ください。
 *
 * @og.rev 4.0.0.2 (2007/12/10) 新規追加
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class JspIncludeReader {
	private static final String CR = System.getProperty("line.separator");

	// タグの属性の値のみを抜き出しています。特に、<>& を含む場合。
	// 5.2.1.0 (2010/10/01) 仮廃止
	//	private static final Pattern ptn = Pattern.compile( "=[ \t]*\"([^\"]*[<>&].[^\"]*)\"" );

	/**
	 *	JSP のインクルードを考慮した、Reader オブジェクトを返します。
	 *
	 * @param file File
	 * @param encode String
	 *
	 * @return Reader
	 */
//	public Reader getReader( final File file,final String encode ) {
//		return new StringReader( getString( file,encode,false ) );
//	}

	/**
	 * JSP のインクルードを考慮した、JSPファイルを、String で返します。
	 * このメソッドは、内部で再帰定義されています。つまり、jsp:directive.include
	 * 文字列が見つかった場合は、その代わりに、ファイル名を取出して、もう一度
	 * このメソッドを呼び出します。
	 * <del>Tomcat の特性上、インクルード時のファイルは、＆等のエスケープを処理しておく
	 * 必要がありますが、オリジナルは、エスケープ処理しません。
	 * その切り替えを、第３引数の isEscape で制御しています。</del>
	 * Ver4 以降で、インクルードファイルに、XML宣言と、jsp:root を付与するケースがあります。
	 * 擬似的に取り込むときには、XML宣言は削除します。
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) directive.include で、XMLタグとroot タグは取り込まない。
	 * @og.rev 5.2.1.0 (2010/10/01) エスケープ処理の引数を廃止します。
	 * @og.rev 5.6.5.2 (2013/06/21) 小細工内容の変更。replaceAll にするのと、スペースまたはタブを使用します。
	 *
	 * @param	file	JSPファイル
	 * @param	encode	ファイルのエンコード
	 *
	 * @return	インクルードを考慮した、JSPファイル
	 */
//	public String getString( final File file,final String encode,final boolean isEscape ) {
	public String getString( final File file,final String encode ) {
		StringBuilder buf = new StringBuilder() ;
		BufferedReader reader = FileUtil.getBufferedReader( file,encode );

		// ファイルが、jsp 直下かどうかを判断します。
		String parentFile = file.getParent() ;
		boolean isUnder = parentFile.endsWith( "\\jsp" );

		int  cmntIn    = -1;
		int  cmntOut   = -1;
		boolean isCmnt = false;
		boolean isEscape = true;	// エスケープするかどうか(true:する/false:しない)
		try {
			String line ;
			while((line = reader.readLine()) != null) {
				// 5.2.1.0 (2010/10/01) directive.include で、XMLタグは取り込まない。
				if( line.indexOf( "<?xml" ) >= 0 && line.indexOf( "?>" ) >= 0 ) { continue; }
				// jsp:root があれば、エスケープ処理を行わない
				if( line.indexOf( "<jsp:root" ) >= 0 ) { isEscape = false; }

				// コメントの削除
				cmntIn  = line.indexOf( "<!--" );
				cmntOut = line.indexOf( "-->" );
				if( cmntIn >= 0 && cmntOut >= 0 ) {
					line = line.substring( 0,cmntIn ) + line.substring( cmntOut );
				}
				else if( cmntIn >= 0 && cmntOut < 0 ) {
					line = line.substring( 0,cmntIn );
					isCmnt = true;
				}
				else if( cmntIn < 0  && cmntOut >= 0 ) {
					line = line.substring( cmntOut );
					isCmnt = false;
				}
				else if( isCmnt && cmntIn < 0 && cmntOut < 0 ) { continue; }

				// 特殊処理：og:head で html タグを出力している。
	//			if( line.indexOf( "<og:head" ) >= 0 ) {
	//				buf.append( "<html>" );
	//			}

				if( isEscape ) {
					// 5.6.5.2 (2013/06/21) 小細工内容の変更。replaceAll にするのと、スペースまたはタブを使用します。
					// & , < , <= , > , >= を前処理します。
//					line = line.replace( "&"  ,"&amp;" );		// ちょっと小細工
//					line = line.replace( " < "," &lt; " );		// ちょっと小細工
//					line = line.replace( " > "," &gt; " );		// ちょっと小細工
//					line = line.replace( " <="," &lt;=" );		// ちょっと小細工
//					line = line.replace( " >="," &gt;=" );		// ちょっと小細工
					line = line.replaceAll( "&"  ,"&amp;" );				// ちょっと小細工
					line = line.replaceAll( "[ \\t]<[ \\t]"," &lt; " );		// ちょっと小細工
					line = line.replaceAll( "[ \\t]>[ \\t]"," &gt; " );		// ちょっと小細工
					line = line.replaceAll( "[ \\t]<="," &lt;=" );			// ちょっと小細工
					line = line.replaceAll( "[ \\t]>="," &gt;=" );			// ちょっと小細工
	// 5.2.1.0 (2010/10/01) 仮廃止
	//				Matcher mtch = ptn.matcher( line );
	//				int adrs = 0;
	//				StringBuilder buf2 = new StringBuilder();
	//				while( mtch.find(adrs) ) {
	//					String grp = mtch.group(1);
	//					String htm = StringUtil.htmlFilter( grp );
	//					int in = mtch.start(1);
	//					buf2.append( line.substring( adrs,in ) ).append( htm );
	//					adrs = mtch.end(1);
	//				}
	//				buf2.append( line.substring( adrs ) );
	//				line = buf2.toString();
				}

				int st = line.indexOf( "<jsp:directive.include" );
				if( st < 0 ) { buf.append( line ); }	// include が無ければ、そのまま追加
				else {
					buf.append( line.substring( 0,st ) );
					int fin = line.indexOf( '\"',st );		// ファイルの最初
					int fout= line.indexOf( '\"',fin+1 );	// ファイルの最後
					String fname = line.substring( fin+1,fout );	// ファイル名

					// htmlend.jsp の インクルードは行わない。
					if( fname.endsWith( "htmlend.jsp" ) ) {
						if( buf.indexOf( "<body" ) >= 0 && buf.indexOf( "</body>" ) < 0 ) {
							buf.append( "</body>" );
						}

	//					if( buf.indexOf( "<html" ) >= 0 ) {
	//						buf.append( "</html>" );
	//					}
					}
					else {
						// ちょっと小細工
						// include するファイルは、/jsp/ からの絶対パス。
						// jsp 直下の場合は、./ 、それ以外は、../ と置き換えます。
						if( isUnder ) { fname = fname.replace( "/jsp/","./" ); }
						else 		  { fname = fname.replace( "/jsp/","../" ); }
						File newfile = new File( parentFile,fname );
						buf.append( getString( newfile,encode ) );
					}
					int tagout = line.indexOf( "/>",fout+1 );	// タグの最後

					buf.append( line.substring( tagout+2 ) );
				}

				// og:commonForward を見つけた場合は、最後に html タグを出力する。
	//			if( line.indexOf( "<og:commonForward" ) >= 0 ) {
	//				buf.append( "</html>" );
	//			}

				buf.append( CR );
			}
		}
		catch( IOException ex ) {
			LogWriter.log( ex );
		}
		finally {
			Closer.ioClose( reader );
		}
		return buf.toString();
	}

	/**
	 * テスト用の main メソッド
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		JspIncludeReader reader = new JspIncludeReader();
//		String xml = reader.getString( new File( args[0] ),"UTF-8",false );
		String xml = reader.getString( new File( args[0] ),"UTF-8" );

		if( args.length > 1 ) {
			java.io.PrintWriter writer = FileUtil.getPrintWriter( new File( args[1] ),"UTF-8" );
			writer.print( xml );
			Closer.ioClose( writer );
		}
		else {
			System.out.println( xml );
		}
	}
}
