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

import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.Attributes;

import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBColumnConfig;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.resource.ResourceFactory;
import org.opengion.hayabusa.resource.ResourceManager;
import org.opengion.hayabusa.resource.LabelData;
import org.opengion.hayabusa.common.HybsSystemException;

import java.time.LocalDateTime;									// 7.4.2.1 (2021/05/21)
import java.time.format.DateTimeFormatter;						// 7.4.2.1 (2021/05/21)

/**
 * 指定の行-列と、動的ｶﾗﾑのﾃｰﾌﾞﾙを作成するクラスです。
 *
 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
 * 各HTMLのタグに必要な setter/getterメソッドのみ，追加定義しています。
 * 行単位の繰り返し色は使いません。
 *
 * RENDERER,EDITOR,DBTYPE を使用する代わりに、簡易的な DATA_TYPE で決定します。
 *
 * GG10 履歴ﾃｰﾌﾞﾙ (書き込むﾃｰﾌﾞﾙ)
 *   ﾄｰｸﾝ(TOKEN)			必須ｷｰ(ﾄｰｸﾝ)
 *   更新ｶｳﾝﾀ(UPCNT)		← 未使用(同一ﾄｰｸﾝでの最大値が有効:逆順検索なので、最初に見つかった値を採用)
 *   値ﾃﾞｰﾀ(VAL)			必須ｷｰ(値の設定)
 *   単位(TANI)				ﾌｨｰﾙﾄﾞの後ろに追記(連続の場合は、一番最後のみ)
 *   判定結果(JUDG)			0:未決 1:不要 2:任意 3:合格 4:保留 5:警告 6:必須 7:不合格
 *   判定理由(RIYU)			上限、下限で判定した結果が入っている。titleに入れてﾎﾟｯﾌﾟｱｯﾌﾟさせる
 *
 * GG01 ﾄｰｸﾝﾏｽﾀ (GG02がnullの時)
 *   ﾄｰｸﾝ名称(TKN_NM)		← 未使用(GG02 のﾄｰｸﾝ名称が未設定の場合…SQL文で処理済み)
 *   表示桁数(VIEW_LEN)		ﾃｷｽﾄﾌｨｰﾙﾄﾞの長さｾｯﾄ
 *   ﾃﾞｰﾀ型(DATA_TYPE)		EDITORを決定
 *   ﾄｰｸﾝｸﾞﾙｰﾌﾟ(TKN_GRP)	(未使用=GG03のSEL_KEY の条件に使用している)
 *
 * GG03 選択ﾏｽﾀ (GG01 ﾄｰｸﾝｸﾞﾙｰﾌﾟの名称とﾏｰｶｰ)
 *   選択名称(SEL_NM)		ﾄｰｸﾝｸﾞﾙｰﾌﾟ名で、fieldset のｷｰﾌﾞﾚｲｸに使用
 *   ﾏｰｶｰ(MARKER)			fieldset のstyle属性として使用
 *
 * GG02 雛形設定ﾏｽﾀ
 *   ﾄｰｸﾝ名称(TKN_NM)		(名称優先)
 *   ﾀﾌﾞ名称(TAB_NM)		← 未使用
 *   ﾀｸﾞﾙｰﾌﾟ配置(GRP_POS)	← 未使用(fieldset を配置する場合に使う予定)
 *   初期値(DEF_VAL)		値の設定 する場合の初期値に使用
 *   行番号(ROWNO)			ﾄｰｸﾝの並び順を指定
 *   列番号(COLNO)			← 未使用(ﾄｰｸﾝの並び順のサブ)
 *   行数(ROWSPAN)			tableのｶﾗﾑを複数縦につなげる場合
 *   列数(COLSPAN)			tableのｶﾗﾑを複数横につなげる場合
 *   登録方法(CDREC)		0:未決 1:不要 2:任意 4:保留 6:必須
 *   表示方法(CDDISP)		1:ﾗﾍﾞﾙｶﾗﾑ 2:ｶﾗﾑ単発 3:ｶﾗﾑ連続 4:表示のみ
 *   異常下限(E_MIN)		異常値の下限判定をﾌｨｰﾙﾄﾞの横に記述
 *   警告下限(W_MIN)		警告値の下限判定をﾌｨｰﾙﾄﾞの横に記述
 *   警告上限(W_MAX)		警告値の上限判定をﾌｨｰﾙﾄﾞの横に記述
 *   異常上限(E_MAX)		異常値の上限判定をﾌｨｰﾙﾄﾞの横に記述
 *   ｵﾌﾟｼｮﾝ属性(OPT_ATTR)	ﾄｰｸﾝのﾌｫｰﾑの属性に追記する
 *
 * @og.group 画面表示
 *
 * @version  7.3
 * @author   Kazuhiko Hasegawa
 * @since    JDK11.0,
 */
