/*
 * 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.util;

import java.util.Locale;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;

/**
 * TagBuffer.java は、共通的に使用される 簡易タグ作成クラスです。
 *
 * ここでは、４つの形式(TAG , CSS , JSON , ARRAY )をサポートします。
 *   TAG形式:
 *     BODYなし  &lt;tag key1="val1" key2="val2" ･･･/&gt;
 *     BODYあり  &lt;tag key1="val1" key2="val2" ･･･&gt;body&lt;/tag&gt;
 *
 *   CSS形式:
 *     { key1:val1; key2:val2; ･･･ }
 *
 *   JSON形式:
 *     文字  { "key1":"val1", "key2":"val2", ･･･ }
 *     数値  { "key1":num1  , "key2":num2  , ･･･ }
 *
 *   ARRAY形式:
 *     文字  [ "val1", "val2", ･･･ ]
 *     数値  [  num1 ,  num2 , ･･･ ]
 *
 * これらの形式は、#startTag( String ) , #startCss( String ) , #startJson() , #startArray()
 * メソッドで始まり、#make() で、完了します。
 * 完了とは、内部の StringBuilder に書き込まれることを意味しますので、再び、
 * startXXX()メソッドから始めることが可能です。
 * つまり、一つのインスタンスに、TAGやCSSをまとめて追記していくことが出来ます。
 * 最後に、#toString() メソッドで、内部の StringBuilder を文字列として返します。
 *
 * TagBuffer クラスの中身を全面的に見直しています。また、過去のTagBufferと互換性を取るための
 * メソッドも残していますが、順次、削除していく予定です。
 *
 * もっと高度な機能が必要であれば、{@link org.opengion.fukurou.util.Attributes } をご参照ください。
 *
 * @og.rev 7.0.1.0 (2018/10/15) 新規作成
 * @og.group ユーティリティ
 *
 * @version  7.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public final class TagBuffer {
	/** 処理中のデータ形式 */
	private enum DTYPE {		// 処理中のデータ形式
//	private static enum DTYPE {		// 処理中のデータ形式
		/** TAG形式 */	TAG ,
		/** CSS形式 */	CSS ,
		/** JSON形式 */	JSON ,
		/** ARRAY形式 */	ARY ;
	}

	// 空要素となるタグ名
	private static final Set<String> YOSO_SET = new HashSet<>( Arrays.asList( "area","base","br","col","command","embed","hr","img","input"
																				,"keygen","link","meta","param","source","track","wbr" ) );

	private final	StringBuilder tagBuf  = new StringBuilder( BUFFER_MIDDLE );		// タグ構築用のバッファ(makeでクリア)
	private final	StringBuilder bodyBuf = new StringBuilder( BUFFER_MIDDLE );		// タグ構築用のバッファ(makeでクリア)
	private final	StringBuilder makeBuf = new StringBuilder( BUFFER_MIDDLE );		// make実行時のバッファ(makeでappend)

	private 	DTYPE	dType	= DTYPE.TAG	;	// データ形式(初期値は、タグ)
	private		String	name	;				// タグ名か、セレクター
	private		char	kvsep	;				// key-val 分離文字( '=' か、':' )
	private		String	endStr	;				// 終端連結時文字( " " か、"; " か、", " など）

	private		String cacheTag	;				// TagBufferと互換性を取るための、makeTag 実行時のキャッシュ
	private		boolean	isKaraTag ;				// 空要素のタグ名かどうかの識別ﾌﾗｸﾞです。

	/**
	 * デフォルトコンストラクター
	 *
	 */
	public TagBuffer() { super(); }			// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * タグ名を指定した、タグ構築用のコンストラクター
	 *
	 * これは、TagBuffer クラスとの互換性の為に用意されたコンストラクターです。
	 *
	 * @param	name	タグの名前
	 */
	public TagBuffer( final String name ) {
		startTag( name );
	}

	/**
	 * TAG形式のデータ作成を宣言します。
	 *
	 *   TAG形式:
	 *     BODYなし  &lt;tag key1="val1" key2="val2" ･･･/&gt;
	 *     BODYあり  &lt;tag key1="val1" key2="val2" ･･･&gt;body&lt;/tag&gt;
	 *
	 * @param	name	タグの名前
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer startTag( final String name ) {
		return start( DTYPE.TAG , name , '='," " );
	}

	/**
	 * CSS形式のデータ作成を宣言します。
	 *
	 *   CSS形式:
	 *     { key1:val1; key2:val2; ･･･ }
	 *
	 * @param	name	CSSのセレクター
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer startCss( final String name ) {
		return start( DTYPE.CSS , name , ':' , "; " );
	}

	/**
	 * JSON形式のデータ作成を宣言します。
	 *
	 *   JSON形式:
	 *     文字  { "key1":"val1", "key2":"val2", ･･･ }
	 *     数値  { "key1":num1  , "key2":num2  , ･･･ }
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer startJson() {
		return start( DTYPE.JSON , null , ':' , ", " );		//name は使いません。
	}

	/**
	 * ARRAY形式のデータ作成を宣言します。
	 *
	 *   ARRAY形式:
	 *     文字  [ "val1", "val2", ･･･ ]
	 *     数値  [  num1 ,  num2 , ･･･ ]
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer startArray() {
		return start( DTYPE.ARY , null , ' ' , ", " );		// name , kvsep は使いません。
	}

	/**
	 * 指定のデータ形式の開始を宣言します。
	 *
	 * このメソッドは、#startTag( String ) , #startCss( String ) , #startJson() , #startArray() から
	 * 呼ばれる集約メソッドです。
	 * 各処理に必要な情報は、呼び出し元で設定しています。
	 *
	 * @param	type	 タグの種類を示す enum DTYPE { TAG , CSS , JSON , ARY ; }
	 * @param	name	 タグの名前か、CSSのセレクター
	 * @param	kvsep	 key-val 分離文字( '=' か、':' )
	 * @param	endStr	 終端連結時文字( " " か、"; " か、", " など）
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	private TagBuffer start( final DTYPE type , final String name , final char kvsep , final String endStr ) {
		this.dType	= type;
		this.name	= name;
		this.kvsep	= kvsep;
		this.endStr	= endStr;

		// 空要素かどうかを判定します。
		isKaraTag	= name != null && YOSO_SET.contains( name.toLowerCase( Locale.JAPAN ) );

		return this;
	}

	/**
	 * データ本体のバッファに直接値を追加します。
	 *
	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
	 *
	 * 引数が null や、空文字列の場合は、何もしません。
	 *
	 * @param	vals	データ本体のバッファに直接追加する可変長文字列
	 * @return	自分自身
	 * @see		#addBuffer( String , boolean )
	 * @og.rtnNotNull
	 */
	public TagBuffer addBuffer( final String... vals ) {
		if( vals != null ) {
			for( final String val : vals ) {
				if( StringUtil.isNotNull( val ) ) {
					makeBuf.append( val ).append( ' ' );
				}
			}
		}

		return this ;
	}

	/**
	 * データ本体のバッファに直接値を追加します。
	 *
	 * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
	 *
	 * 引数が null や、空文字列の場合は、何もしません。
	 *
	 * @param	val	データ本体のバッファに直接追加する文字列
	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
	 * @return	自分自身
	 * @see		#addBuffer( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addBuffer( final String val , final boolean isUse ) {
		return isUse ? addBuffer( val ) : this ;
	}

//	/**
//	 * データ本体のバッファに直接値を追加します。
//	 *
//	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
//	 * データ形式以外の文字列を前後、間に追記したい場合に、都度呼びます。
//	 *
//	 * 引数が null や、空文字列の場合は、何もしません。
//	 *
//	 * @param	vals	データ本体のバッファに直接追加する文字列配列
//	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
//	 * @return	自分自身
//	 * @see		#addBuffer( String... )
//	 * @og.rtnNotNull
//	 */
//	public TagBuffer addBuffer( final String[] vals , final boolean isUse ) {
//		if( isUse && vals != null ) {
//			for( final String val : vals ) {
//				if( StringUtil.isNotNull( val ) ) {
//					makeBuf.append( val ).append( ' ' );
//				}
//			}
//		}
//
//		return this ;
//	}

	/**
	 * データの登録エリアのバッファに直接値を追加します。
	 *
	 * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
	 * 近い将来、削除します。
	 *
	 * @param	val	データの登録エリアのバッファに直接追加する文字列
	 * @return	自分自身
	 * @see		#addBody( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String val ) {
		return addOptions( val );
	}

	/**
	 * データの登録エリアのバッファに直接値を追加します。
	 *
	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * これは、データ生成中のバッファに対して、追加を行います。
	 * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
	 *
	 * 引数が null や、空文字列の場合は、何もしません。
	 *
	 * @param	vals	データの登録エリアのバッファに直接追加する可変長文字列
	 * @return	自分自身
	 * @see		#addOptions( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addOptions( final String... vals ) {
		if( vals != null ) {
			for( final String val : vals ) {
				if( StringUtil.isNotNull( val ) ) {
					tagBuf.append( val ).append( ' ' );
				}
			}
		}

		return this ;
	}

	/**
	 * データの登録エリアのバッファに直接値を追加します。
	 *
	 * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * これは、データ生成中のバッファに対して、追加を行います。
	 * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
	 *
	 * 引数が null や、空文字列の場合は、何もしません。
	 *
	 * @param	val	データの登録エリアのバッファに直接追加する文字列
	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
	 * @return	自分自身
	 * @see		#addOptions( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addOptions( final String val , final boolean isUse ) {
		return isUse ? addOptions( val ) : this ;
	}

//	/**
//	 * データの登録エリアのバッファに直接値を追加します。
//	 *
//	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
//	 * これは、データ生成中のバッファに対して、追加を行います。
//	 * 例えば、外部で、タグやCSSのデータ形式を作成済みで、その文字列を追記するなどです。
//	 *
//	 * 引数が null や、空文字列の場合は、何もしません。
//	 *
//	 * @param	vals	データの登録エリアのバッファに直接追加する文字列配列
//	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
//	 * @return	自分自身
//	 * @see		#addOptions( String... )
//	 * @og.rtnNotNull
//	 */
//	public TagBuffer addOptions( final String[] vals , final boolean isUse ) {
//		if( isUse && vals != null ) {
//			for( final String val : vals ) {
//				if( StringUtil.isNotNull( val ) ) {
//					tagBuf.append( val ).append( ' ' );
//				}
//			}
//		}
//
//		return this ;
//	}

	/**
	 * データの BODY部のバッファに直接値を追加します（データ形式 TAGのみ有効）。
	 *
	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * これは、データの BODY部のバッファに対して、追加を行います。
	 * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
	 * このデータが登録されたかどうかで判定しています。
	 *
	 * 引数が null の場合は、何もしません。
	 *
	 * @param	vals	データのBODY部のバッファに直接追加する可変長文字列
	 * @return	自分自身
	 * @see		#addOptions( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addBody( final String... vals ) {
		if( dType == DTYPE.TAG && vals != null ) {
			bodyBuf.append( String.join( " ",vals ) );

	//		for( final String val : vals ) {
	//			if( val != null ) {
	//				bodyBuf.append( val ).append( ' ' );
	//			}
	//		}
		}

		return this ;
	}

	/**
	 * データの BODY部のバッファに直接値を追加します（データ形式 TAGのみ有効）。
	 *
	 * 指定の文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
	 * これは、データの BODY部のバッファに対して、追加を行います。
	 * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
	 * このデータが登録されたかどうかで判定しています。
	 *
	 * 引数が null や、空文字列の場合は、何もしません。
	 *
	 * @param	val	データのBODY部のバッファに直接追加する文字列
	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
	 * @return	自分自身
	 * @see		#addOptions( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addBody( final String val , final boolean isUse ) {
		return isUse ? addBody( val ) : this ;
	}

//	/**
//	 * データの BODY部のバッファに直接値を追加します（データ形式 TAGのみ有効）。
//	 *
//	 * 指定の可変長文字列は、何の加工も無く、' '(スペース)で区切られて、直接追加されます。
//	 * これは、データの BODY部のバッファに対して、追加を行います。
//	 * BODY部は、タグのbody として使用されているデータで、BODY有り無しの判定は、
//	 * このデータが登録されたかどうかで判定しています。
//	 *
//	 * 引数が null や、空文字列の場合は、何もしません。
//	 *
//	 * @param	vals	データのBODY部のバッファに直接追加する文字列配列
//	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
//	 * @return	自分自身
//	 * @see		#addOptions( String... )
//	 * @og.rtnNotNull
//	 */
//	public TagBuffer addBody( final String[] vals , final boolean isUse ) {
//		if( isUse && dType == DTYPE.TAG && vals != null ) {
//			for( final String val : vals ) {
//				if( StringUtil.isNotNull( val ) ) {
//					bodyBuf.append( val ).append( ' ' );
//				}
//			}
//		}
//
//		return this ;
//	}

	/**
	 * データの登録エリアに、数値型として値を追加します（データ形式 ARRAYのみ有効）。
	 *
	 * データが、数値型の場合、ダブルコーテーションは付けません。
	 * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
	 * ARRAY形式のみ有効なので、endStrは、',' になります。
	 * データが、null の場合、endStr(終端連結時文字)のみが、追加されますので、
	 * データの個数は、変わりません。
	 * 最後に必ず、endStr(終端連結時文字)が付きます。
	 *
	 * @param	vals	データの登録エリアに、数値型として追加する可変長文字列
	 * @return	自分自身
	 * @see		#addNum( String,boolean )
	 * @og.rtnNotNull
	 */
	public TagBuffer addNum( final String... vals ) {
		if( dType == DTYPE.ARY && vals != null ) {
			for( final String val : vals ) {
				if( val == null ) {
					tagBuf.append( endStr );
				}
				else {
					tagBuf.append( val ).append( endStr );
				}
			}
		}

		return this ;
	}

	/**
	 * データの登録エリアに、数値型として値を追加します（データ形式 ARRAYのみ有効）。
	 *
	 * データが、数値型の場合、ダブルコーテーションは付けません。
	 * 指定の文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
	 * ARRAY形式のみ有効なので、endStrは、',' になります。
	 * データが、null の場合、endStr(終端連結時文字)のみが、追加されます。
	 * 最後に必ず、endStr(終端連結時文字)が付きます。
	 *
	 * @param	val	データの登録エリアに、数値型として追加する文字列
	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
	 * @return	自分自身
	 * @see		#addNum( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addNum( final String val , final boolean isUse ) {
		return isUse ? addNum( val ) : this ;
	}

//	/**
//	 * データの登録エリアに、数値型として値を追加します（データ形式 ARRAYのみ有効）。
//	 *
//	 * データが、数値型の場合、ダブルコーテーションは付けません。
//	 * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
//	 * ARRAY形式のみ有効なので、endStrは、',' になります。
//	 * データが、null の場合、endStr(終端連結時文字)のみが、追加されますので、
//	 * データの個数は、変わりません。
//	 * 最後に必ず、endStr(終端連結時文字)が付きます。
//	 *
//	 * @param	vals	データの登録エリアに、数値型として追加する文字列配列
//	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
//	 * @return	自分自身
//	 * @see		#addNum( String... )
//	 * @og.rtnNotNull
//	 */
//	public TagBuffer addNum( final String[] vals , final boolean isUse ) {
//		if( isUse && dType == DTYPE.ARY && vals != null ) {
//			for( final String val : vals ) {
//				if( val == null ) {
//					tagBuf.append( endStr );
//				}
//				else {
//					tagBuf.append( val ).append( endStr );
//				}
//			}
//		}
//
//		return this ;
//	}

	/**
	 * データの登録エリアに、文字型として値を追加します（データ形式 ARRAYのみ有効）。
	 *
	 * データが、文字型の場合、ダブルコーテーションを付けて登録します。
	 * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
	 * ARRAY形式のみ有効なので、endStrは、',' になります。
	 * データが、null の場合、""（空文字列）と、endStr(終端連結時文字)が、追加されますので、
	 * データの個数は、変わりません。
	 * 最後に必ず、endStr(終端連結時文字)が付きます。
	 *
	 * @param	vals	データの登録エリアに、文字型として追加する可変長文字列
	 * @return	自分自身
	 * @see		#addStr( String,boolean )
	 * @og.rtnNotNull
	 */
	public TagBuffer addStr( final String... vals ) {
		if( dType == DTYPE.ARY && vals != null ) {
			for( final String val : vals ) {
				if( val == null ) {
					tagBuf.append( "\"\"" ).append( endStr );
				}
				else {
					tagBuf.append( '"' ).append( val ).append( '"' ).append( endStr );
				}
			}
		}

		return this ;
	}

	/**
	 * データの登録エリアに、文字型として値を追加します（データ形式 ARRAYのみ有効）。
	 *
	 * データが、文字型の場合、ダブルコーテーションを付けて登録します。
	 * 指定の文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
	 * ARRAY形式のみ有効なので、endStrは、',' になります。
	 * データが、null の場合、""（空文字列）と、endStr(終端連結時文字)が、追加されます。
	 * 最後に必ず、endStr(終端連結時文字)が付きます。
	 *
	 * @param	val	データの登録エリアに、文字型として追加する文字列
	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
	 * @return	自分自身
	 * @see		#addStr( String... )
	 * @og.rtnNotNull
	 */
	public TagBuffer addStr( final String val , final boolean isUse ) {
		return isUse ? addStr( val ) : this ;
	}

