/*
 * 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 org.opengion.fukurou.system.OgRuntimeException ;				// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.system.Closer ;
import org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.util.HybsEntry ;
import org.opengion.fukurou.util.FileUtil ;
import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

import java.io.Reader;
import java.io.Writer;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;

import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.ErrorListener;							// 6.4.0.2 (2015/12/11)

/**
 * XML 入力ファイルに、XSL 入力ファイルを適用して、 XSLT変換を行います。
 * 結果は、XML 出力ファイルにセーブします。
 * 各ファイルの代わりに、Writer,Reader を設定することも可能です。
 *
 * このパーサーでは、内部で実行中の入力ファイル情報を パラメータとして設定できます。
 * useFileInfo( true ) とセットすると、以下の４項目が内部的にセットされます。
 * ただし、この設定が可能なのは、XML 入力ファイルに、Reader ではなく、ファイル名を
 * 渡した場合のみです。ストリームの場合は、各種情報は取れません。
 *
 * 入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:\webapps\gf\jsp\DOC10\query.jsp)
 * 入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)
 * 入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)
 * 入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)
 *
 * xsl ファイルでは、パラメータ は、xsl:param で宣言し、xsl:value-of で取り出します。
 * &lt;xsl:param name="ADDRESS" select="" /&gt; と宣言しておき、必要な箇所で
 * &lt;xsl:value-of select="$ADDRESS"     /&gt; とすれば、取得できます。
 *
 *      String inXSTL  = "inXSLfile.xsl" ;   // 入力ＸＳＬファイル
 *      String outFile = "outXMLfile.xml" ;  // 出力ＸＭＬファイル
 *      String inXML   = "inXMLfile.xml" ;   // 入力ＸＭＬファイル
 *
 *      XSLT xslt = new XSLT();
 *      xslt.setXslFile( inXSTL );
 *      xslt.setOutFile( outFile,false );
 *
 *      xslt.transform( inXML );
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class XSLT {
	/** 初期 ENCODE 名	{@value}	*/
	public static final String ENCODE = "UTF-8" ;

	private Transformer transformer	;

	private String		encode		= ENCODE;
	private String		xmlFile		;
	private String		xslFile		;
	private String		outFile		;
	private Reader		xslReader	;
	private Writer		outWriter	;
	private HybsEntry[] paramEntry	;
	private boolean		isFileInfo	;
	private boolean		isErrClose	= true;
	private boolean		isErrXmlIn	;			// useErrXmlIn ⇒ isErrXmlIn 変更
	private boolean		isInclude	= true;		// 4.2.3.0 (2008/05/26)
	private StreamResult result		;

	private String		realPath	;			// 5.7.6.2 (2014/05/16) 新規追加
	private String		debugMsg	;			// 5.6.7.1 (2013/08/09) デバッグ用

	/**
	 * 入力XSLファイルを、指定します。
	 *
	 * @param	file	入力XSLファイル
	 * @see #setXslFile( Reader )
	 */
	public void setXslFile( final String file ) {
		xslFile = file;
		setXslFile( FileUtil.getBufferedReader( new File( xslFile ),encode ) );
	}

	/**
	 * 入力XSLリーダーを、指定します。
	 *
	 * @param	reader	入力XSLリーダー
	 * @see #setXslFile( String )
	 */
	public void setXslFile( final Reader reader ) {
		transformer = null;
		xslReader   = reader;
	}

	/**
	 * 結果XML ファイル名と、そのオープン方法を指定します。
	 * 結果XML ファイルを、追記する(append=true)か新規作成する(append=false)か指定します。
	 * なお、結果XML ファイル(outFile) を指定しない(=null)か、特別な名称 "System.out"
	 * 文字列を渡すと、標準出力に 結果を出力します。
	 *
	 * @param	file	出力ファイル名(null または、"System.out" 文字列時は、標準出力)
	 * @param append [true]追記する/false:新規作成する]
	 */
	public void setOutFile( final String file,final boolean append ) {
		outFile = file ;
		setOutFile( FileUtil.getPrintWriter( new File( outFile ),encode,append ) );
	}

	/**
	 * 結果XML データを出力する、Writer を指定します。
	 * ファイル、標準出力、JSPWriter など、必要に応じて Writer を作成してください。
	 * 標準出力(System.out)の場合は、NonClosePrintWriter クラスなどの非close()処理系を、
	 * JSPWriterの場合は、NonFlushPrintWriter クラスなどの非flush()、close()処理系を、
	 * 使用してください。
	 *
	 * @param	writer	出力するWriter
	 */
	public void setOutFile( final Writer writer ) {
		Closer.ioClose( outWriter );
		outWriter = writer ;
		result = new StreamResult( outWriter );
	}

	/**
	 * 結果XML ライターに、指定のデータを書き出します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @param	outData	書き出すデータ
	 */
	public void setOutData( final String outData ) {
		if( outData != null && outData.length() > 0 ) {
			// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
			if( outWriter == null ) {
				final String errMsg = "#setOutFile(Writer)を先に実行しておいてください。"	+ CR
								+ "   outData =" + outData	+ CR;
				throw new OgRuntimeException( errMsg );
			}

			try {
				outWriter.write( outData );
				outWriter.write( CR );
			}
			catch( final IOException ex ) {
				final String errMsg = "ライターにデータ登録を失敗しました。" + CR
								+ ex.getMessage() ;
				close();
				throw new OgRuntimeException( errMsg,ex );
			}
		}
	}

	/**
	 * XML ファイルをXSLT変換します。
	 * XML 入力ファイルに、XSL 入力ファイルを適用して、 XSLT変換を行います。
	 * 結果は、XML ファイルにセーブします。
	 * 拡張子が『.jsp』で、かつ、isInclude=true の場合、jsp:directive.include 処理を行います。
	 *
	 * @og.rev 4.2.3.0 (2008/05/26) jsp:directive.include 処理の実施可否を引数指定します。
	 * @og.rev 5.2.1.0 (2010/10/01) JspIncludeReader#getString の第３引数を廃止
	 * @og.rev 5.6.7.1 (2013/08/09) デバッグ用に、ファイルリストを取得しておきます。
	 * @og.rev 5.7.6.2 (2014/05/16) #transform( String , boolean ) 廃止。realPath 追加
	 *
	 * @param	file	入力XMLファイル
	 */
	public void transform( final String file ) {
		xmlFile = file;

		if( xmlFile.endsWith( ".jsp" ) && isInclude ) {
			// 5.6.7.1 (2013/08/09) デバッグ用に、ファイルリストを取得しておきます。
			final JspIncludeReader incReader = new JspIncludeReader();
			incReader.setRealPath( realPath );				// 5.7.6.2 (2014/05/16) realPath 追加
			debugMsg = incReader.getIncludeFiles();

			final String incData = incReader.getString( new File( xmlFile ),encode );				// 5.2.1.0 (2010/10/01)
			transform( new StringReader( incData ) );
		}
		else {
			transform( FileUtil.getBufferedReader( new File( xmlFile ),encode ) );
		}
	}

	/**
	 * XML ファイルをXSLT変換します。
	 * XML 入力リーダーに、XSL 入力リーダーを適用して、 XSLT変換を行います。
	 * 結果は、XML ライターに書き出します。
	 * この処理の終了後に、入力XML リーダー は、close() されます。
	 *
	 * @og.rev 5.6.5.2 (2013/06/21) エラーメッセージが判りにくいので、追記します。
	 * @og.rev 5.6.7.1 (2013/08/09) デバッグ用に、ファイルリストを出力します。
	 * @og.rev 6.4.0.2 (2015/12/11) Transformer のエラーを、より詳細に出力します。
	 *
	 * @param	xmlReader	入力XML リーダー
	 * @see #transform( String )
	 */
	public void transform( final Reader xmlReader ) {
		HybsEntry[] entry = null;

		// transformer は使いまわしますが、ErrorListener は、Reader 毎に再作成します。
		final ErrorListener errListener = new HybsErrorListener();			// 6.4.0.2 (2015/12/11)
		try {
			if( transformer == null ) {
				init( errListener );										// 6.4.0.2 (2015/12/11)
			}
			else {
				transformer.reset();
				transformer.setErrorListener( errListener );				// 6.4.0.2 (2015/12/11)
			}

			// 入力XMLファイルのファイル情報を設定します。
			if( isFileInfo && xmlFile != null ) {
				entry = getXmlParameter( xmlFile );
				parameterSet( transformer,entry );
			}
			xmlFile = null ;

			// 入力XMLリーダーからStreamSourceを作る
			final StreamSource data = new StreamSource( xmlReader );

			transformer.transform( data,result );
		}
		catch( final TransformerException ex ) {
			// 5.7.3.0 (2014/02/07) エラー情報をもう少し詳細に取得します。
	//		final String errMsg = ex.getMessageAndLocation() ;
			final String errMsg = errListener.toString();					// 6.4.0.2 (2015/12/11) さらに詳細に出します。

			final StringBuilder errBuf = new StringBuilder( BUFFER_MIDDLE )
				.append( "=====================================================" ).append( CR )
				.append( "XML-XSLT 変換に失敗しました。" ).append( CR )
				.append( errMsg );

			// 6.3.1.0 (2015/06/28) デバッグ用メッセージを出力します。
			if( debugMsg != null && debugMsg.length() > 0 ) {
				errBuf.append( CR ).append( debugMsg );
			}

			// 5.6.5.2 (2013/06/21) エラーメッセージが判りにくいので、追記します。
			if( errMsg.indexOf( "プロローグにはコンテンツを指定できません" ) >= 0 ) {
				errBuf.append( CR ).append( "\t(UTF-8変換時に、BOMが付くとこのエラーが出ます。BOMを外してみてください。)" );
			}

			// 5.6.7.1 (2013/08/09) デバッグ用に、ファイルリストを出力します。
			if( errMsg.indexOf( "で終了する必要があります" ) >= 0 ) {
				errBuf.append( CR ).append( "\t(不整合は、includeファイルの可能性があります。)" );
			}
			errBuf.append( CR );

			if( isErrXmlIn ) { setOutData( toXmlRow( entry, ex ) ); }

			if( isErrClose ) { close(); }
			throw new OgRuntimeException( errBuf.toString(),ex );
		}
		finally {
			Closer.ioClose( xmlReader );
		}
	}

	/**
	 * Transformer オブジェクトに対して、Parameter を設定します。
	 *
	 * 指定されたパラメーターキーは、xsl ファイルでは、xsl:param で宣言し、
	 * xsl:value-of で取り出します。
	 * &lt;xsl:param name="ADDRESS" select="" /&gt; と宣言しておき、必要な箇所で
	 * &lt;xsl:value-of select="$ADDRESS"     /&gt; とすれば、取得できます。
	 *
	 * @param	entry	HybsEntry配列(可変長引数)
	 */
	public void setParamEntry( final HybsEntry... entry ) {
		if( entry != null && entry.length > 0 ) {		// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			paramEntry = new HybsEntry[entry.length];
			System.arraycopy( entry,0,paramEntry,0,entry.length );
		}
	}

	/**
	 * transform 処理中にエラーが発生した場合に、出力ファイルを閉じるかどうかを指定します。
	 *
	 * 処理途中でエラーが発生した場合に、そこで処理を中断するか、それとも、
	 * 無視して、さらに処理を進めるかを指定することが可能です。
	 * 継続して処理を進めたい場合は、出力ファイルを閉じないため、false を
	 * 設定します。ただし、エラー時には、RuntimeException は throw されます。
	 * 初期値は、true(閉じる)です。
	 *
	 * @param	flag	エラー時クローズ [true:閉じる/false:閉じない]
	 */
	public void errClose( final boolean flag ) {
		isErrClose = flag ;
	}

	/**
	 * transform 処理中エラーを、出力ファイルに、XML形式でエラーを追記するかどうかを指定します。
	 *
	 * 処理途中でエラーが発生した場合に、ログだけではなく、結果XMLファイルに、
	 * エラー内容や、エラーファイルなどを埋め込むと、XMLファイルとしてDB登録や、
	 * その他集計等に使えます。
	 * 今は、GE70 スキーマ形式のファイルしか作成できません。
	 * これは、#errClose( boolean ) メソッドと共に使用すると効果的です。
	 * つまり、errClose = false; にして、エラー時でも出力ファイルを閉じずに、
	 * 処理を続ける事で、エラーメッセージもXMLファイルとして蓄積できます。
	 * 初期値は、false(使用しない)です。
	 *
	 * @param	flag	エラー時XML形式 [false:使用しない/true:使用する]
	 */
	public void useErrXmlIn( final boolean flag ) {
		isErrXmlIn = flag ;
	}

	/**
	 * jsp:directive.include 発見時に、そのファイルを INCLUDE するかを指定するかどうかを指定します(初期値:true:使用する)
	 *
	 * 引数の処理対象ファイル(transformの引数ファイル)が、『.jsp』の場合、
	 * jsp:directive.include 発見時に、そのファイルを INCLUDE するかを指定するか
	 * どうかを指定します。
	 * インクルードされたファイルとあわせて、正規のXML にならないと、パーサー
	 * エラーが発生します。
	 * JSPソース解析を行うには、INCLUDE ファイルも考慮しないと正確な結果を
	 * 得られませんが、INCLUDE 先のファイルまで合わせる必要があるため、
	 * 場合によっては、INCLUDEファイルを無視しなければならないケースがあります。
	 * 初期値は、true(使用する)です。
	 *
	 * @param	flag	エラー時XML形式 [false:使用しない/true:使用する]
	 */
	public void jspInclude( final boolean flag ) {
		isInclude = flag ;
	}

	/**
	 * jspInclude=true 時に、/jsp/common/** 等の include ファイルが存在しない場合の共有取得場所を指定します。
	 *
	 * 引数の処理対象ファイル(transformの引数ファイル)が、『.jsp』で、かつ、jspInclude=true の場合、
	 * そのファイルを INCLUDE するのですが、/jsp/common/** 等の include ファイルは、
	 * エンジン共通として、jspCommon6.x.x.x.jar で提供しています。
	 * 従来は、処理対象jspの相対パスで、../../../gf/jsp/commom/** を取り込んでいましたが、
	 * Tomcat起動フォルダ以外のシステムのJSPチェックなどを行う場合は、gf フォルダが存在しない
	 * ケースがあります。
	 * そこで、確実にgf が存在する、処理をキックしている環境の gf を使用するように変更します。
	 * その環境とは、つまり、エンジン内部変数の REAL_PATH ですが、jsp などが実行していないと取得できません。
	 *
	 * @param	path	/jsp/common/** 等の include ファイルの共有取得場所
	 */
	public void setRealPath( final String path ) {
		realPath = path ;
	}

	/**
	 * 入力XSLファイルのストリームを閉じます。
	 *
	 * @og.rev 5.6.7.1 (2013/08/09) includeしたファイルのキャッシュをクリアします。
	 */
	public void close() {
		Closer.ioClose( outWriter );

		// 5.6.7.1 (2013/08/09) includeしたファイルのキャッシュをクリア
		JspIncludeReader.cacheClear();
	}

	/**
	 * XML ファイルをXSLT変換します。
	 * XML 入力ファイルに、XSL 入力ファイルを適用して、 XSLT変換を行います。
	 * 結果は、XML ファイルにセーブします。
	 * なお、結果XML ファイル(outFile) に、特別な名称 "System.out" 文字列を渡すと、
	 * 標準出力に 結果を出力します。
	 *
	 * @og.rev 5.6.7.1 (2013/08/09) includeしたファイルのキャッシュをクリアします。
	 * @og.rev 6.4.0.2 (2015/12/11) Transformer のエラーを、より詳細に出力します。
	 *
	 * @param	errListener ErrorListenerオブジェクト
	 */
	private void init( final ErrorListener errListener ) {
		try {
			// xsl属性からStreamSourceを作る
			final StreamSource style = new StreamSource( xslReader );

			// Transformerを作り、XMLを変換する
			final TransformerFactory tFactory = TransformerFactory.newInstance();

			// 6.4.0.2 (2015/12/11) Transformer のエラーを、より詳細に出力します。
			tFactory.setErrorListener( errListener );

			transformer = tFactory.newTransformer( style );
			// 6.4.0.2 (2015/12/11) Transformer のエラーを、より詳細に出力します。
			transformer.setErrorListener( errListener );

			parameterSet( transformer,paramEntry );

			// 5.6.7.1 (2013/08/09) includeしたファイルのキャッシュをクリア
			JspIncludeReader.cacheClear();
		}
		catch( final TransformerConfigurationException ex ) {
			final String errMsg = xslFile + "ファイルの XSLT 解析に失敗しました。" + CR
	//							+ ex.getMessageAndLocation()	// 6.4.0.2 (2015/12/11) 行番号がうまく取り出せない。
								+ errListener.toString();		// 6.4.0.2 (2015/12/11) エラー内容が重複するかも。
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( xslReader );
			xslReader = null;
		}
	}

	/**
	 * 実行中の入力ファイル名などの属性情報を パラメータとして設定するかどうかを指定します。
	 *
	 * このパーサーでは、内部で実行中の入力ファイル情報を パラメータとして設定できます。
	 * useFileInfo( true ) とセットすると、以下の４項目が内部的にセットされます。
	 *
	 * 入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:\webapps\gf\jsp\DOC10\query.jsp)
	 * 入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)
	 * 入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)
	 * 入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)
	 *
	 * @og.rev 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加。
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 *
	 * xsl ファイルでは、xsl:param で宣言し、xsl:value-of で取り出します。
	 * &lt;xsl:param name="ADDRESS" select="" /&gt; と宣言しておき、必要な箇所で
	 * &lt;xsl:value-of select="$ADDRESS"     /&gt; とすれば、取得できます。
	 *
	 * 初期値は、false(セットしない) です。
	 *
	 * @param	flag	セットする:true/セットしない:false
	 */
	public void useFileInfo( final boolean flag ) {
		isFileInfo = flag;
	}

	/**
	 * ファイル名指定で XML,XSL,OUTファイルを指定する場合のエンコードを指定します。
	 *
	 * 初期値は、UTF-8 です。
	 *
	 * @param	encode	エンコード
	 */
	public void useEncode( final String encode ) {
		this.encode = encode;
	}

	/**
	 * 実行中の入力ファイル名などの属性情報を パラメータとして取得します。
	 *
	 * 入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:\webapps\gf\jsp\DOC10\query.jsp)
	 * 入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)
	 * 入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)
	 * 入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 *
	 * @param	xmlIn	XML入力ファイル
	 *
	 * @return	HybsEntry配列
	 */
	private HybsEntry[] getXmlParameter( final String xmlIn ) {
		HybsEntry[] entry = new HybsEntry[4] ;

		entry[0] = new HybsEntry( "FILEPATH" , xmlIn) ;

		final File xmlFile = new File( xmlIn );
		entry[1] = new HybsEntry( "FILENAME" , xmlFile.getName()) ;

		final File parentFile = xmlFile.getParentFile() ;
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		entry[2] = parentFile == null
						? new HybsEntry( "ADDRESS"  , "" )
						: new HybsEntry( "ADDRESS"  , parentFile.getName()) ;

		final String lastDate = DateSet.getDate( xmlFile.lastModified() , "yyyyMMddHHmmss" ) ;		// 5.5.7.2 (2012/10/09) HybsDateUtil を利用
		entry[3] = new HybsEntry( "MODIFIED" , lastDate ) ;

		return entry ;
	}

	/**
	 * Transformer オブジェクト に、パラメータを設定します。
	 *
	 * 指定されたパラメーターキーは、xsl ファイルでは、xsl:param で宣言し、
	 * xsl:value-of で取り出します。
	 * <xsl:param name="ADDRESS" select="" /> と宣言しておき、必要な箇所で
	 * <xsl:value-of select="$ADDRESS"     /> とすれば、取得できます。
	 *
	 * @param former Transformerオブジェクト
	 * @param entry  パラメータ配列(可変長引数)
	 */
	private void parameterSet( final Transformer former,final HybsEntry... entry ) {
		if( entry != null && entry.length > 0 ) {		// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			final int size   = entry.length;
			for( int i=0; i<size; i++ ) {
				final String key = entry[i].getKey() ;
				final String val = entry[i].getValue();
				former.setParameter( key , val );
			}
		}
	}

	/**
	 * このオブジェクトの内部文字列表現を返します。
	 *
	 * 接続URL + "," + 接続ユーザー + " (" + 作成日付 + ")" です。
	 *
	 * @return 内部文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "XSL File:" ).append( xslFile ).append( CR )
			.append( "XML File:" ).append( xmlFile ).append( CR )
			.append( "OUT File:" ).append( outFile ).append( CR );

		return buf.toString() ;
	}

	/**
	 * エラー情報の内部XML文字列表現を返します。
	 *
	 * エラー時の情報も、XML化して保存する為の簡易処理。
	 * ここでは、XMLスキーマは、固定で、GF70 の形式になります。
	 *
	 * @og.rev 4.2.3.0 (2008/05/26) エラー発生時のXMLファイルを追加します。
	 * @og.rev 5.2.1.0 (2010/10/01) XML形式を変更します。(TEXT⇒TEXT_DATA)
	 *
	 * @param	entry	HybsEntry配列
	 * @param	ex		エラー情報
	 *
	 * @return XMLの部分文字列
	 * @og.rtnNotNull
	 */
	private String toXmlRow( final HybsEntry[] entry,final TransformerException ex ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		// 6.0.2.5 (2014/10/31) char を append する。
		buf.append( "<ROW>" ).append( CR );
		if( paramEntry != null ) {
			for( int i=0; i<paramEntry.length; i++ ) {
				final String key = paramEntry[i].getKey() ;
				final String val = paramEntry[i].getValue();
				buf.append( "  <" ).append( key ).append( '>' )
					.append( val )
					.append( "</" ).append( key ).append( '>' )
					.append( CR );
			}
		}

		if( entry != null ) {
			for( int i=0; i<entry.length; i++ ) {
				final String key = entry[i].getKey() ;
				final String val = entry[i].getValue();
				buf.append( "  <" ).append( key ).append( '>' )
					.append( val )
					.append( "</" ).append( key ).append( '>' )
					.append( CR );
			}
		}

		buf.append( "  <TAGNAME />" ).append( CR )						// XML なので、このまま。
			.append( "  <MSGCD>XML_ERROR</MSGCD>" ).append( CR )
			.append( "  <MSGTXT>XML-XSLT 変換に失敗しました。</MSGTXT>" ).append( CR );

		String errMsg = StringUtil.htmlFilter( ex.getMessage() );
		final int indx = errMsg.lastIndexOf( "Exception:" );
		if( indx >= 0 ) {
			errMsg = errMsg.substring( indx + "Exception:".length() );
		}
		buf.append( "  <TEXT_DATA>" ).append( errMsg ).append( CR )		// 5.2.1.0 (2010/10/01)
			.append( " Location:" ).append( ex.getLocationAsString() ).append( CR )
			.append( "</TEXT_DATA>" ).append( CR )		// 5.2.1.0 (2010/10/01)
			.append( "</ROW>" ).append( CR );

		return buf.toString() ;

/*
	   <ROW>
		 <SYSTEM_ID>  </SYSTEM_ID>
		 <ADDRESS  >  </ADDRESS>
		 <FILENAME >  </FILENAME>
		 <FILEPATH >  </FILEPATH>
		 <MODIFIED >  </MODIFIED>
		 <TAGNAME  >  </TAGNAME>
		 <MSGCD    >  </MSGCD>
		 <MSGTXT   >  </MSGTXT>
		 <TEXT_DATA>  </TEXT_DATA>
	   </ROW>
*/
	}

	/**
	 * テスト用のメインメソッド。
	 *
	 * java org.opengion.fukurou.xml.XSLT in_xml in_xsl out_xml
	 *
	 * @param	args	コマンド引数配列
	 * @throws IOException 入出力エラーが発生した場合
	 */
	public static void main( final String[] args ) throws IOException {
		if( args.length != 3 ) {
			LogWriter.log( "Usage: java org.opengion.fukurou.xml.XSLT in_xml in_xsl out_xml" );
			LogWriter.log( "  XML 入力ファイルに、XSL 入力ファイルを適用して、" );
			LogWriter.log( "  XSLT変換を行います。" );
			LogWriter.log( "  結果は、XML ファイルにセーブします。" );
			LogWriter.log( "  out_xml に System.out を指定すると標準出力に出力します。" );
			return ;
		}

		final XSLT xslt = new XSLT();
		xslt.setXslFile( args[1] );
		xslt.setOutFile( args[2],false );
		xslt.transform( args[0] );
		xslt.close();
	}
}
