/*
 * 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 org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.security.HybsCryptography;
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

/**
 * XHTMLTag.java は、共通的に使用されるHTMLタグの生成メソッドを集約したクラスです。
 *
 * 全変数／メソッドは、public static final 宣言されています。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class XHTMLTag {

	/** URLチェックキー発行用 4.3.7.1 (2009/06/08) */
	private static final HybsCryptography HYBS_CRYPTOGRAPHY = new HybsCryptography(); // 4.3.7.0 (2009/06/01)

	/**
	 * BUTTON タグの属性リストです。
	 *
	 * @og.rev 5.7.1.0 (2013/12/06) HTML5関連の属性を追加
	 */
	private static final String[]
		BUTTON_KEY =  { "type","name","value","onClick"
						,"id","class","lang","dir","title","style","xml:lang"
						,"disabled","tabindex","accesskey"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
						// 5.7.1.0 (2013/12/06) HTML5関連の属性
						,"autofocus"
					};

	/**
	 * INPUT タグの属性リストです。
	 *
	 * なお、name と value は、この属性ﾘｽﾄに含めていません。
	 * これは、#inputAttri( Attributes ) メソッドで、name 属性や value 属性など、
	 * 一般に都度変更されるフィールド以外を、固定値として作成しておくためです。
	 *
	 * @og.rev 5.7.1.0 (2013/12/06) HTML5関連の属性を追加
	 */
	private static final String[]
		INPUT_KEY = { "type","size","maxlength","checked","src"
						,"alt","accept","usemap","ismap"
						,"id","class","lang","dir","title","style","xml:lang"
						,"readonly","disabled","tabindex","accesskey","onClick","onChange"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
						,"onSelect","onKeydown","onKeypress","onKeyup"
						// 5.7.1.0 (2013/12/06) HTML5関連の属性
						,"autocomplete","autofocus","pattern","placeholder","list","min","max","step","required"
					};

	/**
	 * TEXTAREA タグの属性リストです。
	 *
	 * @og.rev 5.7.1.0 (2013/12/06) HTML5関連の属性を追加
	 */
	private static final String[]
		TEXTAREA_KEY = { "name","rows","cols"
						,"id","class","lang","dir","title","style","xml:lang"
						,"readonly","disabled","tabindex","accesskey","onClick"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
						,"onSelect","onKeydown","onKeypress","onKeyup"
						// 5.7.1.0 (2013/12/06) HTML5関連の属性
						,"autofocus","placeholder"
					};

	/**
	 * LINK タグの属性リストです。
	 *
	 * href属性は定義されていません。
	 * これは、UrlEncode 等行う必要がある為、別に処理する必要がある為です。
	 */
	private static final String[]
		LINK_KEY = { "type","name","hreflang","rel","rev","charset"
						,"target","shape","coords","onClick"
						,"id","class","lang","dir","title","style","xml:lang"
						,"tabindex","accesskey"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
					};

	/**
	 * SELECT タグの属性リストです。
	 *
	 * name 属性が無いのは、input と同様、name 属性のみ入れ替える事がある為。
	 *
	 * @og.rev 5.7.1.0 (2013/12/06) HTML5関連の属性を追加
	 */
	private static final String[]
		SELECT_KEY = { "size","multiple",
						"id","class","lang","dir","title","style","xml:lang"
						,"disabled","tabindex","onClick","onChange"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
						,"onSelect","onKeydown","onKeypress","onKeyup"
						// 5.7.1.0 (2013/12/06) HTML5関連の属性
						,"autofocus"
					};

	/**
	 * OPTION タグの属性リストです。
	 *
	 */
	private static final String[]
		OPTION_KEY = { "value","label","selected"
						,"id","class","lang","dir","title","style","xml:lang"
						,"disabled"
					};

	/**
	 * FRAME タグの属性リストです。
	 *
	 */
	private static final String[]
		FRAME_KEY = { "name","longdesc","marginwidth","marginheight","noresize"
						,"scrolling","frameborder"
						,"id","class","title","style"
					};

	/**
	 * IFRAME タグの属性リストです。
	 *
	 * @og.rev 5.9.1.2 (2015/10/23) 新規追加
	 */
	private static final String[]
		IFRAME_KEY = { "name","srcdoc","seamless","sandbox","width","height"
						,"marginwidth","marginheight","noresize","scrolling","frameborder"
						,"id","class","title","style"
					};

	/**
	 * IMAGE タグの属性リストです。
	 *
	 */
	private static final String[]
		IMAGE_KEY = { "src","alt","longdesc","width","height","usemap","ismap","name","onClick"
						,"align","border","hspace","vspace"		 // この行は非推奨属性です。
						,"id","class","title","style","lang","dir","xml:lang"
						,"onBlur","onFocus","ondblClick","onMouseDown","onMouseUp"
						,"onMouseMove","onMouseOut","onMouseOver"
					};

	/**
	 * FORM タグの属性リストです。
	 *
	 */
	private static final String[]
		FORM_KEY = { "action","method","enctype","accept-charset","accept","name","target"
						,"id","class","title","style","lang","dir","xml:lang"
					};

	/**
	 * SPAN タグの属性リストです。
	 *
	 * @og.rev 7.0.3.0 (2019/05/13) SPANの属性リストにonClick等の属性追加
	 */
	private static final String[]
		SPAN_KEY = { "id","class","title","style","lang","dir","xml:lang"
						,"tabindex","onClick"									// 7.0.3.0 (2019/05/13)
						,"onFocus","ondblClick","onMouseDown","onMouseUp"		// 7.0.3.0 (2019/05/13)
						,"onMouseMove","onMouseOut","onMouseOver"				// 7.0.3.0 (2019/05/13)
						,"autofocus"											// 7.0.3.0 (2019/05/13)
					};

	/**
	 * PRE タグの属性リストです。
	 *
	 */
	private static final String[]
		PRE_KEY = { "id","class","title","style","lang","dir","xml:lang" };

	/**
	 *  デフォルトコンストラクターをprivateにして、
	 *  オブジェクトの生成をさせないようにする。
	 *
	 */
	private XHTMLTag() { }

	/**
	 * ボタンを作成します。
	 *
	 * &lt;button type="形式" name="名前" value="送信文字" オプション･･･ &gt;ラベル&lt;/button&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>LabelResource.properties のキー</td></tr>
	 *   <tr><td>type="形式"</td><td>必須</td><td>submit/reset/button</td></tr>
	 *   <tr><td>value="値"</td><td>オプション</td><td>name属性と共に送信される値</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>ボタンを利用できない状態にする場合に指定</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>accesskey="ショートカットキー"</td><td>オプション</td><td>文字セット中の１文字：WindowsであればAltキーと同時使用</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>画像や文字などボタン上に表示させたいタグの文字列</td></tr>
	 * </table>
	 *
	 * 設定できる属性
	 * 形式は，
	 *  submit  送信(サブミット)
	 *  reset   リセット
	 *  button  汎用ボタン
	 * を指定します。
	 *
	 * ラベルに，HTMLテキスト(強調文字など)をはめ込むことが出来ます。
	 * また，イメージ &lt;img ････&gt; を指定することも,可能です。
	 * disabled="disabled" のとき，このボタンのデータはサーバーに送信されません。
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 汎用属性を自由に登録する事が出来ます。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  ボタンタグ文字列
	 * @og.rtnNotNull
	 */
	public static String button( final Attributes attri ) {
		final String checkedType = "|submit|reset|button|";

		final String type  = attri.get( "type" );
		if( checkedType.indexOf( "|" + type + "|" ) < 0 ) {
			final String errMsg = "button タイプ設定エラー [" + type + "]";
			throw new OgRuntimeException( errMsg );
		}

		String body = attri.get( "body" );
		if( body == null ) { body = "" ; }

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "button" )
					.add(     attri.getAttribute( BUTTON_KEY ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * 入力フォームを作成します。
	 *
	 * &lt;input type="text" name="名前" value="送信文字" ....&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>LabelResource.properties のキー</td></tr>
	 *   <tr><td>type="形式"</td><td>必須</td><td>text/password/checkbox/radio/submit/reset/button/image/file/hidden</td></tr>
	 *   <tr><td>value="値"</td><td>オプション</td><td>name属性と共に送信される値</td></tr>
	 *   <tr><td>size="30"</td><td>オプション</td><td>inputタグの大きさ</td></tr>
	 *   <tr><td>maxlength="50"</td><td>オプション</td><td>type属性が｢text｣,｢password｣ のときの最大文字数</td></tr>
	 *   <tr><td>checked="checked"</td><td>オプション</td><td>type属性が｢checkbox｣,｢radio｣ の場合に選択されている状態にする。</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>選択や変更の操作をできない状態にする場合に指定</td></tr>
	 *   <tr><td>accept="MIMEタイプ"</td><td>オプション</td><td>type属性が｢file｣の場合に処理可能なMIMEタイプを指定</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>accesskey="ショートカットキー"</td><td>オプション</td><td>文字セット中の１文字：WindowsであればAltキーと同時使用</td></tr>
	 *   <tr><td>src="URL"</td><td>オプション</td><td>type属性が｢image｣の場合送信ボタンの画像URLを指定</td></tr>
	 *   <tr><td>alt="代替文字列"</td><td>オプション</td><td>type属性が｢image｣の場合、画像が表示できないときの代替文字列を指定</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>画像や文字などボタン上に表示させたいタグの文字列</td></tr>
	 *   <tr><td>サポート外</td><td>未実装</td><td>readonly属性、usemap属性、ismap属性、align属性</td></tr>
	 * </table>
	 *
	 * 設定できるtype属性
	 *  text       １行のテキストフィールド
	 *  password   パスワード用テキストフィールド
	 *  checkbox   チェックボックス(複数選択可)
	 *  radio      ラジオボタン(複数選択不可)
	 *  file       送信ファイルの選択
	 *  hidden     表示せずにサーバーに送信する。
	 *  submit     送信(サブミット)
	 *  reset      リセット
	 *  button     汎用ボタン
	 *  image      イメージによる画像ボタン
	 *
	 * HTML5 より、以下のtypeが指定可能になりました。(ブラウザによってサポート状況は異なります。)
	 *  search     検索テキストの入力欄を作成する
	 *  tel        電話番号の入力欄を作成する
	 *  url        URLの入力欄を作成する
	 *  email      メールアドレスの入力欄を作成する
	 *  datetime   UTC（協定世界時）による日時の入力欄を作成する
	 *  date       日付の入力欄を作成する
	 *  month      月の入力欄を作成する
	 *  week       週の入力欄を作成する
	 *  time       時間の入力欄を作成する
	 *  datetime-local    UTC（協定世界時）によらないローカル日時の入力欄を作成する
	 *  number     数値の入力欄を作成する
	 *  range      レンジの入力欄を作成する
	 *  color      色の入力欄を作成する
	 *
	 * ラジオボタン／チェックボックスであらかじめ,チェックをして
	 * おきたい場合は,checked 属性に "checked" を登録します。
	 * ファイルダイアログの場合は,attributesの accept 属性に "MIMEタイプ"
	 * を登録します。
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 * タブで移動順を指定する tabindex="タブ順"
	 * ショートカットキーを割り当てる accesskey="ショートカットキー"
	 *
	 * @param   attri 属性群
	 *
	 * @return  入力フォームタグ文字列
	 * @og.rtnNotNull
	 * @see     #input( Attributes attri,String name,String value,String optAtt )
	 */
	public static String input( final Attributes attri ) {
		final String name     = attri.get( "name" );
		final String value    = attri.get( "value" );
		final String optAttri = attri.get( "optionAttributes" );

		return input( attri,name,value,optAttri );
	}

	/**
	 * 入力フォームを作成します。
	 *
	 * &lt;input type="text" name="名前" value="送信文字" ....&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>LabelResource.properties のキー</td></tr>
	 *   <tr><td>type="形式"</td><td>必須</td><td>text/password/checkbox/radio/submit/reset/button/image/file/hidden</td></tr>
	 *   <tr><td>value="値"</td><td>オプション</td><td>name属性と共に送信される値</td></tr>
	 *   <tr><td>size="30"</td><td>オプション</td><td>inputタグの大きさ</td></tr>
	 *   <tr><td>maxlength="50"</td><td>オプション</td><td>type属性が｢text｣,｢password｣ のときの最大文字数</td></tr>
	 *   <tr><td>checked="checked"</td><td>オプション</td><td>type属性が｢checkbox｣,｢radio｣ の場合に選択されている状態にする。</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>選択や変更の操作をできない状態にする場合に指定</td></tr>
	 *   <tr><td>accept="MIMEタイプ"</td><td>オプション</td><td>type属性が｢file｣の場合に処理可能なMIMEタイプを指定</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>accesskey="ショートカットキー"</td><td>オプション</td><td>文字セット中の１文字：WindowsであればAltキーと同時使用</td></tr>
	 *   <tr><td>src="URL"</td><td>オプション</td><td>type属性が｢image｣の場合送信ボタンの画像URLを指定</td></tr>
	 *   <tr><td>alt="代替文字列"</td><td>オプション</td><td>type属性が｢image｣の場合、画像が表示できないときの代替文字列を指定</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>画像や文字などボタン上に表示させたいタグの文字列</td></tr>
	 *   <tr><td>サポート外</td><td>未実装</td><td>readonly属性、usemap属性、ismap属性、align属性</td></tr>
	 * </table>
	 *
	 * 設定できるtype属性
	 *  text       １行のテキストフィールド
	 *  password   パスワード用テキストフィールド
	 *  checkbox   チェックボックス(複数選択可)
	 *  radio      ラジオボタン(複数選択不可)
	 *  file       送信ファイルの選択
	 *  hidden     表示せずにサーバーに送信する。
	 *  submit     送信(サブミット)
	 *  reset      リセット
	 *  button     汎用ボタン
	 *  image      イメージによる画像ボタン
	 *
	 * HTML5 より、以下のtypeが指定可能になりました。(ブラウザによってサポート状況は異なります。)
	 *  search     検索テキストの入力欄を作成する
	 *  tel        電話番号の入力欄を作成する
	 *  url        URLの入力欄を作成する
	 *  email      メールアドレスの入力欄を作成する
	 *  datetime   UTC（協定世界時）による日時の入力欄を作成する
	 *  date       日付の入力欄を作成する
	 *  month      月の入力欄を作成する
	 *  week       週の入力欄を作成する
	 *  time       時間の入力欄を作成する
	 *  datetime-local    UTC（協定世界時）によらないローカル日時の入力欄を作成する
	 *  number     数値の入力欄を作成する
	 *  range      レンジの入力欄を作成する
	 *  color      色の入力欄を作成する
	 *
	 * ラジオボタン／チェックボックスであらかじめ,チェックをして
	 * おきたい場合は,checked 属性に "checked" を登録します。
	 * ファイルダイアログの場合は,attributesの accept 属性に "MIMEタイプ"
	 * を登録します。
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 * タブで移動順を指定する tabindex="タブ順"
	 * ショートカットキーを割り当てる accesskey="ショートカットキー"
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 * @og.rev 6.2.2.2 (2015/04/03) NO_MAXLEN キーの値が、"true" の場合、maxlength を強制削除する。
	 *
	 * @param   attri  属性群
	 * @param   name   名前
	 * @param   value  値
	 * @param   optAttri オプション文字列(タグ属性定義されていない属性の登録用文字列)
	 *
	 * @return  入力フォームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String input( final Attributes attri,final String name,final String value,final String optAttri ) {

		// 6.2.2.2 (2015/04/03) NO_MAXLEN キーの値が、"true" の場合、maxlength を強制削除する。
		if( "true".equalsIgnoreCase( attri.get( "NO_MAXLEN" ) ) ) {
			attri.remove( "maxlength" );
		}

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "input" )
					.add(	"name"  , name  )
					.add(	"value" , value )
					.add(	attri.getAttribute( INPUT_KEY ) )
					.add(	optAttri )
					.makeTag();
	}

	/**
	 * 入力フォームの属性情報のみの文字列を作成します。
	 * これは、name 属性や value 属性など、一般に都度変更されるフィールド
	 * 以外の固定的な属性情報を、先に作成しておく場合に、使用します。
	 *
	 * @og.rev 6.2.2.2 (2015/04/03) NO_MAXLEN キーの値が、"true" の場合、maxlength を強制削除する。
	 *
	 * @param   attri	属性リスト
	 *
	 * @return  入力フォームタグの属性情報文字列
	 * @og.rtnNotNull
	 */
	public static String inputAttri( final Attributes attri ) {
		// 6.2.2.2 (2015/04/03) NO_MAXLEN キーの値が、"true" の場合、maxlength を強制削除する。
		if( "true".equalsIgnoreCase( attri.get( "NO_MAXLEN" ) ) ) {
			attri.remove( "maxlength" );
		}
		return attri.getAttribute( INPUT_KEY );
	}

	/**
	 * テキストエリアの属性情報のみの文字列を作成します。
	 * これは、name 属性や value 属性など、一般に都度変更されるフィールド
	 * 以外の固定的な属性情報を、先に作成しておく場合に、使用します。
	 *
	 * @param   attri	属性リスト
	 *
	 * @return  テキストエリアの属性情報文字列
	 * @og.rtnNotNull
	 */
	public static String textareaAttri( final Attributes attri ) {
		return attri.getAttribute( TEXTAREA_KEY );
	}

	/**
	 * プルダウン等のメニューの属性情報のみの文字列を作成します。
	 * これは、name 属性や value 属性など、一般に都度変更されるフィールド
	 * 以外の固定的な属性情報を、先に作成しておく場合に、使用します。
	 *
	 * @param   attri	属性リスト
	 *
	 * @return  プルダウン等のメニューの属性情報文字列
	 * @og.rtnNotNull
	 */
	public static String selectAttri( final Attributes attri ) {
		return attri.getAttribute( SELECT_KEY );
	}

	/**
	 * HIDDEN フォームを作成します。
	 *
	 * id属性に、name と同じ値が設定されます。
	 *
	 * @og.rev 5.5.4.0 (2012/07/02) ID属性追加
	 *
	 * @param   name  フォームの名前
	 * @param   value 値
	 *
	 * @return  HIDDENフォームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String hidden( final String name,final String value ) {
		return hidden( name,value,name );
	}

	/**
	 * HIDDEN フォームを作成します。
	 *
	 * @og.rev 5.5.4.0 (2012/07/02) ID属性追加
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   name  フォームの名前
	 * @param   value 値
	 * @param   id    フォームのID
	 *
	 * @return  HIDDENフォームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String hidden( final String name, final String value, final String id ) {

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "input" )
					.add(     "type"  , "hidden"  )
					.add(     "name"  , name  )
					.add(     "value" , value )
					.add(     "id"    , id    )
					.makeTag();
	}

	/**
	 * テキストエリアを作成します。
	 *
	 * &lt;textarea name="名前" rows="4" cols="40"  ....&gt;送信文字列 &lt;/textarea&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>LabelResource.properties のキー</td></tr>
	 *   <tr><td>rows="行数"</td><td>オプション</td><td>入力フィールドの表示行数</td></tr>
	 *   <tr><td>cols="幅"</td><td>オプション</td><td>入力フィールドの表示幅(文字数)</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>選択や変更の操作をできない状態にする場合に指定</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>accesskey="ショートカットキー"</td><td>オプション</td><td>文字セット中の１文字：WindowsであればAltキーと同時使用</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>value="値"</td><td>オリジナル</td><td>name属性と共に送信される値</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>画像や文字などボタン上に表示させたいタグの文字列</td></tr>
	 *   <tr><td>サポート外</td><td>未実装</td><td>readonly属性</td></tr>
	 * </table>
	 *
	 * 設定できる属性
	 *
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 * タブで移動順を指定する tabindex="タブ順"
	 * ショートカットキーを割り当てる accesskey="ショートカットキー"
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  入力フォームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String textarea( final Attributes attri ) {
		String body   = attri.get( "body" );
		if( body == null ) { body = "" ; }

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "textarea" )
					.add(     attri.getAttribute( TEXTAREA_KEY ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * ページリンクを作成します。
	 *
	 * &lt;A href="ＵＲＬ" target="ターゲット名"&gt;ラベル&lt;/A&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>href="URL"</td><td>必須</td><td>リンク先のURLを指定します。</td></tr>
	 *   <tr><td>charset="文字セット"</td><td>オプション</td><td>リンク先の文字コードセットを指定します。</td></tr>
	 *   <tr><td>hreflang="言語セット"</td><td>オプション</td><td>リンク先の基本となる言語コードを指定します。</td></tr>
	 *   <tr><td>type="MIMEタイプ"</td><td>オプション</td><td>リンク先のMIMEタイプを指定します。</td></tr>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>この要素をリンクの到達点とするための名前を指定します。</td></tr>
	 *   <tr><td>rel="リンクタイプ"</td><td>オプション</td><td>この文書からみた href 属性で指定されるリンク先との関係</td></tr>
	 *   <tr><td>rev="リンクタイプ"</td><td>オプション</td><td>href 属性で指定されるリンク先からみた、この文書との関係</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>accesskey="ショートカットキー"</td><td>オプション</td><td>文字セット中の１文字：WindowsであればAltキーと同時使用</td></tr>
	 *   <tr><td>target="フレーム名"</td><td>オプション</td><td>リンク先のフレーム名</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>画像や文字などをリンクにできます。</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>サポート外</td><td>未実装</td><td>shape属性、coords属性</td></tr>
	 * </table>
	 *
	 * 設定できる属性
	 *
	 * ラベルなしの場合, href属性の "URL" そのものを付けます。
	 *
	 * target属性のフレーム名は
	 *
	 *  _top	フレームを解除して,リンク先をフレーム全体に表示する。
	 *  _parent リンク先を親フレームに表示する。
	 *  _self   リンク先を自分自身に表示する。
	 *  _blank  新しいウインドウを開いて，表示する。
	 *  その他  フレーム作成時の名前で指定可能。
	 *
	 * を指定します。
	 * なしの場合 _self (自分自身)を指定します。
	 *
	 * リンクメール機能
	 * URLを，mailto:メールアドレス で設定すれば,メール送信ダイアログを
	 * 開く事が出来ます。
	 * 画像リンク機能
	 * 画像をクリックするリンクは，ラベルの個所に &lt;img&gt;タグを設定します。
	 *
	 * &lt;a href="books.html"&gt;&lt;img src="banner.gif" width="468px" height="60px" alt="関連書籍紹介" border="0"&gt;&lt;/a&gt;
	 *
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 * タブで移動順を指定する tabindex="タブ順"
	 * ショートカットキーを割り当てる accesskey="ショートカットキー"
	 *
	 * @param   attri 属性群
	 *
	 * @return  ページリンクタグ文字列
	 * @og.rtnNotNull
	 */
	public static String link( final Attributes attri ) {
		return link( attri,"" );
	}

	/**
	 * ページリンクを作成します。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 * @param   urlEncode 文字列   ( ?key1=val1&amp;････ という文字列 無いときは "" )
	 *
	 * @return  ページリンクタグ文字列
	 * @og.rtnNotNull
	 */
	public static String link( final Attributes attri, final String urlEncode ) {
		final String href = attri.get( "href" );
		final String url  = addUrlEncode( href,urlEncode );
		final String body = StringUtil.nval( attri.get( "body" ), href );

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "a" )
					.add(     "href"  , url )
					.add(     attri.getAttribute( LINK_KEY ) )
					.addBody( body )							// body が null の場合、href を代わりに出力
					.makeTag();
	}

	/**
	 * xlink 形式のページリンクを作成します。
	 *
	 * 基本的には、link と同じです。アドレスの指定も、href で指定してください。
	 * 内部的に、xlink:href に変換します。
	 * また、URL引数を、"&amp;" で結合するのではなく、"&amp;amp;" で結合させます。
	 * これは、xlink そのものが、XML上に記述された場合に、XMLのルールで再度パース
	 * される為です。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 * @param   urlEncode 文字列   ( ?key1=val1&amp;････ という文字列 無いときは "" )
	 *
	 * @return  ページリンクタグ文字列
	 * @og.rtnNotNull
	 */
	public static String xlink( final Attributes attri, final String urlEncode ) {
		final String href = attri.get( "href" );
		final String url  = addUrlEncode( href,urlEncode,"&amp;" );
		final String body = StringUtil.nval( attri.get( "body" ), href );

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "a" )
					.add(     "xlink:href"  , url )
					.add(     attri.getAttribute( LINK_KEY ) )
					.addBody( body )							// body が null の場合、href を代わりに出力
					.makeTag();
	}

	/**
	 * メニューを作成します。
	 *
	 * @param   attri 属性群
	 * @param   opt	選択肢(オプション)
	 *
	 * @return  メニュータグ文字列
	 * @og.rtnNotNull
	 */
	public static String select( final Attributes attri,final Options opt ) {
		final String name     = attri.get( "name" );
		final String optAttri = attri.get( "optionAttributes" );

		return select( attri,opt,name,optAttri );
	}

	/**
	 * メニューを作成します。
	 *
	 * &lt;select size="行数" name="名前" multiple&gt;
	 *   &lt;option value="送信文字１"&gt;コメント&lt;/option&gt;
	 *   &lt;option value="送信文字２"&gt;コメント&lt;/option&gt;
	 *   &lt;option value="送信文字３" selected="selected"&gt;コメント&lt;/option&gt;
	 * &lt;/select&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>LabelResource.properties のキー</td></tr>
	 *   <tr><td>size="行数"</td><td>オプション</td><td>select要素をリストボックスとして表示する場合の行数</td></tr>
	 *   <tr><td>multiple="multiple"</td><td>オプション</td><td>選択肢の中から複数選択出来るようにする。</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>選択や変更の操作をできない状態にする場合に指定</td></tr>
	 *   <tr><td>tabindex="Tab移動順"</td><td>オプション</td><td>0～32767の範囲で数字で指定(小さい順に移動)</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 * </table>
	 *
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri	属性群
	 * @param   opt		選択肢(オプション)
	 * @param   name	名前
	 * @param   optAttri オプション属性
	 *
	 * @return  メニュータグ文字列
	 * @og.rtnNotNull
	 */
	public static String select( final Attributes attri,final Options opt,final String name,final String optAttri ) {

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "select" )
					.add(     "name"  , name )
					.add(     attri.getAttribute( SELECT_KEY ) )
					.add(     optAttri )
					.addBody( opt.getOption() )
					.makeTag();
	}

	/**
	 * オプションを作成します。
	 *
	 * &lt;select size="行数" name="名前" multiple&gt;
	 *   &lt;option value="送信文字１"&gt;コメント&lt;/option&gt;
	 *   &lt;option value="送信文字２"&gt;コメント&lt;/option&gt;
	 *   &lt;option value="送信文字３" selected="selected"&gt;コメント&lt;/option&gt;
	 * &lt;/select&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>value="値"</td><td>オプション</td><td>送信する値</td></tr>
	 *   <tr><td>selected="selected"</td><td>オプション</td><td>選択肢をあらかじめ選択された状態にしておく</td></tr>
	 *   <tr><td>disabled="disabled"</td><td>オプション</td><td>選択や変更の操作をできない状態にする場合に指定</td></tr>
	 *   <tr><td>body="表示するタグ文字列"</td><td>オリジナル</td><td>選択肢に表示させたいタグの文字列</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 * </table>
	 *
	 * セレクタとは，リストボックスやメニューなどの option引数にセットする
	 * 複数のデータをoptionタグでくるんだものです。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  オプションタグ文字列
	 * @og.rtnNotNull
	 * @see		#option( Attributes,String )
	 */
	public static String option( final Attributes attri ) {
		final String body = StringUtil.nval( attri.get( "body" ), " " );	// "No Label" を止めてスペースにする。

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "option" )
					.add(     attri.getAttribute( OPTION_KEY ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * オプションを作成します。
	 *
	 * これは、addKeyLabel引数を考慮した、オプションタグ文字列の作成処理を行います。
	 * true を指定すると、「強制的に」キー:ラベル形式で表示します。
	 * false の場合は、「強制的に」ラベルのみで表示されます。
	 * 初期値の null の場合、指定のままのラベルで作成します。
	 *
	 * @og.rev 6.0.4.0 (2014/11/28) キー:ラベル形式で表示するかどうか。新規追加
	 *
	 * @param   attri		属性群
	 * @param   addKeyLabel true:キー:ラベル形式/false:ラベルのみ/null:指定通り
	 *
	 * @return  オプションタグ文字列
	 * @og.rtnNotNull
	 * @see		#option( Attributes )
	 */
	public static String option( final Attributes attri , final String addKeyLabel ) {
		if( addKeyLabel != null ) {
			final String val = attri.get( "value" );
			String lbl = attri.get( "label" );
			String lblKey = "label" ;
			if( lbl == null ) {		// label がなければ、body が表示されるので。
				lbl = attri.get( "body" );
				lblKey = "body" ;
			}

			if( val != null && lbl != null ) {
				final boolean isKeyLbl = "true".equalsIgnoreCase( addKeyLabel );
				final boolean useKey   = lbl.startsWith( val + ':' ) ;	// すでに、ラベルにキーが付与されている。

				// addKeyLabel するが、ラベルが付与されていない。
				if( isKeyLbl && !useKey ) {
					attri.set( lblKey , val + ':' + lbl );
				}
				// addKeyLabel しないが、ラベルが付与されている。
				else if( !isKeyLbl && useKey ) {
					attri.set( lblKey , lbl.substring( (val + ':').length() ) );	// ラベルからキー部分を削除する。
				}
			}
		}

		return option( attri );
	}

	/**
	 * フレームタグを作成します。
	 *
	 * &lt;frame marginheight="2px" marginwidth="2px" src="query.jsp" name="QUERY" /&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>src="URL"</td><td>オプション</td><td>フレームの表示先URLを指定します。</td></tr>
	 *   <tr><td>name="フレーム名"</td><td>オプション</td><td>フレームに付ける名前を指定します。</td></tr>
	 *   <tr><td>longdesc="URI"</td><td>オプション</td><td>フレームの詳しい説明のURI</td></tr>
	 *   <tr><td>marginwidth="左右のマージン"</td><td>オプション</td><td>フレーム内の左右のマージンを指定します。</td></tr>
	 *   <tr><td>marginheight="上下のマージン"</td><td>オプション</td><td>フレーム内の上下のマージンを指定します。</td></tr>
	 *   <tr><td>noresize="noresize"</td><td>オプション</td><td>フレームサイズを変更できないようにします。</td></tr>
	 *   <tr><td>scrolling="スクロールの制御"</td><td>オプション</td><td>yes:スクロールバーを表示 no:表示しない auto:必要に応じて表示(デフォルト)</td></tr>
	 *   <tr><td>frameborder="枠の表示"</td><td>オプション</td><td>0:枠を表示しない  1:枠を表示する。(デフォルト)</td></tr>
	 *   <tr><td>keys="引数にセットするキー"</td><td>オプション</td><td>URI の引数にセットするキーを CSV形式でセットします。</td></tr>
	 *   <tr><td>value="引数にセットする値"</td><td>オプション</td><td>URI の引数にセットする値を CSV形式でセットします。</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style</td></tr>
	 * </table>
	 *
	 * 設定できる属性
	 *
	 * scrolling属性
	 *
	 *  yes:常にスクロールバーを表示
	 *  no:常にスクロールバーを表示しない
	 *  auto:必要に応じてスクロールバーを表示(デフォルト)
	 *
	 * を指定します。
	 *
	 * frameborder属性
	 *
	 *  0:枠を表示しない
	 *  1:枠を表示する。(デフォルト)
	 *
	 * を指定します。
	 *
	 * 属性群は,タグの中に,CSS等で使用できる class="XXX" などの
	 * 文字を自由に登録する事が出来ます。
	 * CSSでクラスを対応 class="XXXX"
	 *
	 * @param   attri 属性群
	 *
	 * @return  フレームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String frame( final Attributes attri ) {
		return frame( attri,"" );
	}

	/**
	 * フレームタグを作成します。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 * @param   urlEncode 文字列   ( ?key1=val1&amp;････ という文字列 無いときは "" )
	 *
	 * @return  フレームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String frame( final Attributes attri,final String urlEncode ) {
		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "frame" )
					.add(    "src" , addUrlEncode( attri.get( "src" ),urlEncode ) )
					.add(     attri.getAttribute( FRAME_KEY ) )
					.makeTag();
	}

	/**
	 * インラインフレームタグを作成します。
	 *
	 * @param   attri 属性群
	 * @param   urlEncode 文字列   ( ?key1=val1&amp;････ という文字列 無いときは "" )
	 *
	 * @og.rev 5.9.1.2 (2015/10/23) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) TagBuffer を使用するように変更
	 * @og.rev 6.3.9.0 (2015/11/06) TagBuffer を使用するように変更
	 * @og.rev 7.4.2.0 (2021/05/08) optionAttributes 追加
	 *
	 * @return  インラインフレームタグ文字列
	 */
	public static String iframe( final Attributes attri,final String urlEncode ) {

		return new TagBuffer( "iframe" )
					.add(    "src" , addUrlEncode( attri.get( "src" ),urlEncode ) )
					.add(     attri.getAttribute( IFRAME_KEY ) )
					.add(     attri.get( "optionAttributes" ) )					// 7.4.2.0 (2021/05/08)
					.addBody( "<!-- -->" )	// iframeは自己終了できない
					.makeTag();
	}

	/**
	 * URLエンコード文字列を作成します。
	 * エンコードすべき文字列が無い場合は, ０ストリング("") を返します。
	 * エンコード文字列がある場合は, "?KEY1=VAL1&amp;KEY2=VAL2&amp;･･･" という文字列を
	 * 返します。
	 * つまり、どちらのケースでも、URI に 連結させればよいことになります。
	 *
	 * @param   keys   URLの引数となるキー群
	 * @param   values URLの引数となる値群
	 *
	 * @return  URLエンコード文字列
	 * @og.rtnNotNull
	 */
	public static String urlEncode( final String keys,final String values ) {
		return urlEncode( keys,values,"&" );
	}

	/**
	 * URLエンコード文字列を作成します。
	 * エンコードすべき文字列が無い場合は, ０ストリング("") を返します。
	 * エンコード文字列がある場合は, "?KEY1=VAL1&amp;KEY2=VAL2&amp;･･･" という文字列を
	 * 返します。
	 * つまり、どちらのケースでも、URI に 連結させればよいことになります。
	 *
	 * @param   keys   URLの引数となるキー群
	 * @param   values URLの引数となる値群
	 * @param   join   URLの引数群を連結させる文字列
	 *
	 * @return  URLエンコード文字列
	 * @og.rtnNotNull
	 */
	public static String urlEncode( final String keys,final String values,final String join ) {
		if( keys == null || values == null ) { return ""; }

		final String[] key = StringUtil.csv2Array( keys );
		final String[] val = StringUtil.csv2Array( values );

		return urlEncode( key,val,join ) ;
	}

	/**
	 * URLエンコード文字列を作成します。
	 * エンコードすべき文字列が無い場合は, ０ストリング("") を返します。
	 * エンコード文字列がある場合は, "?KEY1=VAL1&amp;KEY2=VAL2&amp;･･･" という文字列を
	 * 返します。
	 * つまり、どちらのケースでも、URI に 連結させればよいことになります。
	 *
	 * @param   key   URLの引数となるキーの配列
	 * @param   val   URLの引数となる値の配列
	 *
	 * @return  URLエンコード文字列
	 * @og.rtnNotNull
	 */
	public static String urlEncode( final String[] key,final String[] val ) {
		return urlEncode( key,val,"&" );
	}

	/**
	 * URLエンコード文字列を作成します。
	 * エンコードすべき文字列が無い場合は, ０ストリング("") を返します。
	 * エンコード文字列がある場合は, "?KEY1=VAL1&amp;KEY2=VAL2&amp;･･･" という文字列を
	 * 返します。
	 * つまり、どちらのケースでも、URI に 連結させればよいことになります。
	 *
	 * @og.rev 5.10.15.3 (2019/09/27) fenc追加対応で作成
	 *
	 * @param   key   URLの引数となるキーの配列
	 * @param   val   URLの引数となる値の配列
	 * @param   join   URLの引数群を連結させる文字列
	 *
	 * @return  URLエンコード文字列
	 */
	public static String urlEncode( final String[] key,final String[] val,final String join ) {
		return urlEncode( key, val, join, false);
	}

	/**
	 * URLエンコード文字列を作成します。
	 * エンコードすべき文字列が無い場合は, ０ストリング("") を返します。
	 * エンコード文字列がある場合は, "?KEY1=VAL1&amp;KEY2=VAL2&amp;･･･" という文字列を
	 * 返します。
	 * つまり、どちらのケースでも、URI に 連結させればよいことになります。
	 *
	 * @og.rev 4.3.3.3 (2008/10/22) valに対して副作用を及ぼさないように修正
	 * @og.rev 5.10.12.4 (2019/06/21) keyのURIエンコードを追加
	 * @og.rev 5.10.15.3 (2019/09/27) fencフラグ追加
	 *
	 * @param   key   URLの引数となるキーの配列
	 * @param   val   URLの引数となる値の配列
	 * @param   join   URLの引数群を連結させる文字列
	 * @param   fenc  先頭が[の場合でも強制エンコードするためのフラグ(初期値false)
	 *
	 * @return  URLエンコード文字列
	 * @og.rtnNotNull
	 */
//	public static String urlEncode( final String[] key,final String[] val,final String join ) {
	public static String urlEncode( final String[] key,final String[] val,final String join, final boolean fenc ) {
		if( key == null || key.length == 0 || val == null || val.length == 0 ) {
			return "";
		}
		else if( key.length != val.length ) {
			final String errMsg = "urlEncode のキーとバリューの個数が異なります。" + CR
						+ "key.length=[" + key.length + "]  val.length=[" + val.length + "]";
			throw new OgRuntimeException( errMsg );
		}

		// 4.3.3.3 (2008/10/22)
		String[] tval = new String[val.length];

		for( int i=0; i<val.length; i++ ) {
			if( key[i] == null || key[i].isEmpty() ) { return ""; }
			if( val[i] == null || val[i].isEmpty() ) { tval[i] = ""; }
//			else if( val[i].charAt(0) == '[' ) {		// 暫定対応
			else if( val[i].charAt(0) == '[' && !fenc ) {		// 5.10.15.3 (2019/09/27) fenc追加
				tval[i] = val[i];
			}
			else {
				tval[i] = StringUtil.urlEncode( val[i] );
			}
		}

		final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );

		// 6.0.2.5 (2014/10/31) char を append する。
//		rtn.append( key[0] ).append( '=' ).append( tval[0] );
		rtn.append( StringUtil.urlEncode( key[0]) ).append( '=' ).append( tval[0] );	// 5.10.12.4 (2019/06/21)
		for( int i=1; i<key.length; i++ ) {
			rtn.append( join );
//			rtn.append( key[i] ).append( '=' ).append( tval[i] );
			rtn.append( StringUtil.urlEncode(key[i]) ).append( '=' ).append( tval[i] );	// 5.10.12.4 (2019/06/21)
		}
		return rtn.toString();
	}

	/**
	 * URL文字列に、URLエンコード文字列を連結します。
	 *
	 * URL文字列中にすでに "?" 文字が存在する場合は、URLエンコード側の
	 * 文字列とは、 "&amp;" で連結します。
	 * 逆に、"?" が存在しなければ、"?" で連結します。
	 * URLエンコード文字列が null の場合は、連結しません。
	 *
	 * @param   url	URL文字列
	 * @param   encode URLエンコード文字列
	 *
	 * @return  連結文字列
	 * @og.rtnNotNull
	 */
	public static String addUrlEncode( final String url,final String encode ) {
		return addUrlEncode( url,encode,"&" );
	}

	/**
	 * URL文字列に、URLエンコード文字列を連結します。
	 *
	 * URL文字列中にすでに "?" 文字が存在する場合は、URLエンコード側の
	 * 文字列とは、 join (例 "&amp;" ) で連結します。
	 * 逆に、"?" が存在しなければ、"?" で連結します。
	 * URLエンコード文字列が null の場合は、連結しません。
	 * 連結する、encode 文字列の先頭が、join 文字列の場合、そのまま連結します。
	 * 先頭が、そうでない場合は、join 文字列で連結します。
	 * "?" が存在せず、encode 文字列の先頭が、join 文字列の場合は、、
	 * encode 文字列の先頭を取り除いて、"?" で連結します。
	 *
	 * 例：
	 *    ①. abc.html    key1=val1&amp;key2=val2      ⇒ abc.html?key1=val1&amp;key2=val2
	 *    ②．abc.html   &amp;key1=val1&amp;key2=val2  ⇒ abc.html?key1=val1&amp;key2=val2
	 *    ③．abc.html?key1=val1    key2=val2          ⇒ abc.html?key1=val1&amp;key2=val2
	 *    ④．abc.html?key1=val1   &amp;key2=val2      ⇒ abc.html?key1=val1&amp;key2=val2
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) urlがnullの場合に、NullPointerExceptionが発生するバグを修正
	 *
	 * @param   url	URL文字列
	 * @param   encode URLエンコード文字列
	 * @param   join   URLの引数群を連結させる文字列
	 *
	 * @return  連結文字列
	 * @og.rtnNotNull
	 */
	public static String addUrlEncode( final String url,final String encode,final String join ) {
		// 5.2.1.0 (2010/10/01) urlがnullの場合に、NullPointerExceptionが発生するバグを修正
		final String tmpUrl = ( url == null ? "" : url );

		if( encode == null || encode.isEmpty() ) { return tmpUrl; }

		final String rtn ;
		if( tmpUrl.indexOf( '?' ) < 0 ) {
			if( encode.startsWith( join ) ) {
				rtn = tmpUrl + "?" + encode.substring(join.length());		// ②
			}
			else {
				rtn = tmpUrl + "?" + encode;								// ①
			}
		}
		else {
			if( encode.startsWith( join ) ) {
				rtn = tmpUrl + encode;						// ④
			}
			else {
				rtn = tmpUrl + join + encode;				// ③
			}
		}
		return rtn ;
	}

	/**
	 * 指定位置に画像を配置します。
	 *
	 * alt属性だけ設定されて、title属性が未設定の場合、title属性にもalt属性と同じ値を設定しておきます。
	 * 本来は、それぞれに役割があるので、同じ値を設定するのは行儀が悪いのですが、
	 * ブラウザの互換性など、色々な関係で、このように設定しています。
	 * alt属性   ：「その画像が表している”文脈”」を書く
	 * title属性 ：「画像」の補足説明を書く
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 * @og.rev 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
	 *
	 * @param   attri 属性群
	 *
	 * @return  イメージタグ文字列
	 * @og.rtnNotNull
	 */
	public static String img( final Attributes attri ) {
		// 6.4.2.0 (2016/01/29) title属性がなければ、alt属性を設定しておく。
		final String title = attri.get( "title" );
		if( title == null ) {
			attri.set( "title" , attri.get( "alt" ) );
		}

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "img" )
					.add(     attri.getAttribute( IMAGE_KEY ) )
					.makeTag();
	}

	/**
	 * フォームを作成します。
	 *
	 * &lt;form action="URI" method="HTTPメソッド" enctype="MIMEタイプ" target="フレーム名" ･･･ &gt;フォーム等&lt;/form&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>action="URI"</td><td>必須</td><td>送信されたフォームデータを処理するプログラムＵＲＩ</td></tr>
	 *   <tr><td>method="HTTPメソッド"</td><td>オプション</td><td>get/post</td></tr>
	 *   <tr><td>enctype="MIMEタイプ"</td><td>オプション</td><td>フォームデータ送信時のMIMEタイプ</td></tr>
	 *   <tr><td>accept-charset="文字セット"</td><td>オプション</td><td>データとして受付可能な文字セットの指定</td></tr>
	 *   <tr><td>accept="MIMEタイプ"</td><td>オプション</td><td>データとして処理可能なMIMEタイプを指定</td></tr>
	 *   <tr><td>name="名前"</td><td>オプション</td><td>スクリプト等から参照する場合の名前</td></tr>
	 *   <tr><td>target="フレーム名"</td><td>オプション</td><td>フォームを送信した結果を表示させるフレーム</td></tr>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="フォーム等の文字列"</td><td>必須</td><td>input 等のフォーム要素</td></tr>
	 * </table>
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  フォームタグ文字列
	 * @og.rtnNotNull
	 */
	public static String form( final Attributes attri ) {
		String body   = attri.get( "body" );
		if( body == null ) { body = "" ; }

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "form" )
					.add(     attri.getAttribute( FORM_KEY ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * 汎用インライン要素(SPAN)を作成します。
	 *
	 * &lt;span class="XXXX" ･･･ &gt;テキスト等&lt;/span&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="テキスト等の文字列"</td><td>オプション</td><td>このテキストを修飾します。</td></tr>
	 * </table>
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  SPANタグ文字列
	 * @og.rtnNotNull
	 */
	public static String span( final Attributes attri ) {
		String body   = attri.get( "body" );
		if( body == null ) { body = "" ; }

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "span" )
					.add(     attri.getAttribute( SPAN_KEY ) )
					.add(     attri.get( "optionAttributes" ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * 整形済みテキスト(PRE)を作成します。
	 *
	 * &lt;pre class="XXXX" ･･･ &gt;テキスト等&lt;/pre&gt;
	 *
	 * <table class="plain">
	 *   <caption>Attributes に設定できる属性</caption>
	 *   <tr><td>汎用属性</td><td>オプション</td><td>class,id,title,style,lang,dir,xml:lang</td></tr>
	 *   <tr><td>body="テキスト等の文字列"</td><td>オプション</td><td>このテキストを修飾します。</td></tr>
	 * </table>
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
	 *
	 * @param   attri 属性群
	 *
	 * @return  PREタグ文字列
	 * @og.rtnNotNull
	 */
	public static String pre( final Attributes attri ) {
		String body   = attri.get( "body" );
		if( body == null ) { body = "" ; }

		// 6.2.0.0 (2015/02/27) TagBuffer を使用するように変更。
		return new TagBuffer( "pre" )
					.add(     attri.getAttribute( PRE_KEY ) )
					.add(     attri.get( "optionAttributes" ) )
					.addBody( body )
					.makeTag();
	}

	/**
	 * URLチェック用のキーを返します。
	 *
	 * 引数に指定されたhrefに対して、時間とユーザーIDを付加した暗号化文字列を
	 * 引数に追加します。
	 *
	 * 暗号化は、org.opengion.fukurou.security.HybsCryptographyを使用します。
	 * 暗号化を行う文字列のフォーマットは、[href],time=[checkTime],userid=[loginUser]です。
	 *
	 * @og.rev 4.3.7.1 (2009/06/08) 新規追加
	 * @og.rev 4.3.7.4 (2009/07/01) 循環参照を解消
	 * @og.rev 5.8.8.0 (2015/06/05) キー指定対応で別メソッドに処理を委譲
	 *
	 * @param   href チェック対象のURL
	 * @param   key チェックキーのパラメーターキー
	 * @param   userid ユーザーID
	 * @param   time 有効時間
	 *
	 * @return  チェックキー
	 * @og.rtnNotNull
	 * @see org.opengion.fukurou.security.HybsCryptography
	 */
	public static String addURLCheckKey( final String href, final String key, final String userid, final long time ) {

		return addURLCheckKey( href, key, userid, time, null );
	}

	/**
	 * URLチェック用のキーを返します。
	 *
	 * 引数に指定されたhrefに対して、時間とユーザーIDを付加した暗号化文字列を
	 * 引数に追加します。
	 *
	 * 暗号化は、org.opengion.fukurou.util.HybsCryptographyを使用します。
	 * cryptを渡した場合はそのキーを利用して変換をかけますが、NULLの場合は標準キーで暗号化されます。
	 * 暗号化を行う文字列のフォーマットは、[href],time=[checkTime],userid=[loginUser]です。
	 *
	 * @og.rev 5.8.8.0 (2015/06/05) 新規作成
	 *
	 * @param   href チェック対象のURL
	 * @param   key チェックキーのパラメーターキー
	 * @param   userid ユーザーID
	 * @param   time 有効時間
	 * @param	crypt 暗号化クラス
	 *
	 * @return  チェックキー
	 * @see org.opengion.fukurou.security.HybsCryptography
	 */
	public static String addURLCheckKey( final String href, final String key, final String userid, final long time, final HybsCryptography crypt ) {
		// 6.4.2.1 (2016/02/05) PMD refactoring. Prefer StringBuffer over += for concatenating strings
		final String orgChKey = href + ",time=" + time + ",userid=" + userid;

		final String checkKey = key + "=" +
						crypt == null ? HYBS_CRYPTOGRAPHY.encrypt( orgChKey )
									  : crypt.encrypt( orgChKey ) ;

		return addUrlEncode( href , checkKey );
	}

	/**
	 * Aタグの文字列を解析して、href属性にURLチェック用の暗号化文字列を付加した形で、
	 * Aタグを再構築し、返します。
	 *
	 * @og.rev 4.3.7.1 (2009/06/08) 新規追加
	 * @og.rev 4.3.7.4 (2009/07/01) 循環参照を解消
	 * @og.rev 5.8.8.0 (2015/06/05) キー指定対応で別メソッドに処理を委譲
	 *
	 * @param   tag Aタグ文字列
	 * @param   key チェックキーのパラメーターキー
	 * @param   userid ユーザーID
	 * @param   time 有効時間
	 *
	 * @return  URLチェックキーが付加されたAタグ文字列
	 * @og.rtnNotNull
	 */
	public static String embedURLCheckKey( final String tag, final String key, final String userid, final long time  ) {
		return embedURLCheckKey( tag, key, userid, time, null  );
	}

	/**
	 * Aタグの文字列を解析して、href属性にURLチェック用の暗号化文字列を付加した形で、
	 * Aタグを再構築し、返します。
	 *
	 * @og.rev 5.8.8.0 (2015/06/05) 新規作成
	 *
	 * @param   tag Aタグ文字列
	 * @param   key チェックキーのパラメーターキー
	 * @param   userid ユーザーID
	 * @param   time 有効時間
	 * @param	crypt 暗号化クラス
	 *
	 * @return  URLチェックキーが付加されたAタグ文字列
	 */
	public static String embedURLCheckKey( final String tag, final String key, final String userid, final long time, final HybsCryptography crypt ) {
		String rtn = tag;
		final int hrefStr = rtn.indexOf( "href=\"" );
		if( hrefStr >= 0 ) {
			final int hrefEnd = rtn.indexOf( "\"",hrefStr + 6 );
			if( hrefEnd >= 0 ) {
				String href = rtn.substring( hrefStr + 6, hrefEnd );
				href = XHTMLTag.addURLCheckKey( href, key, userid, time, crypt );
				rtn = rtn.substring( 0,  hrefStr ) + "href=\"" + href + rtn.substring( hrefEnd );
			}
		}
		return rtn;
	}
}