public class ViewForm_HTMLTokenTable extends ViewForm_HTMLTable {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "7.4.2.3 (2021/06/09)" ;

	private static final String INS_VAL = "$$$$";		// 表示方法(CDDISP)が3:ｶﾗﾑ連続 の場合の挿入位置を示すﾏｰｶｰ

	private static final DateTimeFormatter YMD    = DateTimeFormatter.ofPattern( "yyyyMMdd" );
	private static final DateTimeFormatter HM     = DateTimeFormatter.ofPattern( "HHmm" );
	private static final DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss" );

	// Rendereを使う ﾃﾞｰﾀ型(DATA_TYPE) の指定
	// TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNALは、Rendereを使う。contains で判定しているので、部分一致に注意
	private static final String RENDERE_TYPE = "TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNAL" ;

	private int noToken		= -1;
//	private int noUpcnt		= -1;
	private int noVal		= -1;
	private int noTani		= -1;
	private int noJudg		= -1;
	private int noRiyu		= -1;

	private int noViewlen	= -1;
	private int noType		= -1;

	private int noSelnm		= -1;
	private int noMarker	= -1;

	private int noName		= -1;
	private int noDefval	= -1;
	private int noRowno		= -1;
//	private int noColno		= -1;
	private int noRowspan	= -1;
	private int noColspan	= -1;

	private int noCdrec		= -1;
	private int noCddisp	= -1;
	private int noEmin		= -1;
	private int noWmin		= -1;
	private int noWmax		= -1;
	private int noEmax		= -1;
	private int noOptAttr	= -1;

	private DBTableModel table ;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 7.3.2.1 (2021/03/25) 動的ｶﾗﾑのﾃｰﾌﾞﾙを作成する
	 */
	public ViewForm_HTMLTokenTable() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 初期化します。
	 * ここでは、内部で使用されているキャッシュをクリアし、
	 * 新しいモデル(DBTableModel)と言語(lang) を元に内部データを再構築します。
	 * ただし、設定情報は、以前の状態がそのままキープされています。
	 *
	 * @og.rev 7.3.2.1 (2021/03/25) 動的ｶﾗﾑのﾃｰﾌﾞﾙを作成する
	 *
	 * @param	table	DBTableModelオブジェクト
	 */
	@Override
	public void init( final DBTableModel table ) {
		super.init( table );
		this.table = table;

		noToken		= table.getColumnNo( "TOKEN"			);	// ﾄｰｸﾝ
	//	noUpcnt		= table.getColumnNo( "UPCNT"			);	// 更新ｶｳﾝﾀ
		noVal		= table.getColumnNo( "VAL"				);	// 値
		noTani		= table.getColumnNo( "TANI"		, false );	//
		noJudg		= table.getColumnNo( "JUDG"		, false );	//
		noRiyu		= table.getColumnNo( "RIYU"		, false );	//
		noViewlen	= table.getColumnNo( "VIEW_LEN"	, false );	//
		noType		= table.getColumnNo( "DATA_TYPE", false );	//
		noSelnm		= table.getColumnNo( "SEL_NM"	, false );	//
		noMarker	= table.getColumnNo( "MARKER"	, false );	//
		noName		= table.getColumnNo( "TKN_NM"	, false );	//
		noDefval	= table.getColumnNo( "DEF_VAL"	, false );	//
		noRowno		= table.getColumnNo( "ROWNO"	, false );	//
	//	noColno		= table.getColumnNo( "COLNO"	, false );	//
		noRowspan	= table.getColumnNo( "ROWSPAN"	, false );	//
		noColspan	= table.getColumnNo( "COLSPAN" 	, false );	//
		noCdrec		= table.getColumnNo( "CDREC"	, false );	//
		noCddisp	= table.getColumnNo( "CDDISP"	, false );	//
		noEmin		= table.getColumnNo( "E_MIN"	, false );	//
		noWmin		= table.getColumnNo( "W_MIN"	, false );	//
		noWmax		= table.getColumnNo( "W_MAX"	, false );	//
		noEmax		= table.getColumnNo( "E_MAX"	, false );	//
		noOptAttr	= table.getColumnNo( "OPT_ATTR"	, false );	//
	}

