001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.plugin.view;
017
018// import org.opengion.fukurou.util.StringUtil;                                 // 8.0.0.0 (2021/07/31) Delete
019import org.opengion.fukurou.util.Attributes;
020
021import org.opengion.hayabusa.db.DBColumn;
022import org.opengion.hayabusa.db.DBColumnConfig;
023import org.opengion.hayabusa.db.DBTableModel;
024import org.opengion.hayabusa.resource.ResourceFactory;
025import org.opengion.hayabusa.resource.ResourceManager;
026// import org.opengion.hayabusa.resource.LabelData;                             // 8.0.0.0 (2021/07/31) Delete
027import org.opengion.hayabusa.common.HybsSystemException;
028
029import java.time.LocalDateTime;                                                                 // 7.4.2.1 (2021/05/21)
030import java.time.format.DateTimeFormatter;                                              // 7.4.2.1 (2021/05/21)
031
032import java.util.function.Consumer ;                                                    // 8.0.0.0 (2021/08/20)
033
034/**
035 * 指定の行-列と、動的カラムのテーブルを作成するクラスです。
036 *
037 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
038 * 各HTMLのタグに必要な setter/getterメソッドのみ,追加定義しています。
039 * 行単位の繰り返し色は使いません。
040 *
041 * RENDERER,EDITOR,DBTYPE を使用する代わりに、簡易的な DATA_TYPE で決定します。
042 *
043 * GG10 履歴テーブル (書き込むテーブル)
044 *   トークン(TOKEN)                        必須キー(トークン)
045 *   更新カウンタ(UPCNT)              ← 未使用(同一トークンでの最大値が有効:逆順検索なので、最初に見つかった値を採用)
046 *   値データ(VAL)                 必須キー(値の設定)
047 *   単位(TANI)                           フィールドの後ろに追記(連続の場合は、一番最後のみ)
048 *   判定結果(JUDG)                 0:未決 1:不要 2:任意 3:合格 4:保留 5:警告 6:必須 7:不合格
049 *   判定理由(RIYU)                 上限、下限で判定した結果が入っている。titleに入れてポップアップさせる
050 *
051 * GG01 トークンマスタ (GG02がnullの時)
052 *   トークン名称(TKN_NM)             ← 未使用(GG02 のトークン名称が未設定の場合…SQL文で処理済み)
053 *   表示桁数(VIEW_LEN)             テキストフィールドの長さセット
054 *   データ型(DATA_TYPE)           EDITORを決定
055 *   トークングループ(TKN_GRP)        (未使用=GG03のSEL_KEY の条件に使用している)
056 *
057 * GG03 選択マスタ (GG01 トークングループの名称とマーカー)
058 *   選択名称(SEL_NM)               トークングループ名で、fieldset のキーブレイクに使用
059 *   マーカー(MARKER)                       fieldset のstyle属性として使用
060 *
061 * GG02 雛形設定マスタ
062 *   トークン名称(TKN_NM)             (名称優先)
063 *   タブ名称(TAB_NM)              ← 未使用
064 *   タグループ配置(GRP_POS) ← 未使用(fieldset を配置する場合に使う予定)
065 *   初期値(DEF_VAL)               値の設定 する場合の初期値に使用
066 *   行番号(ROWNO)                 トークンの並び順を指定
067 *   列番号(COLNO)                 ← 未使用(トークンの並び順のサブ)
068 *   行数(ROWSPAN)                        tableのカラムを複数縦につなげる場合
069 *   列数(COLSPAN)                        tableのカラムを複数横につなげる場合
070 *   登録方法(CDREC)                0:未決 1:不要 2:任意 4:保留 6:必須
071 *   表示方法(CDDISP)               1:ラベルカラム 2:カラム単発 3:カラム連続 4:表示のみ
072 *   異常下限(E_MIN)                異常値の下限判定をフィールドの横に記述
073 *   警告下限(W_MIN)                警告値の下限判定をフィールドの横に記述
074 *   警告上限(W_MAX)                警告値の上限判定をフィールドの横に記述
075 *   異常上限(E_MAX)                異常値の上限判定をフィールドの横に記述
076 *   オプション属性(OPT_ATTR) トークンのフォームの属性に追記する
077 *
078 * @og.group 画面表示
079 *
080 * @version  7.3
081 * @author   Kazuhiko Hasegawa
082 * @since    JDK11.0,
083 */
084public class ViewForm_HTMLTokenTable extends ViewForm_HTMLTable {
085        /** このプログラムのVERSION文字列を設定します。   {@value} */
086        private static final String VERSION = "8.0.0.0 (2021/08/20)" ;
087
088        private static final String INS_VAL = "$$$$";           // 表示方法(CDDISP)が3:カラム連続 の場合の挿入位置を示すマーカー
089
090        private static final DateTimeFormatter YMD    = DateTimeFormatter.ofPattern( "yyyyMMdd" );
091        private static final DateTimeFormatter HM     = DateTimeFormatter.ofPattern( "HHmm" );
092        private static final DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss" );
093
094        // Rendereを使う データ型(DATA_TYPE) の指定
095        // TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNALは、Rendereを使う。contains で判定しているので、部分一致に注意
096        private static final String RENDERE_TYPE = "TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNAL" ;
097
098        private int noToken             = -1;
099//      private int noUpcnt             = -1;
100        private int noVal               = -1;
101        private int noTani              = -1;
102        private int noJudg              = -1;
103        private int noRiyu              = -1;
104
105        private int noViewlen   = -1;
106        private int noType              = -1;
107
108        private int noSelnm             = -1;
109        private int noMarker    = -1;
110
111        private int noName              = -1;
112        private int noDefval    = -1;
113        private int noRowno             = -1;
114//      private int noColno             = -1;
115        private int noRowspan   = -1;
116        private int noColspan   = -1;
117
118        private int noCdrec             = -1;
119        private int noCddisp    = -1;
120        private int noEmin              = -1;
121        private int noWmin              = -1;
122        private int noWmax              = -1;
123        private int noEmax              = -1;
124        private int noOptAttr   = -1;
125
126        private DBTableModel table ;
127
128        /**
129         * デフォルトコンストラクター
130         *
131         * @og.rev 7.3.2.1 (2021/03/25) 動的カラムのテーブルを作成する
132         */
133        public ViewForm_HTMLTokenTable() { super(); }           // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
134
135        /**
136         * 初期化します。
137         * ここでは、内部で使用されているキャッシュをクリアし、
138         * 新しいモデル(DBTableModel)と言語(lang) を元に内部データを再構築します。
139         * ただし、設定情報は、以前の状態がそのままキープされています。
140         *
141         * @og.rev 7.3.2.1 (2021/03/25) 動的カラムのテーブルを作成する
142         *
143         * @param       table   DBTableModelオブジェクト
144         */
145        @Override
146        public void init( final DBTableModel table ) {
147                super.init( table );
148                this.table = table;
149
150                noToken         = table.getColumnNo( "TOKEN"                    );      // トークン
151        //      noUpcnt         = table.getColumnNo( "UPCNT"                    );      // 更新カウンタ
152                noVal           = table.getColumnNo( "VAL"                              );      // 値
153                noTani          = table.getColumnNo( "TANI"             , false );      //
154                noJudg          = table.getColumnNo( "JUDG"             , false );      //
155                noRiyu          = table.getColumnNo( "RIYU"             , false );      //
156                noViewlen       = table.getColumnNo( "VIEW_LEN" , false );      //
157                noType          = table.getColumnNo( "DATA_TYPE", false );      //
158                noSelnm         = table.getColumnNo( "SEL_NM"   , false );      //
159                noMarker        = table.getColumnNo( "MARKER"   , false );      //
160                noName          = table.getColumnNo( "TKN_NM"   , false );      //
161                noDefval        = table.getColumnNo( "DEF_VAL"  , false );      //
162                noRowno         = table.getColumnNo( "ROWNO"    , false );      //
163        //      noColno         = table.getColumnNo( "COLNO"    , false );      //
164                noRowspan       = table.getColumnNo( "ROWSPAN"  , false );      //
165                noColspan       = table.getColumnNo( "COLSPAN"  , false );      //
166                noCdrec         = table.getColumnNo( "CDREC"    , false );      //
167                noCddisp        = table.getColumnNo( "CDDISP"   , false );      //
168                noEmin          = table.getColumnNo( "E_MIN"    , false );      //
169                noWmin          = table.getColumnNo( "W_MIN"    , false );      //
170                noWmax          = table.getColumnNo( "W_MAX"    , false );      //
171                noEmax          = table.getColumnNo( "E_MAX"    , false );      //
172                noOptAttr       = table.getColumnNo( "OPT_ATTR" , false );      //
173        }
174
175        /**
176         * DBTableModel から HTML文字列を作成して返します。
177         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
178         * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
179         *
180         * @og.rev 7.3.2.1 (2021/03/25) 動的カラムのテーブルを作成する
181         * @og.rev 7.4.2.1 (2021/05/21) TP_RADIO追加,4:表示のみは、値をRendererで出す。
182         * @og.rev 7.4.2.3 (2021/06/09) 数値フォーマット(カンマ無し、桁数指定)を設定、addNoValue制御を追加
183         *
184         * @param  startNo    表示開始位置
185         * @param  pageSize   表示件数
186         *
187         * @return  DBTableModelから作成された HTML文字列
188         * @og.rtnNotNull
189         */
190        @Override
191        public String create( final int startNo, final int pageSize )  {
192                if( getRowCount() == 0 ) { return ""; } // 暫定処置
193
194                final int lastNo = getLastNo( startNo, pageSize );
195
196                final StringBuilder tagOut = new StringBuilder( BUFFER_LARGE );         // 最終的に出力するタグの文字列バッファ
197//                      .append( getCountForm( startNo,pageSize ) )
198//                      .append( getHeader() );
199
200                final ResourceManager resource = ResourceFactory.newInstance( "ja" ) ;
201
202                int lastRow  = -1;
203//              int bgClrCnt = 0;
204
205                String bkSelNm = "";                    // 最後にセットされた SEL_NM (キーブレイク用)
206                String bkToken = "";                    // 更新カウンタの最大値(逆順検索しているので、最初に見つかったトークン)のみ採用する。
207
208        //      final int clmCnt = getColumnCount();
209                for( int row=startNo; row<lastNo; row++ ) {
210                        final int rowno = rowData( row,noRowno,row-startNo );   // DBに指定した列の番号
211
212                        final String token = table.getValue( row,noToken );
213        //              final String upcnt = table.getValue( row,noUpcnt );             // 更新カウンタは、同一トークン内で、一番最初に現れたレコードのみ使用する。
214                        if( bkToken.equals( token ) ) { continue; }                             // 同一トークンなら、過去データなので取り直し
215                        bkToken = token;
216
217                        final String selNm  = rowData( row,noSelnm ,"" );               // 選択名称(SEL_NM)
218//                      if( lastRow != rowno || !bkSelNm.equals( selNm ) ) {
219                        if( lastRow != rowno ) {        // 行のブレイク
220                                if( lastRow >= 0 ) {    // 最初のループだけは、実行しない。
221                                        if( !bkSelNm.equals( selNm ) && !bkSelNm.isEmpty() ) {
222                                                tagOut.append( "</tr></tbody></table></fieldset>" );
223                                        }
224                                        else {
225                                                tagOut.append(" </tr>").append( CR );
226                                        }
227                                }
228
229                                // ※ 繰り返し色:getBgColorCycleClass( bgClrCnt++,row ) は使いません。
230                                if( !bkSelNm.equals( selNm ) && !selNm.isEmpty() ) {
231                                        final String style = rowData( row,noMarker," style=\"", "\"" ); // マーカー(MARKER) は、style属性に追加する。
232                                        tagOut.append( "<fieldset" ).append( style ).append( "><legend>" ).append( selNm ).append( "</legend>" )
233                                                .append("<table><tbody><tr>").append( CR );             // 繰り返し色背景色は使わない
234//                                              .append("<table><tbody><tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
235                                }
236                                else if( lastRow < 0 ) {        // 最初の行で、fieldset のラベルが 未指定の場合、<table> が必要。
237                                        tagOut.append("<table><tbody><tr>").append( CR );       // 繰り返し色背景色は使わない
238//                                      tagOut.append("<table><tbody><tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
239                                }
240                                else {
241                                        tagOut.append("<tr>").append( CR );                                     // 繰り返し色背景色は使わない
242//                                      tagOut.append("<tr").append( getBgColorCycleClass( bgClrCnt++,row ) ).append('>').append( CR );
243                                }
244                                bkSelNm = selNm;
245                                lastRow = rowno;
246                        }
247
248                        final String lbl   = rowData( row,noName,token );               // 表示するラベル(無ければトークンそのもの)
249                        final String dbType= rowData( row,noType,"TP_TEXT" );   // DBタイプ。未指定時は、TP_TEXT:テキスト
250
251                        final DBColumn clm = resource.makeDBColumn( dbType,lbl );
252                        final DBColumnConfig config = clm.getConfig();
253                        config.setName( "VAL" );                                                                // 登録するカラムの名前は、VAL になる。
254
255                        final String defVal = timeVal( dbType,rowData( row,noDefval,"" ) );     // 初期値  7.4.2.1 (2021/05/21)
256                        final String val = rowData( row,noVal,defVal );
257
258                        final String cdrec = rowData( row,noCdrec ,"0" );       // 0:未決 1:不要 2:任意        4:保留        6:必須
259                        final String judg  = rowData( row,noJudg  ,"0" );       // 0:未決 1:不要 2:任意 3:合格 4:保留 5:警告 6:必須 7:不合格
260
261                        final Attributes editAttri = config.getEditorAttributes();              // 内部オブジェクトを取得している。
262//                      final String optAttr = changeOptAttr( editAttri, rowData( row,noOptAttr,"" ) ); // オプション属性(OPT_ATTR)
263                        final String optAttr = changeOptAttr( config, editAttri, rowData( row,noOptAttr,"" ) ); // 7.4.2.3 (2021/06/09) オプション属性(OPT_ATTR)
264
265                        final boolean isEmp = val.isEmpty()                                                                                                     // 本当にempty
266                                                                        || ( "TP_CHECK".equals( dbType ) && "0".equals(val) );          // TP_CHECK が '0' は、未設定と同じ
267
268                        if( isEmp ) {                                                                           // val が未設定の場合は、cdrec をそのままセットする。
269                                editAttri.add( "class" , "judg" + cdrec );              // 注意:キーは、judg
270                        }
271                        else {                                                                                          // val が設定済みの場合は、judg をセットする。
272                                editAttri.add( "class" , "judg" + judg );
273                        }
274
275        //              日付フォーマットが日本風じゃないので、取りやめ
276        //              // dbType が、TP_YMD,TP_HMS,TP_YMDH の場合は、日付、時刻のみ可能とする。
277        //              if( "TP_YMD".equals( dbType ) ) {
278        //                      editAttri.set( "type" , "date" );                               // type="date" で日付
279        //              }
280        //              else if( "TP_HMS".equals( dbType ) ) {
281        //                      editAttri.set( "type" , "time" );                               // type="time" だけだと、秒が入らない。
282        //                      editAttri.set( "step" , "1" );                                  // 秒 を有効にするには、step="1" にする。
283        //              }
284
285                        // dbType が、TP_INT の場合は、数値のみ可能とする。
286                        if( "TP_INT".equals( dbType ) ) {
287                                editAttri.set( "type" , "number" );             // type="number" だけだと、整数しか入らない。
288                                // Editor_NUMBER は、フォーマット指定できない。TableFilter_JUDG で書き戻しして、カンマを削除している。
289                        }
290                        else if( "TP_REAL".equals( dbType ) ) {
291                                editAttri.set( "type" , "number" );             // type="number" だけだと、整数しか入らない。
292                                editAttri.set( "step" , "0.01"   );             // とりあえず、無条件に、0.01 刻みにする。
293                                config.setEditorParam( "#0.00" );               // 7.4.2.3 (2021/06/09) 数値フォーマット(カンマ無し、桁数指定)を設定
294                        }
295
296        //              if( noViewlen >= 0 ) { config.setFieldSize( table.getValue( row,noViewlen ) ); }        // フィールドの表示桁数
297                        final String viewLen = rowData( row,noViewlen,"" );             // フィールドの文字桁数
298                        if( !viewLen.isEmpty() ) {
299                                config.setMaxlength( viewLen );
300
301                                // dbType が、TP_REAL の場合は、表示桁数 に合わせて、step を再設定する。
302                                if( "TP_REAL".equals( dbType ) ) {
303                                        if(              viewLen.contains( ".1" ) ) {
304                                                editAttri.set( "step" , "0.1" );
305        //                                      config.setEditorParam( "#0.0" );                        // 7.4.2.3 (2021/06/09)  数値フォーマット(カンマ無し、桁数指定)を設定
306                                        }
307                                        else if( viewLen.contains( ".2" ) ) {
308                                                editAttri.set( "step" , "0.01" );
309        //                                      config.setEditorParam( "#0.00" );                       // 7.4.2.3 (2021/06/09)  数値フォーマット(カンマ無し、桁数指定)を設定
310                                        }
311                                        else if( viewLen.contains( ".3" ) ) {
312                                                editAttri.set( "step" , "0.001" );
313        //                                      config.setEditorParam( "#0.000" );                      // 7.4.2.3 (2021/06/09)  数値フォーマット(カンマ無し、桁数指定)を設定
314                                        }
315                                        else {
316                                                editAttri.set( "step" , "any" );
317                                        }
318                                }
319                        }
320
321                        final String cddisp = rowData( row,noCddisp,"1" );              // 1:ラベルカラム 2:カラム単発 3:カラム連続 4:表示のみ
322
323                        // 表示方法(CDDISP)=3:カラム連続 かつ ラベルの末尾が数字の場合は、①~の番号を placeholder にセットする。
324                        if( "3".equals( cddisp ) && !optAttr.contains( "placeholder" ) ) {                      // オプション属性 に plaseholder が未設定の場合のみ
325                                int idx = lbl.length()-1;
326                                while( idx >= 0 ) {
327                                        final char ch = lbl.charAt( idx );
328                                        if( '0' <= ch && ch <= '9' ) { idx--; }         // 末尾から数字の間、処理を継続
329                                        else { break; }                                                         // 数字でなくなった時点で処理終了
330                                }
331                                if( idx >= 0 && idx < lbl.length()-1 ) {                // 末尾に何らかの数値があった場合
332                                        final char plc = (char)('①' + ( Integer.parseInt(lbl.substring( idx+1 ))-1 ) );
333                                        editAttri.set( "placeholder" , String.valueOf( plc ) );
334                                }
335                        }
336
337//                      // 理由 があれば、title に入れて popup させる
338//                      final String riyu= rowData( row,noRiyu,"" );
339//                      if( ! riyu.isEmpty() ) {
340//                              editAttri.set( "title" , riyu );
341//                      }
342
343                        // 登録方法(CDREC) が、1:不要 か、書き込み禁止(isWritable( row )がfalse) の場合は、disabled をセットする。
344                        if( "1".equals( cdrec ) || !isWritable( row ) ) {
345                                editAttri.set( "disabled" , "disabled" );               // 1:不要 は、disabled をセットする。
346                        }
347
348        //              config.setEditorAttributes( editAttri );                        // 内部オブジェクトを追加しているので、再設定不要。追加する場合に使用する。
349
350                        if( "TP_FILE".equals( dbType ) ) {                                      // ファイル添付は、値をアップロードするフォルダとする。
351                                config.setEditorParam( "UPLOAD_DIR=" + val );   // jsp 以下のフォルダになります。
352                        }
353
354                        // RENDERE_TYPE="TP_IFRAME,TP_IMAGE,TP_LINE,TP_SIGNAL" は、Rendereを使う。
355                        // 表示方法(CDDISP) 4:表示のみ の場合も、Rendereを使う。
356        //              final String valBuf ;
357                        String valBuf ;
358//                      if( "TP_IFRAME".equals( dbType ) || "TP_IMAGE".equals( dbType ) || "TP_LINE".equals( dbType ) || "TP_SIGNAL".equals( dbType ) || "4".equals( cddisp ) ) {
359                        if( RENDERE_TYPE.contains( dbType ) || "4".equals( cddisp ) ) {
360                                if( !optAttr.isEmpty() ) {
361        //                              final Attributes rendAttri = config.getRendererAttributes();    // 内部オブジェクトを取得している。
362        //                              rendAttri.add( "optionAttributes" , optAttr );
363                                        config.setRendererParam( optAttr );
364                                }
365                                valBuf = new DBColumn( config ).getRendererValue( row,val );            // 表示欄(Renderer)
366                        }
367                        else {
368                                if( !optAttr.isEmpty() ) {
369                                        editAttri.add( "optionAttributes" , optAttr );
370        //                              config.setEditorParam( optAttr );
371                                }
372
373                                // dbType が、TP_MENU かTP_RADIO の場合は、DBMENUの引数($2)に、トークンを渡す。(GG03を検索する)
374                                String tempVal = val;
375                                if( "TP_MENU".equals( dbType ) || "TP_RADIO".equals( dbType ) ) {
376                                        config.setAddNoValue( true );           // プルダウンメニューは、空欄を選択できるようにしておく
377                                        tempVal = val + ":" + token;            // DBMENUの引数($2)
378                                }
379
380                                valBuf = new DBColumn( config ).getEditorValue( row,tempVal );          // 入力欄(Editor)
381                        }
382
383                        // 理由 があれば、valBuf の直後にそのまま入れる。
384                        final String riyu= rowData( row,noRiyu,"" );
385                        if( ! riyu.isEmpty() ) {
386                                valBuf = valBuf + riyu ;
387                        }
388
389                        final int colspan = rowData( row,noColspan,1 );
390                        final int rowspan = rowData( row,noRowspan,1 );
391
392                        // 表示方法(CDDISP) 1:ラベルカラム 2:カラム単発 3:カラム連続 4:表示のみ で、
393                        // 3:カラム連続 の場合のみ、</td> の直前に INSERT する。
394
395                        // ラベル項目の表示
396                        if( "1".equals( cddisp ) ) {                                                    // CDDISP=1:ラベルカラム の場合、ラベル表示される(colspanなし)
397                                if( 1 == rowspan ) {
398                                        tagOut.append( "<td class='label'>" );
399                                }
400                                else {
401                                        tagOut.append( "<td class='label' rowspan=\"" ).append( rowspan ).append( "\">" );
402                                }
403                                tagOut.append(lbl).append( "</td>" );
404                        }
405
406                        if( "1".equals( cddisp ) || "2".equals( cddisp ) ) {    // CDDISP=1:ラベルカラム or 2:カラム単発 の場合 td が必要。
407                                tagOut.append( "<td" );
408                                // colspan を反映させるのは、フィールド部のみ
409                                if( 1 != colspan ) {
410                                        tagOut.append( " colspan=\"" ).append( colspan ).append( '"' ); // ラベルと本体があるが自分で調整する。
411                                }
412
413                                // rowspan を反映させるのは、フィールド部のみ
414                                if( 1 != rowspan ) {
415                                        tagOut.append( " rowspan=\"" ).append( rowspan ).append( '"' );
416                                }
417                                tagOut.append( '>' ).append( valBuf ).append( INS_VAL );                        // INS_VAL は、CDDISP=3:カラム連続 の場合の挿入位置
418                                taniErrWarn( row,tagOut );
419                        }
420
421                        // 4:表示のみ の場合は、td を出す。
422                        if( "4".equals( cddisp ) ) {                                                    // CDDISP=4:表示のみ の場合 td が必要。
423                                tagOut.append( "<td" );
424                                // colspan を反映させる
425                                if( 1 != colspan ) {
426                                        tagOut.append( " colspan=\"" ).append( colspan ).append( '"' ); // colspan をそのまま出す。自分で調整する。
427                                }
428
429                                // rowspan を反映させるのは、フィールド部のみ
430                                if( 1 != rowspan ) {
431                                        tagOut.append( " rowspan=\"" ).append( rowspan ).append( '"' );
432                                }
433                                tagOut.append( '>' ).append( valBuf ).append( "</td>" );        // valBuf を使うが、先にRenderer の値を設定している。
434                        }
435
436                        if( "3".equals( cddisp ) ) {                                                    // CDDISP=3:カラム連続 の場合は、INS_VALの直前に挿入する。
437                                final int lstIdx = tagOut.lastIndexOf( INS_VAL ) ;
438                                if( lstIdx >= 0 ) {
439                                        tagOut.insert( lstIdx,valBuf );
440                                }
441                                else {                                                  // ありえないはずだが、挿入位置が見つからない場合は、td で囲う。
442                                        tagOut.append( "<td>" ).append( valBuf ).append( INS_VAL ).append( "</td>" );
443                                }
444                        }
445                }
446
447                tagOut.append(" </tr></tbody></table>").append( CR );
448                if( !bkSelNm.isEmpty() ) { tagOut.append( "</fieldset>" ); }
449                tagOut.append( getScrollBarEndDiv() );
450
451                return tagOut.toString().replace( INS_VAL , "" );                       // 最後に文字列に変換後、INS_VAL は、すべて空文字列に置き換えます。
452        }
453
454        /**
455         * 値出力の終端処理をまとめて行います。
456         *
457         * 全体出力の tagOut に、値フィールドの valBuf と、 tani を連結し、
458         * DBTableModelから、異常下限,警告下限,警告上限,異常上限を、div で固めます。
459         * valBuf は、初期化されます。
460         *
461         * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
462         *
463         * @param       row 行
464         * @param       tagOut 全体出力のStringBuilder
465         * @param       valBuf 値フィールドのStringBuilder(このメソッド内でクリアする)
466         * @param       tani 単位
467         */
468//      private void valBufOut( final int row,final StringBuilder tagOut,final StringBuilder valBuf,final String tani ) {
469        private void taniErrWarn( final int row,final StringBuilder tagOut ) {
470                final String tani = rowData( row,noTani,"(",")" );      // 単位表示(カラムの最後に出す)
471                tagOut.append( tani );
472
473                final String wmin = rowData( row,noWmin,"" );           // 警告下限(W_MIN)
474                final String wmax = rowData( row,noWmax,"" );           // 警告上限(W_MAX)
475                final String emin = rowData( row,noEmin,"" );           // 異常下限(E_MIN)
476                final String emax = rowData( row,noEmax,"" );           // 異常上限(E_MAX)
477
478                if( wmin.isEmpty() && wmax.isEmpty() && emin.isEmpty() && emax.isEmpty() ) {
479                        tagOut.append( "</td>" );
480                }
481                else {
482                        tagOut.append( "<div class='small' title='上段:警告&#10;下段:異常'>" );
483
484                        if( !wmin.isEmpty() || !wmax.isEmpty() ) {                      // 警告下限(W_MIN) 警告上限(W_MAX)
485                                final String max = "%" + Math.max( wmin.length() , wmax.length() ) + "s";       // 最大桁数を求める。
486                                tagOut.append( String.format( max + "~" + max ,wmin,wmax ) );
487                        }
488                        tagOut.append( "</br>" );
489
490                        if( !emin.isEmpty() || !emax.isEmpty() ) {                      // 異常下限(E_MIN) 異常上限(E_MAX)
491                                final String max = "%" + Math.max( emin.length() , emax.length() ) + "s";       // 最大桁数を求める。
492                                tagOut.append( String.format( max + " ~ " + max ,emin,emax ) );                 // 異常の範囲の方が広いので、間を少し開けている。
493                        }
494                        tagOut.append( "</div></td>" );
495                }
496        }
497
498        /**
499         * DBTableModelから、指定行-列のデータを取得します。
500         *
501         * 列番号がマイナスの場合は、カラムが存在していないため、初期値を返します。
502         *
503         * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
504         *
505         * @param       row 行
506         * @param       col 列
507         * @param       defVal 初期値(文字列)
508         *
509         * @return 指定行-列のデータ(文字列)
510         */
511        private String rowData( final int row , final int col , final String defVal ) {
512                String rtn = defVal ;
513                if( col >= 0 ) {
514                        final String str = table.getValue( row,col );
515                        if( str != null && !str.isEmpty()) {
516                                rtn = str ;
517                        }
518                }
519                return rtn ;
520        }
521
522        /**
523         * DBTableModelから、指定行-列のデータを取得し、前後に文字列を追加します。
524         *
525         * 列番号がマイナスの場合は、カラムが存在していないため、長さゼロの文字列を返します。
526         * 取得したデータが、長さゼロの文字列でない場合は、前後に指定の文字列を追加して返します。
527         *
528         * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
529         *
530         * @param       row 行
531         * @param       col 列
532         * @param       prefix 前に付ける文字列
533         * @param       suffix 後ろに付ける文字列
534         *
535         * @return 指定行-列のデータ(文字列)
536         */
537        private String rowData( final int row , final int col , final String prefix , final String suffix ) {
538                String rtn = "" ;
539                if( col >= 0 ) {
540                        final String str = table.getValue( row,col );
541                        if( str != null && !str.isEmpty()) {
542                                rtn = prefix + str + suffix ;
543                        }
544                }
545                return rtn ;
546        }
547
548        /**
549         * DBTableModelから、指定行-列のデータを取得します。
550         *
551         * 列番号がマイナスの場合は、カラムが存在していないため、初期値を返します。
552         *
553         * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
554         *
555         * @param       row 行
556         * @param       col 列
557         * @param       defVal 初期値(数値)
558         *
559         * @return 指定行-列のデータ(数値)
560         */
561        private int rowData( final int row , final int col , final int defVal ) {
562                final String val = col < 0 ? null : table.getValue( row,col ) ;
563
564                try {
565                        return val == null || val.isEmpty() ? defVal : Integer.parseInt( val );
566                }
567                catch( final NumberFormatException ex ) {
568                        final String errMsg = "数値項目に数値以外の文字が含まれています。行=" + row + " , 列=" + col + " , 値=" + val ;
569                        throw new HybsSystemException( errMsg,ex );
570                }
571        }
572
573        /**
574         * DBタイプ TP_YMD、TP_HMS、TP_YMDH の初期値に現在時刻を求めます。
575         *
576         * DBタイプが、上記以外か、val が NOW 以外の場合は、val をそのまま返します。
577         *
578         * @og.rev 7.3.1.1 (2021/02/25) USE_CSV 属性追加
579         *
580         * @param       dbType  DBタイプ TP_YMD、TP_HMS、TP_YMDH 以外は、引数のval をそのまま返します。
581         * @param       val             変換する文字列 NOW 以外の場合は、引数のval をそのまま返します。
582         *
583         * @return 日時タイプの初期値を求めます
584         */
585        private String timeVal( final String dbType , final String val ) {
586                String rtn = val;
587                if( "NOW".equalsIgnoreCase( val ) ) {
588                        final LocalDateTime now = LocalDateTime.now();
589
590                        if( "TP_YMD".equals(dbType) ) {
591                                rtn = now.format( YMD );
592                        }
593                        else if( "TP_HMS".equals(dbType) ) {
594                                rtn = now.format( HM );
595                        }
596                        else if( "TP_YMDH".equals(dbType) ) {
597                                rtn = now.format( YMDHMS );
598                        }
599                }
600
601                return rtn ;
602        }
603
604        /**
605         * オプション属性の特殊記号を変換します。
606         *
607         * シングルクオートは必要です。『=』から後ろ全てをそのまま 適用します。
608         *
609         * 【これらの設定は、Attributesオブジェクトに設定します】
610         *  P='AAA'                    → placeholder='AAA'
611         *  T='ツールチップ'                → title='ツールチップ'
612         *  C='HALF'                   → class='HALF'
613         *  S='XX:BB;YY:CC;'           → style='XX:BB;YY:CC;'
614         *
615         * 【これらは変換して属性に設定します】
616         *  W='120px'                  → width='120px'
617         *  H='300px'                  → height='300px'
618         *  D='GRP1'                   → dis='GRP1'                             DIS で指定したグループを disabled するときのキー
619         *
620         * 【DISはonchangeで書き込み不可制御を行います】
621         *  DIS(・・・・)                  → onchange=disabl(this.・・・・)
622         *    以下、変換は同一です。
623         *  DIS(value=='123','GRP1')   → onchange=disabl(this.value=='123','GRP1')              textfieldなど
624         *  DIS(checked,'GRP1')        → onchange=disabl(this.checked,'GRP1')                   チェックボックス
625         *  DIS(selectedIndex==2,'GRP1')                       → onchange=disabl(selectedIndex==2,'GRP1')       プウルダウンメニュー(インデックス判定)
626         *  DIS(options[selectedIndex].value=='テスト2','GRP1')   → onchange=disabl(options[selectedIndex].value=='テスト2','GRP1')   プウルダウンメニュー(値判定)
627         *
628         * 【ANVは、addNoValue制御の略で、メニューに空オプションを追加できます】
629         *  ANV='true'                                  → DBColumnConfig#setAddNoValue(true) を設定します。
630         * 【AKLは、addKeyLabel制御の略で、trueでキー:ラベル形式、falseでラベルのみ、null(未指定)はリソースに準拠します】
631         *  AKL='true'                                  → DBColumnConfig#setAddKeyLabel("true") を設定します。
632         *
633         * 【それ以外は、変換なしで設定します】
634         *  readonly                   → readonly               (例)
635         *
636         * @og.rev 7.3.1.1 (2021/02/25) オプション属性の特殊記号を変換
637         * @og.rev 7.4.2.3 (2021/06/09) addNoValue制御、addKeyLabel制御を追加
638         * @og.rev 8.0.0.0 (2021/08/20) rows,cols 属性を処理できるようにします。
639         *
640         * @param       attri   Attributesオブジェクト
641         * @param       val             変換するオプション属性
642         *
643         * @return 変換後のオプション属性
644         */
645//      private String changeOptAttr( final Attributes attri,final String val ) {
646        private String changeOptAttr( final DBColumnConfig config , final Attributes attri,final String val ) {
647                if( val.isEmpty() ) { return val; }                             // 確率的に、空文字はそのまま返します。
648
649                final StringBuilder buf = new StringBuilder( val );     // 最終的に出力するタグの文字列バッファ
650
651        // 【これらの設定は、Attributesオブジェクトに設定します】
652        //  P='AAA'                    → placeholder='AAA'
653        //  T='ツールチップ'                → title='ツールチップ'
654        //  C='HALF'                   → class='HALF'
655        //  S='XX:BB;YY:CC;'           → style='XX:BB;YY:CC;'
656
657                keyChange( buf,"P=", key -> attri.set( "placeholder",key ) );
658                keyChange( buf,"T=", key -> attri.set( "title",key ) );
659                keyChange( buf,"C=", key -> attri.add( "class",key ) );
660                keyChange( buf,"S=", key -> attri.add( "style",key ) );
661
662                // 8.0.0.0 (2021/08/20) rows,cols 属性を処理できるようにします。
663                keyChange( buf,"rows=", key -> attri.set( "rows",key ) );
664                keyChange( buf,"cols=", key -> attri.set( "cols",key ) );
665
666        // 【ANVは、addNoValue制御の略で、メニューに空オプションを追加できます】
667        //  ANV='true'                                  → DBColumnConfig#setAddNoValue(true) を設定します。
668        // 【AKLは、addKeyLabel制御の略で、trueでキー:ラベル形式、falseでラベルのみ、null(未指定)はリソースに準拠します】
669        //  AKL='true'                                  → DBColumnConfig#setAddKeyLabel("true") を設定します。
670                keyChange( buf,"ANV=", key -> config.setAddNoValue( Boolean.parseBoolean( key ) ) );
671                keyChange( buf,"AKL=", key -> config.setAddKeyLabel( key ) );
672
673//              int st = 0;
674//              int ed = 0;
675
676//              st = buf.indexOf( "P=" );
677//              if( st >= 0 ) {
678//                      ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );          // シングルか、ダブルか
679//                      attri.set( "placeholder" , buf.substring( st+3,ed ) );  // P= の後ろ+1から、最後のコーテーション(含まず)まで
680//                      buf.delete( st,ed+1 );
681//              }
682
683//              st = buf.indexOf( "T=" );
684//              if( st >= 0 ) {
685//                      ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
686//                      attri.set( "title" , buf.substring( st+3,ed ) );                // T= の後ろ+1から、最後のコーテーション(含まず)まで
687//                      buf.delete( st,ed+1 );
688//              }
689
690//              st = buf.indexOf( "C=" );
691//              if( st >= 0 ) {
692//                      ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
693//                      attri.add( "class" , buf.substring( st+3,ed ) );                // T= の後ろ+1から、最後のコーテーション(含まず)まで
694//                      buf.delete( st,ed+1 );
695//              }
696
697//              st = buf.indexOf( "S=" );
698//              if( st >= 0 ) {
699//                      ed = Math.max( buf.indexOf( "'" , st+3 ),buf.indexOf( "\"" , st+3 ) );
700//                      attri.add( "style" , buf.substring( st+3,ed ) );                // T= の後ろ+1から、最後のコーテーション(含まず)まで
701//                      buf.delete( st,ed+1 );
702//              }
703
704//              // 7.4.2.3 (2021/06/09) addNoValue制御追加
705//              st = buf.indexOf( "ANV=" );
706//              if( st >= 0 ) {
707//                      ed = Math.max( buf.indexOf( "'" , st+5 ),buf.indexOf( "\"" , st+5 ) );
708//                      config.setAddNoValue( Boolean.parseBoolean( buf.substring( st+5,ed ) ) );
709//                      buf.delete( st,ed+1 );
710//              }
711
712//              // 7.4.2.3 (2021/06/09) addKeyLabel制御を追加
713//              st = buf.indexOf( "AKL=" );
714//              if( st >= 0 ) {
715//                      ed = Math.max( buf.indexOf( "'" , st+5 ),buf.indexOf( "\"" , st+5 ) );
716//                      config.setAddKeyLabel( buf.substring( st+5,ed ) );              // 空文字列は、処理不定
717//                      buf.delete( st,ed+1 );
718//              }
719
720                return buf.toString()
721                                .replace( "W=" , "width=" )
722                                .replace( "H=" , "height=" )
723                                .replace( "D=" , "dis=" )
724                                .replace( "DIS(" , "onchange=disabl(this." ) ;
725        }
726
727        /**
728         * キーワードをStringBuilderから見つけ、置換するための関数型ファンクションを呼び出します。
729         * キーワードが見つからない場合は、処理しません。
730         *
731         * @og.rev 8.0.0.0 (2021/08/20) 共通の処理を関数型ファンクションと置き換えます。
732         *
733         * @param buf   StringBuilderで、key が存在すれば置換処理を行い、bufから削除します。
734         * @param key   存在チェックと置換元
735         * @param cons  置換処理の関数型Consumerインターフェース
736         *
737         * @see #changeOptAttr(DBColumnConfig , Attributes , String)
738         */
739        private static void keyChange( final StringBuilder buf , final String key , final Consumer<String> cons ) {
740                final int st = buf.indexOf( key );
741                if( st >= 0 ) {
742                        final int stLen = st + key.length() + 1;
743                        final int ed = Math.max( buf.indexOf( "'" , stLen ),buf.indexOf( "\"" , stLen ) );      // シングルか、ダブルか
744                        cons.accept( buf.substring( stLen,ed ) );
745                        buf.delete( st,ed+1 );
746                }
747        }
748
749        /**
750         * DBTableModel から テーブルのヘッダータグ文字列を作成して返します。
751         *
752         * @og.rev 3.5.2.0 (2003/10/20) ヘッダーそのもののキャッシュはしない。
753         *
754         * @return      テーブルのヘッダータグ文字列
755         * @og.rtnNotNull
756         */
757//      protected String getHeader() {
758//              // ヘッダーは不要なので、オーバーライドしておく。
759//              return "" ;
760//      }
761}