//	/**
//	 * データの登録エリアに、文字型として値を追加します（データ形式 ARRAYのみ有効）。
//	 *
//	 * データが、文字型の場合、ダブルコーテーションを付けて登録します。
//	 * 指定の可変長文字列は、何の加工も無く、endStr(終端連結時文字)で区切られて、追加されます。
//	 * ARRAY形式のみ有効なので、endStrは、',' になります。
//	 * データが、null の場合、""（空文字列）と、endStr(終端連結時文字)が、追加されますので、
//	 * データの個数は、変わりません。
//	 * 最後に必ず、endStr(終端連結時文字)が付きます。
//	 *
//	 * @param	vals	データの登録エリアに、文字型として追加する文字列配列
//	 * @param	isUse	追加処理を行うかどうかの判定(true:処理する/false:処理しない)
//	 * @return	自分自身
//	 * @see		#addStr( String... )
//	 * @og.rtnNotNull
//	 */
//	public TagBuffer addStr( final String[] vals , final boolean isUse ) {
//		if( isUse && dType == DTYPE.ARY && vals != null ) {
//			for( final String val : vals ) {
//				if( val == null ) {
//					tagBuf.append( "\"\"" ).append( endStr );
//				}
//				else {
//					tagBuf.append( '"' ).append( val ).append( '"' ).append( endStr );
//				}
//			}
//		}
//
//		return this ;
//	}

	/**
	 * キーと値のセットを、追加します。
	 *
	 * key が nullか、ゼロ文字列の場合は、なにもしません。
	 * val が null の場合は、なにもしません。
	 * val が、ゼロ文字列の場合は、追加します
	 *
	 * val に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
	 * でくくります。
	 * 両方含まれている場合は、シングルコーテーションをエスケープ文字(&amp;#39;)に変換します。
	 *
	 * @param	key	 属性キー(nullか、ゼロ文字列の場合は、なにもしません)
	 * @param	val	 属性値 (null の場合は、なにもしない)
	 *
	 * @return	自分自身
	 * @see		#add( String , String , boolean )
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String key,final String val ) {
		return add( key,val,true );
	}

	/**
	 * キー配列と値配列のセットを、追加します。
	 *
	 * これは、配列の数だけ、#add( boolean , String , String ) を呼び出して処理を行います。
	 * 個々の配列内のkey,valは、先のメソッドの規約に従います。
	 * キー配列と値配列のサイズが異なる場合や、配列が null の場合は、何もしません。
	 * （ｴﾗｰではなく何もしないのでご注意ください。）
	 *
	 * @param	keys	 属性キー配列
	 * @param	vals	 属性値配列
	 *
	 * @return	自分自身
	 * @see		#add( String , String , boolean )
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String[] keys,final String[] vals ) {
		if( keys != null && vals != null && keys.length == vals.length ) {
			for( int i=0; i<keys.length; i++ ) {
				add( keys[i] , vals[i] , true );
			}
		}

		return this ;
	}

	/**
	 * キーと値のセットを、追加します（データ形式 ARRAY以外有効）。
	 *
	 * key が nullか、ゼロ文字列の場合は、なにもしません。
	 * val が null の場合は、なにもしません。
	 * val が、ゼロ文字列の場合は、追加します
	 * isUse が、false の場合は、なにもしません。
	 *
	 * val に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
	 * でくくります。
	 * 両方含まれている場合は、シングルコーテーションをエスケープ文字(&amp;#39;)に変換します。
	 *
	 * isUse に、false を指定すると、属性は追加しません。これは、if( isUse ) { tagBuf.add( key,val ); }
	 * の様な判定処理を、tagBuf.add( key,val,isUse ); とすることで、StringBuilderの様に、連結して使用できます。
	 *
	 * 値 に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
	 * でくくります。
	 * 両方含まれている場合は、シングルコーテーションをエスケープ文字に変換します。
	 *
	 * @param	key	 	属性キー(nullか、ゼロ文字列の場合は、なにもしません)
	 * @param	val	 	属性値 (null の場合は、なにもしない)
	 * @param	isUse	属性を追加かどうかを決めるフラグ(true:追加する/false:何もしない)
	 *
	 * @return	自分自身
	 * @see		#add( String , String )
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String key , final String val , final boolean isUse ) {
		if( isUse && dType != DTYPE.ARY && StringUtil.isNotNull( key ) && val != null ) {
			if( dType == DTYPE.JSON ) {					// JSON の場合は、keyにﾀﾞﾌﾞﾙｸｵｰﾄを付けます。
				tagBuf.append( '"' ).append( key ).append( '"' ).append( kvsep );
			}
			else {
				tagBuf.append( key ).append( kvsep );	// TAG,CSS
			}

			if( dType == DTYPE.CSS ) {					// CSS の場合は、属性にﾀﾞﾌﾞﾙｸｵｰﾄは付けません。
				tagBuf.append( val );
			}
			else {
				// ダブルコーテーションがある場合は、シングルコーテーションで囲う。
				if( val.indexOf( '"' ) >= 0 ) {
					// ただし、シングルコーテーションを含む場合は、エスケープ文字に置き換える。
					tagBuf.append( '\'' )
						.append( val.indexOf( '\'' ) >= 0 ? val.replaceAll( "'","&#39;" ) : val )
						.append( '\'' );
				}
				else {
					tagBuf.append( '"' ).append( val ).append( '"' );
				}
			}
			tagBuf.append( endStr );
		}

		return this ;
	}

	/**
	 * 整形されたデータ文字列を 作成します。
	 * このメソッドは、startXXX で開始さえたデータ形式に応じて、データ本体のバッファに追記します。
	 * 一連のデータ作成処理は、この、#make() メソッドで終了します。
	 *
	 * その後、startXXX を呼び出すことで、新しいデータ形式の作成を開始できます。
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer make() {
		if( dType == DTYPE.TAG ) {						// TAG の場合
			if( name == null ) {
				makeBuf.append( tagBuf );
			}
			else {
				makeBuf.append( '<' ).append( name ).append( ' ' ).append( tagBuf );

				if( isKaraTag ) {						// 空要素の場合
	//			if( bodyBuf.length() == 0 ) {			// BODY がない
	//				makeBuf.append( "/>" );
					makeBuf.append( '>' );				// XHTML 以外の文法
				}
				else {
					makeBuf.append( '>' )
						.append( bodyBuf )
						.append( "</" ).append( name ).append( '>' );
				}
			}
		}
		else if( dType == DTYPE.CSS ) {					// CSS の場合
			if( name == null ) {
				makeBuf.append( tagBuf );
			}
			else {
				makeBuf.append( name ).append( " { " ).append( tagBuf ).append( " } " );
			}
		}
		else if( dType == DTYPE.JSON ) {				// JSON の場合
			makeBuf.append( " { " ).append( tagBuf ).append( " }, " );
		}
		else if( dType == DTYPE.ARY ) {					// ARY の場合
			makeBuf.append( " [ " ).append( tagBuf ).append( " ], " );
		}

		tagBuf.setLength( 0 );		// StringBuilder のクリア
		bodyBuf.setLength( 0 );		// StringBuilder のクリア

		return this ;
	}

	/**
	 * TAG形式のデータの、要素部分(BODYの直前まで)の文字列を作成して返します。
	 *
	 * これは、TAG形式で、BODY部分が、別途処理が必要な場合、要素部分まで作成したい場合に使用します。
	 * また、TAG形式以外の場合は、データの登録エリアのバッファをそのまま文字列にして返します。
	 *
	 * この処理を実行すると、内部のすべてのバッファがクリアされます。
	 * 唯一、nameや、区切り文字情報は残っていますので、続きから、BODY の登録や、次のタグの登録が可能です。
	 * 最後は、#toAfter() メソッドを実行すれば、タグとしての形式を保つことが可能です。
	 *
	 * @return	要素部分(BODYの直前まで)の文字列
	 * @see		#toAfter()
	 * @og.rtnNotNull
	 */
	public String toBefore() {
		if( dType == DTYPE.TAG && name != null ) {
			makeBuf.append( '<' ).append( name ).append( ' ' ).append( tagBuf ).append( '>' );
		}
		else {
			makeBuf.append( tagBuf );
		}

		final String rtn = makeBuf.toString();

		clear();					// すべての内部情報をクリアします。

		return rtn;
	}

	/**
	 * TAG形式のデータの、BODY＋終了タグの文字列を作成して返します。
	 *
	 * 通常は、#toBefore() を呼んだ後に、最後に実行するメソッドです。
	 * この処理を実行すると、内部のすべてのバッファがクリアされます。
	 *
	 * @return	要素部分(BODYの直前まで)の文字列
	 * @see		#toBefore()
	 * @og.rtnNotNull
	 */
	public String toAfter() {
		if( dType == DTYPE.TAG && name != null ) {
				makeBuf.append( bodyBuf ).append( "</" ).append( name ).append( '>' );
		}
		else {
			makeBuf.append( bodyBuf );
		}

		final String rtn = makeBuf.toString();

		clear();					// すべての内部情報をクリアします。

		return rtn;
	}

	/**
	 * 内部データのバッファをすべてクリアします。
	 *
	 * 内部データには、データ登録バッファ、データBODY部バッファ、データ本体バッファと
	 * ３種類のバッファを持っています。
	 *
	 * この、３種類のバッファすべてをクリアします。
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer clear() {
		tagBuf.setLength( 0 );		// StringBuilder のクリア
		bodyBuf.setLength( 0 );		// StringBuilder のクリア
		makeBuf.setLength( 0 );		// StringBuilder のクリア

		return this ;
	}

	/**
	 * 内部データの文字列を返します。
	 *
	 * データ本体のバッファを文字列に変換して返します。
	 * これは、#make() か、#tagBefore() を実行後に行います。そうしないと、
	 * データが設定されていません。
	 * この処理を行っても、データはクリアされないため、再び処理を行うことが可能です。
	 * 中間のデータを確認する場合や、最後にデータを取り出す場合に利用します。
	 *
	 * @return	データ本体の文字列
	 */
	@Override
	public String toString() {
		return makeBuf.toString();
	}

	/**
	 * 内部データの文字列を返します。
	 *
	 * return tagBuf.make().toString(); と同じ結果を返します。
	 *
	 * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
	 * 内部にキャッシュとして持つため、最初に一度実行すると、以降、同じ値しか返されません。
	 *
	 * @return	データ本体の文字列
	 */
	public String makeTag() {
		if( cacheTag == null ) {
			cacheTag = make().toString();
		}

		return cacheTag;
	}

	/**
	 * 行番号付きのタグの 整形された文字列を 作成します。
	 *
	 * 内部データから生成された文字列に含まれる、[I] に、行番号を、
	 * [V] に、設定値を埋め込みます。
	 *
	 * これは、TagBuffer クラスとの互換性の為に用意されたメソッドです。
	 * 内部にキャッシュとして持つため、最初に一度実行すると、以降、同じ値しか返されません。
	 *
	 * 逆に、何度実行しても、内部変数は書き換えられませんので、
	 * 行番号と、設定値を替えて、連続で呼ぶことが可能です。
	 *
	 * @param	rowNo	行番号([I] 文字列を変換します)
	 * @param	val		設定値([V] 文字列を変換します)
	 *
	 * @return	行番号と設定値が置き換えられた文字列
	 */
	public String makeTag( final int rowNo,final String val ) {
		if( cacheTag == null ) {
			cacheTag = make().toString();
		}

		return cacheTag.replace( "[I]",String.valueOf( rowNo ) ).replace( "[V]",val );

	//	String tag = makeBuf.toString();
	//	tag = StringUtil.replace( tag,"[I]",String.valueOf( rowNo ) );
	//	tag = StringUtil.replace( tag,"[V]",val );

	//	return tag ;
	}
}