	/**
	 * DBTableModel から HTML文字列を作成して返します。
	 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
	 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
	 *
	 * @og.rev 7.3.2.1 (2021/03/25) 動的ｶﾗﾑのﾃｰﾌﾞﾙを作成する
	 * @og.rev 7.4.2.1 (2021/05/21) TP_RADIO追加,4:表示のみは、値をRendererで出す。
	 * @og.rev 7.4.2.3 (2021/06/09) 数値ﾌｫｰﾏｯﾄ(ｶﾝﾏ無し、桁数指定)を設定、addNoValue制御を追加
	 *
	 * @param  startNo    表示開始位置
	 * @param  pageSize   表示件数
	 *
	 * @return  DBTableModelから作成された HTML文字列
	 * @og.rtnNotNull
	 */
	@Override
	public String create( final int startNo, final int pageSize )  {
		if( getRowCount() == 0 ) { return ""; }	// 暫定処置

		final int lastNo = getLastNo( startNo, pageSize );

		final StringBuilder tagOut = new StringBuilder( BUFFER_LARGE );		// 最終的に出力するﾀｸﾞの文字列ﾊﾞｯﾌｧ
//			.append( getCountForm( startNo,pageSize ) )
//			.append( getHeader() );

		final ResourceManager resource = ResourceFactory.newInstance( "ja" ) ;

		int lastRow  = -1;
//		int bgClrCnt = 0;

		String bkSelNm = "";			// 最後にセットされた SEL_NM (ｷｰﾌﾞﾚｲｸ用)
		String bkToken = "";			// 更新ｶｳﾝﾀの最大値(逆順検索しているので、最初に見つかったﾄｰｸﾝ)のみ採用する。

		final int clmCnt = getColumnCount();
		for( int row=startNo; row<lastNo; row++ ) {
			final int rowno = rowData( row,noRowno,row-startNo );	// DBに指定した列の番号

			final String token = table.getValue( row,noToken );
	//		final String upcnt = table.getValue( row,noUpcnt );		// 更新ｶｳﾝﾀは、同一ﾄｰｸﾝ内で、一番最初に現れたﾚｺｰﾄﾞのみ使用する。
			if( bkToken.equals( token ) ) { continue; }				// 同一ﾄｰｸﾝなら、過去ﾃﾞｰﾀなので取り直し
			bkToken = token;

			final String selNm  = rowData( row,noSelnm ,"" );		// 選択名称(SEL_NM)
//			if( lastRow != rowno || !bkSelNm.equals( selNm ) ) {
			if( lastRow != rowno ) {	// 行のﾌﾞﾚｲｸ
				if( lastRow >= 0 ) {	// 最初のループだけは、実行しない。
					if( !bkSelNm.equals( selNm ) && !bkSelNm.isEmpty() ) {
						tagOut.append( "</tr></tbody></table></fieldset>" );
					}
					else {
						tagOut.append(" </tr>").append( CR );
					}
				}

				// ※ 繰り返し色:getBgColorCycleClass( bgClrCnt++,row ) は使いません。
				if( !bkSelNm.equals( selNm ) && !selNm.isEmpty() ) {
					final String style = rowData( row,noMarker," style=\"", "\"" );	// ﾏｰｶｰ(MARKER) は、style属性に追加する。
					tagOut.append( "<fieldset" ).append( style ).append( "><legend>" ).append( selNm ).append( "</legend>" )
						.append("<table><tbody><tr>").append( CR );		// 繰り返し色背景色は使わない
//						.append("<table><tbody><tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
				}
				else if( lastRow < 0 ) {	// 最初の行で、fieldset のﾗﾍﾞﾙが 未指定の場合、<table> が必要。
					tagOut.append("<table><tbody><tr>").append( CR );	// 繰り返し色背景色は使わない
//					tagOut.append("<table><tbody><tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
				}
				else {
					tagOut.append("<tr>").append( CR );					// 繰り返し色背景色は使わない
//					tagOut.append("<tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
				}
				bkSelNm = selNm;
				lastRow = rowno;
			}

			final String lbl   = rowData( row,noName,token );		// 表示するﾗﾍﾞﾙ(無ければﾄｰｸﾝそのもの)
			final String dbType= rowData( row,noType,"TP_TEXT" );	// DBﾀｲﾌﾟ。未指定時は、TP_TEXT:ﾃｷｽﾄ

			final DBColumn clm = resource.makeDBColumn( dbType,lbl );
			final DBColumnConfig config = clm.getConfig();
			config.setName( "VAL" );								// 登録するｶﾗﾑの名前は、VAL になる。

			final String defVal = timeVal( dbType,rowData( row,noDefval,"" ) );	// 初期値	7.4.2.1 (2021/05/21)
			final String val = rowData( row,noVal,defVal );

			final String cdrec = rowData( row,noCdrec ,"0" );	// 0:未決 1:不要 2:任意        4:保留        6:必須
			final String judg  = rowData( row,noJudg  ,"0" );	// 0:未決 1:不要 2:任意 3:合格 4:保留 5:警告 6:必須 7:不合格

			final Attributes editAttri = config.getEditorAttributes();		// 内部ｵﾌﾞｼﾞｪｸﾄを取得している。
//			final String optAttr = changeOptAttr( editAttri, rowData( row,noOptAttr,"" ) );	// ｵﾌﾟｼｮﾝ属性(OPT_ATTR)
			final String optAttr = changeOptAttr( config, editAttri, rowData( row,noOptAttr,"" ) );	// 7.4.2.3 (2021/06/09) ｵﾌﾟｼｮﾝ属性(OPT_ATTR)

			final boolean isEmp = val.isEmpty()													// 本当にempty
									|| ( "TP_CHECK".equals( dbType ) && "0".equals(val) );		// TP_CHECK が '0' は、未設定と同じ

			if( isEmp ) {										// val が未設定の場合は、cdrec をそのままセットする。
				editAttri.add( "class" , "judg" + cdrec );		// 注意：キーは、judg
			}
			else {												// val が設定済みの場合は、judg をセットする。
				editAttri.add( "class" , "judg" + judg );
			}

	//		日付ﾌｫｰﾏｯﾄが日本風じゃないので、取りやめ
	//		// dbType が、TP_YMD,TP_HMS,TP_YMDH の場合は、日付、時刻のみ可能とする。
	//		if( "TP_YMD".equals( dbType ) ) {
	//			editAttri.set( "type" , "date" );				// type="date" で日付
	//		}
	//		else if( "TP_HMS".equals( dbType ) ) {
	//			editAttri.set( "type" , "time" );				// type="time" だけだと、秒が入らない。
	//			editAttri.set( "step" , "1" );					// 秒 を有効にするには、step="1" にする。
	//		}

			// dbType が、TP_INT の場合は、数値のみ可能とする。
			if( "TP_INT".equals( dbType ) ) {
				editAttri.set( "type" , "number" );		// type="number" だけだと、整数しか入らない。
				// Editor_NUMBER は、ﾌｫｰﾏｯﾄ指定できない。TableFilter_JUDG で書き戻しして、ｶﾝﾏを削除している。
			}
			else if( "TP_REAL".equals( dbType ) ) {
				editAttri.set( "type" , "number" );		// type="number" だけだと、整数しか入らない。
				editAttri.set( "step" , "0.01"   );		// とりあえず、無条件に、0.01 刻みにする。
				config.setEditorParam( "#0.00" );		// 7.4.2.3 (2021/06/09) 数値ﾌｫｰﾏｯﾄ(ｶﾝﾏ無し、桁数指定)を設定
			}

	//		if( noViewlen >= 0 ) { config.setFieldSize( table.getValue( row,noViewlen ) ); }	// ﾌｨｰﾙﾄﾞの表示桁数
			final String viewLen = rowData( row,noViewlen,"" );		// ﾌｨｰﾙﾄﾞの文字桁数
			if( !viewLen.isEmpty() ) {
				config.setMaxlength( viewLen );

				// dbType が、TP_REAL の場合は、表示桁数 に合わせて、step を再設定する。
				if( "TP_REAL".equals( dbType ) ) {
					if(		 viewLen.contains( ".1" ) ) {
						editAttri.set( "step" , "0.1" );
	//					config.setEditorParam( "#0.0" );			// 7.4.2.3 (2021/06/09)  数値ﾌｫｰﾏｯﾄ(ｶﾝﾏ無し、桁数指定)を設定
					}
					else if( viewLen.contains( ".2" ) ) {
						editAttri.set( "step" , "0.01" );
	//					config.setEditorParam( "#0.00" );			// 7.4.2.3 (2021/06/09)  数値ﾌｫｰﾏｯﾄ(ｶﾝﾏ無し、桁数指定)を設定
					}
					else if( viewLen.contains( ".3" ) ) {
						editAttri.set( "step" , "0.001" );
	//					config.setEditorParam( "#0.000" );			// 7.4.2.3 (2021/06/09)  数値ﾌｫｰﾏｯﾄ(ｶﾝﾏ無し、桁数指定)を設定
					}
					else {
						editAttri.set( "step" , "any" );
					}
				}
			}

			final String cddisp = rowData( row,noCddisp,"1" );		// 1:ﾗﾍﾞﾙｶﾗﾑ 2:ｶﾗﾑ単発 3:ｶﾗﾑ連続 4:表示のみ

			// 表示方法(CDDISP)=3:ｶﾗﾑ連続 かつ ﾗﾍﾞﾙの末尾が数字の場合は、①～の番号を placeholder にｾｯﾄする。
			if( "3".equals( cddisp ) && !optAttr.contains( "placeholder" ) ) {			// ｵﾌﾟｼｮﾝ属性 に plaseholder が未設定の場合のみ
				int idx = lbl.length()-1;
				while( idx >= 0 ) {
					final char ch = lbl.charAt( idx );
					if( '0' <= ch && ch <= '9' ) { idx--; }		// 末尾から数字の間、処理を継続
					else { break; }								// 数字でなくなった時点で処理終了
				}
				if( idx >= 0 && idx < lbl.length()-1 ) {		// 末尾に何らかの数値があった場合
					final char plc = (char)('①' + ( Integer.parseInt(lbl.substring( idx+1 ))-1 ) );
					editAttri.set( "placeholder" , String.valueOf( plc ) );
				}
			}

//			// 理由 があれば、title に入れて popup させる
//			final String riyu= rowData( row,noRiyu,"" );
//			if( ! riyu.isEmpty() ) {
//				editAttri.set( "title" , riyu );
//			}

			// 登録方法(CDREC) が、1:不要 か、書き込み禁止(isWritable( row )がfalse) の場合は、disabled をｾｯﾄする。
			if( "1".equals( cdrec ) || !isWritable( row ) ) {
				editAttri.set( "disabled" , "disabled" );		// 1:不要 は、disabled をｾｯﾄする。
			}

	//		config.setEditorAttributes( editAttri );			// 内部ｵﾌﾞｼﾞｪｸﾄを追加しているので、再設定不要。追加する場合に使用する。

			if( "TP_FILE".equals( dbType ) ) {					// ﾌｧｲﾙ添付は、値をｱｯﾌﾟﾛｰﾄﾞするﾌｫﾙﾀﾞとする。
				config.setEditorParam( "UPLOAD_DIR=" + val );	// jsp 以下のﾌｫﾙﾀﾞになります。
			}

			// RENDERE_TYPE="TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNAL" は、Rendereを使う。
			// 表示方法(CDDISP) 4:表示のみ の場合も、Rendereを使う。
	//		final String valBuf ;
			String valBuf ;
//			if( "TP_IFRAME".equals( dbType ) || "TP_IMAGE".equals( dbType ) || "TP_LINE".equals( dbType ) || "TP_SIGNAL".equals( dbType ) || "4".equals( cddisp ) ) {
			if( RENDERE_TYPE.contains( dbType ) || "4".equals( cddisp ) ) {
				if( !optAttr.isEmpty() ) {
	//				final Attributes rendAttri = config.getRendererAttributes();	// 内部ｵﾌﾞｼﾞｪｸﾄを取得している。
	//				rendAttri.add( "optionAttributes" , optAttr );
					config.setRendererParam( optAttr );
				}
				valBuf = new DBColumn( config ).getRendererValue( row,val );		// 表示欄(Renderer)
			}
			else {
				if( !optAttr.isEmpty() ) {
					editAttri.add( "optionAttributes" , optAttr );
	//				config.setEditorParam( optAttr );
				}

				// dbType が、TP_MENU かTP_RADIO の場合は、DBMENUの引数($2)に、ﾄｰｸﾝを渡す。(GG03を検索する)
				String tempVal = val;
				if( "TP_MENU".equals( dbType ) || "TP_RADIO".equals( dbType ) ) {
					config.setAddNoValue( true );		// ﾌﾟﾙﾀﾞｳﾝﾒﾆｭｰは、空欄を選択できるようにしておく
					tempVal = val + ":" + token;		// DBMENUの引数($2)
				}

				valBuf = new DBColumn( config ).getEditorValue( row,tempVal );		// 入力欄(Editor)
			}

			// 理由 があれば、valBuf の直後にそのまま入れる。
			final String riyu= rowData( row,noRiyu,"" );
			if( ! riyu.isEmpty() ) {
				valBuf = valBuf + riyu ;
			}

			final int colspan = rowData( row,noColspan,1 );
			final int rowspan = rowData( row,noRowspan,1 );

			// 表示方法(CDDISP) 1:ﾗﾍﾞﾙｶﾗﾑ 2:ｶﾗﾑ単発 3:ｶﾗﾑ連続 4:表示のみ で、
			// 3:ｶﾗﾑ連続 の場合のみ、</td> の直前に INSERT する。

			// ﾗﾍﾞﾙ項目の表示
			if( "1".equals( cddisp ) ) {							// CDDISP=1:ﾗﾍﾞﾙｶﾗﾑ の場合、ﾗﾍﾞﾙ表示される(colspanなし)
				if( 1 == rowspan ) {
					tagOut.append( "<td class='label'>" );
				}
				else {
					tagOut.append( "<td class='label' rowspan=\"" ).append( rowspan ).append( "\">" );
				}
				tagOut.append(lbl).append( "</td>" );
			}

			if( "1".equals( cddisp ) || "2".equals( cddisp ) ) {	// CDDISP=1:ﾗﾍﾞﾙｶﾗﾑ or 2:ｶﾗﾑ単発 の場合 td が必要。
				tagOut.append( "<td" );
				// colspan を反映させるのは、ﾌｨｰﾙﾄﾞ部のみ
				if( 1 != colspan ) {
					tagOut.append( " colspan=\"" ).append( colspan ).append( '"' );	// ラベルと本体があるが自分で調整する。
				}

				// rowspan を反映させるのは、ﾌｨｰﾙﾄﾞ部のみ
				if( 1 != rowspan ) {
					tagOut.append( " rowspan=\"" ).append( rowspan ).append( '"' );
				}
				tagOut.append( '>' ).append( valBuf ).append( INS_VAL );			// INS_VAL は、CDDISP=3:ｶﾗﾑ連続 の場合の挿入位置
				taniErrWarn( row,tagOut );
			}

			// 4:表示のみ の場合は、td を出す。
			if( "4".equals( cddisp ) ) {							// CDDISP=4:表示のみ の場合 td が必要。
				tagOut.append( "<td" );
				// colspan を反映させる
				if( 1 != colspan ) {
					tagOut.append( " colspan=\"" ).append( colspan ).append( '"' );	// colspan をそのまま出す。自分で調整する。
				}

				// rowspan を反映させるのは、ﾌｨｰﾙﾄﾞ部のみ
				if( 1 != rowspan ) {
					tagOut.append( " rowspan=\"" ).append( rowspan ).append( '"' );
				}
				tagOut.append( '>' ).append( valBuf ).append( "</td>" );	// valBuf を使うが、先にRenderer の値を設定している。
			}

			if( "3".equals( cddisp ) ) {							// CDDISP=3:ｶﾗﾑ連続 の場合は、INS_VALの直前に挿入する。
				final int lstIdx = tagOut.lastIndexOf( INS_VAL ) ;
				if( lstIdx >= 0 ) {
					tagOut.insert( lstIdx,valBuf );
				}
				else {							// ありえないはずだが、挿入位置が見つからない場合は、td で囲う。
					tagOut.append( "<td>" ).append( valBuf ).append( INS_VAL ).append( "</td>" );
				}
			}
		}

		tagOut.append(" </tr></tbody></table>").append( CR );
		if( !bkSelNm.isEmpty() ) { tagOut.append( "</fieldset>" ); }
		tagOut.append( getScrollBarEndDiv() );

		return tagOut.toString().replace( INS_VAL , "" );			// 最後に文字列に変換後、INS_VAL は、すべて空文字列に置き換えます。
	}

