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