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

/**
 * 指定の行-列と、動的ｶﾗﾑのﾃｰﾌﾞﾙを作成するクラスです。
 *
 * 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.3.2.1 (2021/03/25)" ;

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

	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) 動的ｶﾗﾑのﾃｰﾌﾞﾙを作成する
	 *
	 * @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 = rowData( row,noDefval,"" );	// 初期値

			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 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 );
			}

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

				// dbType が、TP_NUMBER の場合は、数値のみ可能とする。
				if( "TP_NUMBER".equals( dbType ) ) {
					editAttri.set( "type" , "number" );		// type="number" だけだと、整数しか入らない。
					if(		 viewLen.contains( ".1" ) ) { editAttri.set( "step" , "0.1"  ); }
					else if( viewLen.contains( ".2" ) ) { editAttri.set( "step" , "0.01" ); }
					else if( viewLen.contains( ".3" ) ) { editAttri.set( "step" , "0.001" ); }
				}
			}

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

			// ﾗﾍﾞﾙの末尾が数字の場合は、①～の番号を placeholder にｾｯﾄする。
	//		if( "3".equals( cddisp ) ) {						// 表示方法(CDDISP)=3:ｶﾗﾑ連続 の場合に、
				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 以下のﾌｫﾙﾀﾞになります。
			}

			// 入力欄を連結するため、ﾙｰﾌﾟの最後にまとめて出す必要がある。
			// dbType が、TP_MENU の場合は、DBMENUの引数($2)に、ﾄｰｸﾝを渡す。(GG03を検索する)
			String tempVal = val;
			if( "TP_MENU".equals( dbType ) ) {
				config.setAddNoValue( true );		// ﾌﾟﾙﾀﾞｳﾝﾒﾆｭｰは、空欄を選択できるようにしておく
				tempVal = val + ":" + token;		// DBMENUの引数($2)
			}

			final String optAttr = rowData( row,noOptAttr,"" );						// ｵﾌﾟｼｮﾝ属性(OPT_ATTR)

			// TP_IFRAMEとTP_IMAGEは、Rendereを使う。それ以外は、Editor
			final String valBuf ;
			if( "TP_IFRAME".equals( dbType ) || "TP_IMAGE".equals( dbType ) ) {
				if( !optAttr.isEmpty() ) {
	//				final Attributes rendAttri = config.getRendererAttributes();	// 内部ｵﾌﾞｼﾞｪｸﾄを取得している。
	//				rendAttri.add( "optionAttributes" , optAttr );
					config.setRendererParam( optAttr );
				}
				valBuf = new DBColumn( config ).getRendererValue( row,tempVal );	// 表示欄(Renderer)
			}
			else {
				if( !optAttr.isEmpty() ) {
					editAttri.add( "optionAttributes" , optAttr );
	//				config.setEditorParam( optAttr );
				}
				valBuf = new DBColumn( config ).getEditorValue( row,tempVal );		// 入力欄(Editor)
			}

			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 ) || "4".equals( cddisp ) ) {	// CDDISP=1:ﾗﾍﾞﾙｶﾗﾑ,4:ﾗﾍﾞﾙのみ の場合、ﾗﾍﾞﾙ表示される
			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(lbl).append( "</td>" );
			}

			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 );
		}
	}

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