	/**
	 * 値出力の終端処理をまとめて行います。
	 *
	 * 全体出力の tagOut に、値ﾌｨｰﾙﾄﾞの valBuf と、 tani を連結し、
	 * DBTableModelから、異常下限,警告下限,警告上限,異常上限を、div で固めます。
	 * valBuf は、初期化されます。
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
	 *
	 * @param	row 行
	 * @param	tagOut 全体出力のStringBuilder
	 * @param	valBuf 値ﾌｨｰﾙﾄﾞのStringBuilder(このﾒｿｯﾄﾞ内でｸﾘｱする)
	 * @param	tani 単位
	 */
//	private void valBufOut( final int row,final StringBuilder tagOut,final StringBuilder valBuf,final String tani ) {
	private void taniErrWarn( final int row,final StringBuilder tagOut ) {
		final String tani = rowData( row,noTani,"(",")" );	// 単位表示(ｶﾗﾑの最後に出す)
		tagOut.append( tani );

		final String wmin = rowData( row,noWmin,"" );		// 警告下限(W_MIN)
		final String wmax = rowData( row,noWmax,"" );		// 警告上限(W_MAX)
		final String emin = rowData( row,noEmin,"" );		// 異常下限(E_MIN)
		final String emax = rowData( row,noEmax,"" );		// 異常上限(E_MAX)

		if( wmin.isEmpty() && wmax.isEmpty() && emin.isEmpty() && emax.isEmpty() ) {
			tagOut.append( "</td>" );
		}
		else {
			tagOut.append( "<div class='small' title='上段:警告&#10;下段:異常'>" );

			if( !wmin.isEmpty() || !wmax.isEmpty() ) {			// 警告下限(W_MIN) 警告上限(W_MAX)
				final String max = "%" + Math.max( wmin.length() , wmax.length() ) + "s";	// 最大桁数を求める。
				tagOut.append( String.format( max + "～" + max ,wmin,wmax ) );
			}
			tagOut.append( "</br>" );

			if( !emin.isEmpty() || !emax.isEmpty() ) {			// 異常下限(E_MIN) 異常上限(E_MAX)
				final String max = "%" + Math.max( emin.length() , emax.length() ) + "s";	// 最大桁数を求める。
				tagOut.append( String.format( max + " ～ " + max ,emin,emax ) );			// 異常の範囲の方が広いので、間を少し開けている。
			}
			tagOut.append( "</div></td>" );
		}
	}

	/**
	 * DBTableModelから、指定行-列のデータを取得します。
	 *
	 * 列番号がマイナスの場合は、ｶﾗﾑが存在していないため、初期値を返します。
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
	 *
	 * @param	row 行
	 * @param	col 列
	 * @param	defVal 初期値(文字列)
	 *
	 * @return 指定行-列のデータ(文字列)
	 */
	private String rowData( final int row , final int col , final String defVal ) {
		String rtn = defVal ;
		if( col >= 0 ) {
			final String str = table.getValue( row,col );
			if( str != null && !str.isEmpty()) {
				rtn = str ;
			}
		}
		return rtn ;
	}

	/**
	 * DBTableModelから、指定行-列のデータを取得し、前後に文字列を追加します。
	 *
	 * 列番号がマイナスの場合は、ｶﾗﾑが存在していないため、長さゼロの文字列を返します。
	 * 取得したデータが、長さゼロの文字列でない場合は、前後に指定の文字列を追加して返します。
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
	 *
	 * @param	row 行
	 * @param	col 列
	 * @param	prefix 前に付ける文字列
	 * @param	suffix 後ろに付ける文字列
	 *
	 * @return 指定行-列のデータ(文字列)
	 */
	private String rowData( final int row , final int col , final String prefix , final String suffix ) {
		String rtn = "" ;
		if( col >= 0 ) {
			final String str = table.getValue( row,col );
			if( str != null && !str.isEmpty()) {
				rtn = prefix + str + suffix ;
			}
		}
		return rtn ;
	}

	/**
	 * DBTableModelから、指定行-列のデータを取得します。
	 *
	 * 列番号がマイナスの場合は、ｶﾗﾑが存在していないため、初期値を返します。
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
	 *
	 * @param	row 行
	 * @param	col 列
	 * @param	defVal 初期値(数値)
	 *
	 * @return 指定行-列のデータ(数値)
	 */
	private int rowData( final int row , final int col , final int defVal ) {
		final String val = col < 0 ? null : table.getValue( row,col ) ;

		try {
			return val == null || val.isEmpty() ? defVal : Integer.parseInt( val );
		}
		catch( final NumberFormatException ex ) {
			final String errMsg = "数値項目に数値以外の文字が含まれています。行=" + row + " , 列=" + col + " , 値=" + val ;
			throw new HybsSystemException( errMsg,ex );
		}
	}

	/**
	 * DBﾀｲﾌﾟ TP_YMD、TP_HMS、TP_YMDH の初期値に現在時刻を求めます。
	 *
	 * DBﾀｲﾌﾟが、上記以外か、val が NOW 以外の場合は、val をそのまま返します。
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
	 *
	 * @param	dbType	DBﾀｲﾌﾟ TP_YMD、TP_HMS、TP_YMDH 以外は、引数のval をそのまま返します。
	 * @param	val		変換する文字列 NOW 以外の場合は、引数のval をそのまま返します。
	 *
	 * @return 日時ﾀｲﾌﾟの初期値を求めます
	 */
	private String timeVal( final String dbType , final String val ) {
		String rtn = val;
		if( "NOW".equalsIgnoreCase( val ) ) {
			final LocalDateTime now = LocalDateTime.now();

			if( "TP_YMD".equals(dbType) ) {
				rtn = now.format( YMD );
			}
			else if( "TP_HMS".equals(dbType) ) {
				rtn = now.format( HM );
			}
			else if( "TP_YMDH".equals(dbType) ) {
				rtn = now.format( YMDHMS );
			}
		}

		return rtn ;
	}

	/**
	 * ｵﾌﾟｼｮﾝ属性の特殊記号を変換します。
	 *
	 * ｼﾝｸﾞﾙｸｵｰﾄは必要です。『=』から後ろ全てをそのまま 適用します。
	 *
	 * 【これらの設定は、Attributesｵﾌﾞｼﾞｪｸﾄに設定します】
	 * 　P='AAA'                    →　placeholder='AAA'
	 * 　T='ﾂｰﾙﾁｯﾌﾟ'                →　title='ﾂｰﾙﾁｯﾌﾟ'
	 * 　C='HALF'                   →　class='HALF'
	 * 　S='XX:BB;YY:CC;'           →　style='XX:BB;YY:CC;'
	 *
	 * 【これらは変換して属性に設定します】
	 * 　W='120px'                  →　width='120px'
	 * 　H='300px'                  →　height='300px'
	 * 　D='GRP1'                   →　dis='GRP1'				DIS で指定したｸﾞﾙｰﾌﾟを disabled するときのｷｰ
	 *
	 * 【DISはonchangeで書き込み不可制御を行います】
	 * 　DIS(････)                  →　onchange=disabl(this.････)
	 * 　  以下、変換は同一です。
	 * 　DIS(value=='123','GRP1')   →　onchange=disabl(this.value=='123','GRP1')		textfieldなど
	 * 　DIS(checked,'GRP1')        →　onchange=disabl(this.checked,'GRP1')			ﾁｪｯｸﾎﾞｯｸｽ
	 * 　DIS(selectedIndex==2,'GRP1')                       →　onchange=disabl(selectedIndex==2,'GRP1')	ﾌﾟｳﾙﾀﾞｳﾝﾒﾆｭｰ(ｲﾝﾃﾞｯｸｽ判定)
	 * 　DIS(options[selectedIndex].value=='ﾃｽﾄ2','GRP1')   →　onchange=disabl(options[selectedIndex].value=='ﾃｽﾄ2','GRP1')	ﾌﾟｳﾙﾀﾞｳﾝﾒﾆｭｰ(値判定)
	 *
	 * 【ANVは、addNoValue制御の略で、ﾒﾆｭｰに空ｵﾌﾟｼｮﾝを追加できます】
	 * 　ANV='true'					→ DBColumnConfig#setAddNoValue(true) を設定します。
	 * 【AKLは、addKeyLabel制御の略で、trueでキー:ラベル形式、falseでラベルのみ、null(未指定)はﾘｿｰｽに準拠します】
	 * 　AKL='true'					→ DBColumnConfig#setAddKeyLabel("true") を設定します。
	 *
	 * 【それ以外は、変換なしで設定します】
	 * 　readonly                   →　readonly		(例)
	 *
	 * @og.rev 7.3.1.1 (2021/02/25) ｵﾌﾟｼｮﾝ属性の特殊記号を変換
	 * @og.rev 7.4.2.3 (2021/06/09) addNoValue制御、addKeyLabel制御を追加
	 *
	 * @param	attri	Attributesｵﾌﾞｼﾞｪｸﾄ
	 * @param	val		変換するｵﾌﾟｼｮﾝ属性
	 *
	 * @return 変換後のｵﾌﾟｼｮﾝ属性
	 */
//	private String changeOptAttr( final Attributes attri,final String val ) {
	private String changeOptAttr( final DBColumnConfig config , final Attributes attri,final String val ) {
		if( val.isEmpty() ) { return val; }				// 確率的に、空文字はそのまま返します。

		final StringBuilder buf = new StringBuilder( val );	// 最終的に出力するﾀｸﾞの文字列ﾊﾞｯﾌｧ

		int st = 0;
		int ed = 0;

		st = buf.indexOf( "P=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );		// ｼﾝｸﾞﾙか、ﾀﾞﾌﾞﾙか
			attri.set( "placeholder" , buf.substring( st+3,ed ) );	// P= の後ろ+1から、最後のｺｰﾃｰｼｮﾝ(含まず)まで
			buf.delete( st,ed+1 );
		}

		st = buf.indexOf( "T=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
			attri.set( "title" , buf.substring( st+3,ed ) );		// T= の後ろ+1から、最後のｺｰﾃｰｼｮﾝ(含まず)まで
			buf.delete( st,ed+1 );
		}

		st = buf.indexOf( "C=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
			attri.add( "class" , buf.substring( st+3,ed ) );		// T= の後ろ+1から、最後のｺｰﾃｰｼｮﾝ(含まず)まで
			buf.delete( st,ed+1 );
		}

		st = buf.indexOf( "S=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
			attri.add( "style" , buf.substring( st+3,ed ) );		// T= の後ろ+1から、最後のｺｰﾃｰｼｮﾝ(含まず)まで
			buf.delete( st,ed+1 );
		}

		// 7.4.2.3 (2021/06/09) addNoValue制御、addKeyLabel制御を追加
		st = buf.indexOf( "ANV=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+5 ),buf.indexOf( "\"" , st+5 ) );
			config.setAddNoValue( Boolean.parseBoolean( buf.substring( st+5,ed ) ) );
			buf.delete( st,ed+1 );
		}

		// 7.4.2.3 (2021/06/09) addNoValue制御、addKeyLabel制御を追加
		st = buf.indexOf( "AKL=" );
		if( st >= 0 ) {
			ed = Math.max( buf.indexOf( "'" , st+5 ),buf.indexOf( "\"" , st+5 ) );
			config.setAddKeyLabel( buf.substring( st+5,ed ) );		// 空文字列は、処理不定
			buf.delete( st,ed+1 );
		}

		return buf.toString()
				.replace( "W=" , "width=" )
				.replace( "H=" , "height=" )
				.replace( "D=" , "dis=" )
				.replace( "DIS(" , "onchange=disabl(this." ) ;
	}

	/**
	 * DBTableModel から テーブルのヘッダータグ文字列を作成して返します。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) ヘッダーそのもののキャッシュはしない。
	 *
	 * @return	テーブルのヘッダータグ文字列
	 * @og.rtnNotNull
	 */
//	protected String getHeader() {
//		// ヘッダーは不要なので、オーバーライドしておく。
//		return "" ;
//	}
}
