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 org.opengion.fukurou.system.OgBuilder ; // 6.4.4.2 (2016/04/01) 019import org.opengion.fukurou.util.StringUtil; 020import org.opengion.hayabusa.common.HybsSystem; 021import org.opengion.hayabusa.common.HybsSystemException; 022import org.opengion.hayabusa.db.DBTableModel; 023import org.opengion.hayabusa.html.TableFormatter; 024import org.opengion.hayabusa.html.ViewGanttTableParam; 025 026import java.util.regex.Pattern; 027import java.util.regex.Matcher; 028 029import java.util.Locale; 030import java.util.TreeSet; 031import java.util.List; 032import java.util.Date; 033import java.util.Calendar; 034import java.text.SimpleDateFormat; 035import java.text.ParseException; 036 037/** 038 * ガントチャート(テーブル形式)を作成する、ガントチャート表示クラスです。 039 * 040 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。 041 * 各HTMLのタグに必要な setter/getterメソッドのみ,追加定義しています。 042 * 043 * AbstractViewForm を継承している為,ロケールに応じたラベルを出力させる事が出来ます。 044 * 045 * @og.group 画面表示 046 * 047 * @version 4.0 048 * @author Kazuhiko Hasegawa 049 * @since JDK5.0, 050 */ 051public class ViewForm_HTMLGanttTable extends ViewForm_HTMLTable { 052 /** このプログラムのVERSION文字列を設定します。 {@value} */ 053 private static final String VERSION = "7.0.4.0 (2019/05/31)" ; 054 055 // 3.5.4.0 (2003/11/25) TableFormatter クラス追加 056 private TableFormatter headerFormat ; 057 private TableFormatter[] bodyFormats ; 058 private TableFormatter footerFormat ; 059 private int bodyFormatsCount; 060 061 // 繰り返すTD用 3.5.6.0 (2004/06/18) 廃止 062 private TableFormatter[] itdFormats ; // 追加 063 064 private String ganttHeadLine ; 065 private int[] groupCols ; 066 private int posDuration = -1; 067 private int posDystart = -1; 068 069 private String formatDystart = ViewGanttTableParam.DYSTART_FORMAT_VALUE; 070 private double minDuration = StringUtil.parseDouble( ViewGanttTableParam.MIN_DURATION_VALUE ) ; // 3.5.5.8 (2004/05/20) 071 private double headerDuration = minDuration ; // 3.5.5.8 (2004/05/20) 072 073 // 3.5.4.6 (2004/01/30) 初期値変更 074 private static final int BODYFORMAT_MAX_COUNT = 10; 075 076 // <(td|th)(.*)>(.*)</(td|th)>を確認する 077 private static final Pattern TDTH_PTN = // 6.4.1.1 (2016/01/16) cpTdTh → TDTH_PTN refactoring 078 Pattern.compile("\\<(td|th)([^\\>]*)\\>(.*)\\</(td|th)\\>" 079 , Pattern.DOTALL + Pattern.CASE_INSENSITIVE); 080 // 3.5.5.9 (2004/06/07) 081 private int maxDayCnt ; 082 private String headerLocale ; 083 private String[] headDays ; 084 private int headDaysCnt ; 085 086 // 3.5.6.3 (2004/07/12) 行チェックによる編集用の hidden 087 private static final String CHECK_ROW = 088 "<input type=\"hidden\" name=\"" + HybsSystem.ROW_SEL_KEY + "\" value=\""; 089 090 // 3.6.1.0 (2005/01/05) 開始日付けと終了日付けの指定 091 private boolean useSeqDay ; 092 private String startDay ; 093 private String endDay ; 094 095 private boolean useItd ; // 5.0.0.3 (2009/09/22) 096 097 /** 098 * デフォルトコンストラクター 099 * 100 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 101 */ 102 public ViewForm_HTMLGanttTable() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 103 104 /** 105 * 内容をクリア(初期化)します。 106 * 107 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。 108 * @og.rev 3.5.0.0 (2003/09/17) Noカラムに、表示を全て消せるように、class 属性を追加。 109 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。 110 * @og.rev 3.5.5.8 (2004/05/20) minDuration , headerDuration 追加。 不要な変数削除 111 * @og.rev 3.5.6.0 (2004/06/18) ithFormat , itdFormat 属性削除、itdFormats属性を追加 112 * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加 113 * @og.rev 5.0.0.3 (2009/09/22) itdタグの有無でcolspan対策のtdの出力個数を調整 114 */ 115 @Override 116 public void clear() { 117 super.clear(); 118 119 headerFormat = null; 120 bodyFormats = null; 121 footerFormat = null; 122 bodyFormatsCount = 0; 123 124 ganttHeadLine = null; 125 itdFormats = null; // 3.5.6.0 (2004/06/18) 126 groupCols = null; 127 posDuration = -1 ; 128 posDystart = -1; 129 formatDystart = ViewGanttTableParam.DYSTART_FORMAT_VALUE; 130 minDuration = StringUtil.parseDouble( ViewGanttTableParam.MIN_DURATION_VALUE ) ; // 3.5.5.8 (2004/05/20) 131 headerDuration = minDuration ; // 3.5.5.8 (2004/05/20) 132 133 maxDayCnt = 0; // 3.5.5.9 (2004/06/07) 134 headerLocale = null; // 3.5.5.9 (2004/06/07) 135 headDays = null; // 3.5.5.9 (2004/06/07) 136 headDaysCnt = 0; // 3.5.5.9 (2004/06/07) 137 138 useSeqDay = false; // 3.6.1.0 (2005/01/05) 139 startDay = null; // 3.6.1.0 (2005/01/05) 140 endDay = null; // 3.6.1.0 (2005/01/05) 141 142 useItd = false; // 5.0.0.3 (2009/09/22) 143 } 144 145 /** 146 * DBTableModel から HTML文字列を作成して返します。 147 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。 148 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。 149 * 150 * @og.rev 3.5.0.0 (2003/09/17) BODY要素の noClass 属性を追加。 151 * @og.rev 3.5.0.0 (2003/09/17) <tr>属性は、元のフォーマットのまま使用します。 152 * @og.rev 3.5.2.0 (2003/10/20) ヘッダー繰り返し属性( headerSkipCount )を採用 153 * @og.rev 3.5.3.1 (2003/10/31) skip属性を採用。headerLine のキャッシュクリア 154 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。 155 * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応 156 * @og.rev 3.5.5.9 (2004/06/07) IEの colspan が上手く動かない対策。 157 * @og.rev 3.5.5.9 (2004/06/07) [#カラム名] , [$カラム名] に対応 158 * @og.rev 3.5.6.0 (2004/06/18) itdFormat を、BODY毎のFormatを使用するように修正 159 * @og.rev 3.5.6.0 (2004/06/18) '!' 値のみ 追加 既存の '$' は、レンデラー 160 * @og.rev 3.5.6.3 (2004/07/12) 行チェックによる編集が出来るように機能を追加 161 * @og.rev 3.5.6.4 (2004/07/16) ヘッダーとボディー部をJavaScriptで分離 162 * @og.rev 3.6.1.0 (2005/01/05) 行チェックによる編集が、検索即登録時も可能なようにします。 163 * @og.rev 4.0.0.0 (2005/01/31) 新規作成(getColumnClassName ⇒ getColumnDbType) 164 * @og.rev 3.7.0.1 (2005/01/31) E の colspan バグ対応で入れた最終行の 空タグを消す為の修正 165 * @og.rev 4.3.1.0 (2008/09/08) フォーマットが設定されていない場合のエラー追加 166 * @og.rev 4.3.7.4 (2009/07/01) tbodyタグの入れ子を解消(FireFox対応) 167 * @og.rev 5.0.0.3 (2009/09/22) itdタグの有無でcolspan対策のtdの出力個数を調整 168 * @og.rev 5.5.4.4 (2012/07/20) 二重チェック状態になってしまう対策 169 * @og.rev 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応 170 * @og.rev 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。 171 * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。 172 * @og.rev 6.4.5.0 (2016/04/08) メソッド変更( getColumnDbType(int) → getClassName(int) ) 173 * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、<td> から <td に変更します(タグの最後が記述されていない状態でもらう)。 174 * @og.rev 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。 175 * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"/>" 止めを、">" に変更します)。 176 * 177 * @param stNo 表示開始位置 178 * @param pgSize 表示件数 179 * 180 * @return DBTableModelから作成された HTML文字列 181 * @og.rtnNotNull 182 */ 183 @Override 184 public String create( final int stNo, final int pgSize ) { 185 // ガントは、キーブレイクがあるため、全件表示します。 186 final int pageSize = getRowCount() ; 187 if( pageSize == 0 ) { return ""; } // 暫定処置 188 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 189 190 // 4.3.1.0 (2008/09/08) 191 if( headerFormat == null ) { 192 final String errMsg = "ViewTagで canUseFormat() = true の場合、Formatter は必須です。"; 193 throw new HybsSystemException( errMsg ); 194 } 195 196 headerLine = null; // 3.5.3.1 (2003/10/31) キャッシュクリア 197 198 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point. 199 // ガントは、キーブレイクがあるため、全件表示します。 200 final int startNo = 0; 201 final int lastNo = getLastNo( startNo, pageSize ); 202 final int blc = getBackLinkCount(); 203 final int hsc = getHeaderSkipCount(); // 3.5.2.0 (2003/10/20) 204 int hscCnt = 1; // 3.5.2.0 (2003/10/20) 205 206 // このビューの特有な属性を初期化 207 paramInit(); 208 209 final StringBuilder out = new StringBuilder( BUFFER_LARGE ); 210 211 out.append( getCountForm( startNo,pageSize ) ); 212 213 if( posDuration < 0 ) { ganttHeadLine = getGanttHeadNoDuration(startNo, lastNo); } 214 else { ganttHeadLine = getGanttHead(startNo, lastNo); } 215 216 out.append( getHeader() ); 217 218 if( bodyFormatsCount == 0 ) { 219 bodyFormats[0] = headerFormat ; 220 bodyFormatsCount ++ ; 221 } 222 else { 223 for( int i=0; i<bodyFormatsCount; i++ ) { 224 bodyFormats[i].makeFormat( getDBTableModel() ); 225 // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応 226 setFormatNoDisplay( bodyFormats[i] ); 227 228 if( itdFormats[i] != null ) { 229 itdFormats[i].makeFormat( getDBTableModel() ); 230 // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応 231 setFormatNoDisplay( itdFormats[i] ); 232 } 233 } 234 } 235 236 String[] astrOldGroupKeys = new String[groupCols.length]; 237 for( int nIndex =0; nIndex<astrOldGroupKeys.length; nIndex++ ) { 238 astrOldGroupKeys[nIndex] = ""; 239 } 240 241 final StringBuilder bodyBuf = new StringBuilder( BUFFER_LARGE ); // 6.1.0.0 (2014/12/26) refactoring 242 final StringBuilder itdBuf = new StringBuilder( BUFFER_LARGE ); // 6.1.0.0 (2014/12/26) refactoring 243 boolean checked = false; // 3.5.6.3 (2004/07/12) 244 int bgClrCnt = 0; 245 TableFormatter itdFormat = null; 246 for( int row=startNo; row<lastNo; row++ ) { 247 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 248 boolean outputCheck = true; // 5.5.4.4. (2012/07/20) チェックボックス出力判定 249 // ガントでは、データのスキップは行いません。 250 251 if( !isSameGroup(row, astrOldGroupKeys) ) { 252 // 3.5.6.3 (2004/07/12) キーブレイク時にチェック行かどうかを記録 253 checked = getDBTableModel().isRowChecked( row ); 254 255 if( row != startNo ) { 256 // ヘッダー日付けが残っているのに、データがなくなった場合 257 while( headDays != null && headDaysCnt < headDays.length ) { 258 itdBuf.append( "<td></td>" ); 259 headDaysCnt++; 260 } 261 out.append(StringUtil.replace(bodyBuf.toString(), TableFormatter.HYBS_ITD_MARKER, itdBuf.toString())); 262 itdBuf.setLength(0); // 6.1.0.0 (2014/12/26) refactoring 263 headDaysCnt = 0; 264 } 265 266 bodyBuf.setLength(0); 267 268 // カラムのグループがブレイクするまで、BodyFormatは同一である前提 269 for( int i=0; i<bodyFormatsCount; i++ ) { 270 final TableFormatter bodyFormat = bodyFormats[i]; 271 if( ! bodyFormat.isUse( row,getDBTableModel() ) ) { continue; } // 3.5.4.0 (2003/11/25) 272 itdFormat = itdFormats[i]; // 273 274 bodyBuf.append("<tbody") 275 .append( getBgColorCycleClass( bgClrCnt++ ) ) 276 .append('>') // 6.0.2.5 (2014/10/31) char を append する。 277 .append( bodyFormat.getTrTag() ); 278 279 // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加 280 if( isNumberDisplay() ) { 281 final String ckboxTD = "<td" + bodyFormat.getRowspan(); // 6.8.1.1 (2017/07/22) 282 bodyBuf.append( makeCheckbox( ckboxTD,row,blc,true ) ); // 6.8.2.0 (2017/10/13) 283 outputCheck = false; // 5.5.4.4. (2012/07/20) 284 } 285 286 int cl = 0; 287 for( ; cl<bodyFormat.getLocationSize(); cl++ ) { 288 String fmt = bodyFormat.getFormat(cl); 289 final int loc = bodyFormat.getLocation(cl); // 3.5.5.0 (2004/03/12) 290 // 6.1.0.0 (2014/12/26) refactoring 291 if( ! bodyFormat.isNoClass() && loc >= 0 ) { 292 // 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。 293 final int idx = fmt.lastIndexOf( "<td" ); 294 if( idx >= 0 ) { // matchしてるので、あるはず 295 final String tdclass = " class=\"" + getClassName(loc) + "\" "; // 6.4.5.0 (2016/04/08) 296 fmt = fmt.substring( 0,idx+3 ) + tdclass + fmt.substring( idx+3 ) ; 297 } 298 } 299 bodyBuf.append( fmt ); // 3.5.0.0 (2003/09/17) 300 // 3.5.5.9 (2004/06/07) #,$ 対応 301 if( loc >= 0 ) { 302 // 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。 303 bodyBuf.append( getTypeCaseValue( bodyFormat.getType(cl),row,loc ) ); 304 } 305 else { 306 bodyBuf.append( bodyFormat.getSystemFormat(row,loc) ); 307 } 308 } 309 bodyBuf.append( bodyFormat.getFormat(cl) ) 310 .append("</tbody>").append( CR ); 311 } 312 313 // 3.5.2.0 (2003/10/20) ヘッダー繰り返し属性( headerSkipCount )を採用 314 if( hsc > 0 && hscCnt % hsc == 0 ) { 315 bodyBuf.append("<tbody class=\"row_h\" >") 316 .append( getHeadLine() ) 317 .append("</tbody>"); 318 hscCnt = 1; 319 } 320 else { 321 hscCnt ++ ; 322 } 323 } 324 325 // 3.5.6.3 (2004/07/12) キーブレイク時のチェック行の状態を繰り返し行に反映 326 // 3.6.1.0 (2005/01/05) さらに、外部でチェックを入れている場合は、個別対応する。 327 if( (checked || isChecked( row )) && outputCheck ) { // 5.5.4.4. (2012/07/20) 328 getDBTableModel().setRowWritable( row,true ); 329 out.append( CHECK_ROW ) 330 .append( row ) 331// .append( "\" />" ); 332 .append( "\" >" ); // 7.0.1.0 (2018/10/15) 333 } 334 335 formatItd(row, itdFormat, itdBuf); 336 } 337 338 // 残ったデータを出力 339 if( itdBuf.length() > 0 ) { 340 // ヘッダー日付けが残っているのに、データがなくなった場合 341 while( headDays != null && headDaysCnt < headDays.length ) { 342 itdBuf.append( "<td></td>" ); 343 headDaysCnt++; 344 } 345 out.append(StringUtil.replace(bodyBuf.toString(), TableFormatter.HYBS_ITD_MARKER, itdBuf.toString())); 346 } 347 348 // 3.5.5.9 (2004/06/07) IEの colspan が上手く動かない対策。 349 // minDuration が、1.0 以下の場合のみ実行 350 if( minDuration < 1.0d ) { 351 // int tdCount = (int)Math.round( maxDayCnt / minDuration ); 352 // 5.0.0.3 (2009/09/22) itdタグの有無でtdCountを変える。 353 final int tdCount = useItd ? (int) Math.round( maxDayCnt / minDuration ) : headerFormat.getLocationSize(); 354 355 // 3.7.0.1 (2005/01/31) E の colspan バグ対応で入れた最終行の 空タグを消す為の修正 356 out.append("<tbody><tr class=\"dummy\">").append( CR ); 357 for( int i=0; i<tdCount; i++ ) { 358// out.append( "<td/>" ); 359 out.append( "<td></td>" ); // 7.0.1.0 (2018/10/15) 360 } 361 out.append("</tr></tbody>").append( CR ); 362 } 363 364 if( footerFormat != null ) { 365 // 6.3.9.0 (2015/11/06) 引数にTableFormatterを渡して、処理の共有化を図る。 366 out.append( getTableFoot( footerFormat ) ); 367 } 368 369 out.append("</table>").append( CR ) 370 .append( getScrollBarEndDiv() ); // 3.8.0.3 (2005/07/15) 371 372 return out.toString(); 373 } 374 375 /** 376 * このビーに対する特別な初期化を行う。 377 * 378 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。 379 * @og.rev 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更 380 * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加 381 */ 382 private void paramInit() { 383 384 final String strGroupCols = getParam( ViewGanttTableParam.GROUP_COLUMNS_KEY ,ViewGanttTableParam.GROUP_COLUMNS_VALUE ); 385 final String strColDuration = getParam( ViewGanttTableParam.DURATION_COLUMN_KEY ,null ); 386 final String strColDystart = getParam( ViewGanttTableParam.DYSTART_COLUMN_KEY ,ViewGanttTableParam.DYSTART_COLUMN_VALUE ); 387 final String strMinDuration = getParam( ViewGanttTableParam.MIN_DURATION_KEY ,ViewGanttTableParam.MIN_DURATION_VALUE ); 388 389 formatDystart = getParam( ViewGanttTableParam.DYSTART_FORMAT_KEY ,ViewGanttTableParam.DYSTART_FORMAT_VALUE ); 390 headerLocale = getParam( ViewGanttTableParam.HEADER_LOCALE_KEY ,ViewGanttTableParam.HEADER_LOCALE_VALUE ); 391 startDay = getParam( ViewGanttTableParam.START_DAY_KEY ,null ); // 3.6.1.0 (2005/01/05) 392 endDay = getParam( ViewGanttTableParam.END_DAY_KEY ,null ); // 3.6.1.0 (2005/01/05) 393 394 final String seqDay = getParam( ViewGanttTableParam.USE_SEQ_DAY_KEY ,ViewGanttTableParam.USE_SEQ_DAY_VALUE ); // 3.6.1.0 (2005/01/05) 395 useSeqDay = Boolean.parseBoolean( seqDay ); // 6.1.0.0 (2014/12/26) refactoring 396 397 final DBTableModel table = getDBTableModel(); 398 399 // 3.5.5.9 (2004/06/07) durationColumn を指定しない場合の処理を追加 400 if( strColDuration != null ) { 401 posDuration = table.getColumnNo( strColDuration ); 402 } 403 404 posDystart = table.getColumnNo( strColDystart ); 405 406 final String[] groupKeys = StringUtil.csv2Array(strGroupCols); 407 groupCols = new int[groupKeys.length]; 408 for( int nIndex=0; nIndex<groupCols.length ; nIndex++ ) { 409 groupCols[nIndex] = table.getColumnNo( groupKeys[nIndex] ); 410 } 411 412 minDuration = StringUtil.parseDouble( strMinDuration ); 413 if( minDuration <= 0.0d ) { 414 final String errMsg = "最小期間単位(minDuration)が、0 かそれ以下です。"; 415 throw new HybsSystemException( errMsg ); 416 } 417 418 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point. 419 final String strHeadDuration= getParam( ViewGanttTableParam.HEADER_DURATION_KEY ,strMinDuration ); 420 headerDuration = StringUtil.parseDouble( strHeadDuration ); 421 if( headerDuration <= 0.0d ) { 422 final String errMsg = "ヘッダーの表示期間(headerDuration)が、0 かそれ以下です。"; 423 throw new HybsSystemException( errMsg ); 424 } 425 426 // 3.5.5.9 (2004/06/07) エラーチェックの強化 427 // 4.0.0 (2005/01/31) Equality checks with floating point numbers can lead to unexpected behavior. 428 if( posDuration < 0 && ( 429 Double.compare( minDuration,1.0d ) != 0 || 430 Double.compare( headerDuration,1.0d ) != 0 ) ) { 431 432 final String errMsg = "期間カラム(durationColumn)を指定しない場合は、" 433 + "最小期間単位(minDuration)および、" 434 + "ヘッダーの表示期間(headerDuration)を '1' 以外に設定できません。"; 435 throw new HybsSystemException( errMsg ); 436 } 437 } 438 439 /** 440 * 上下行のデータが同じグルプかどうかをチェックする。 441 * 442 * @param nRowIndex テーブルモデルの行番号 443 * @param astrOldValues 古いグループデータ配列 444 * 445 * @return 使用可能(true)/ 使用不可能 (false) 446 */ 447 private boolean isSameGroup(final int nRowIndex, final String[] astrOldValues) { 448 boolean bRet = groupCols.length > 0 ; 449 if( bRet ) { 450 for( int nIndex=0; bRet && nIndex<groupCols.length ; nIndex++ ) { 451 bRet = astrOldValues[nIndex].equals( getValue( nRowIndex, groupCols[nIndex] ) ); 452 } 453 454 // 不一致時に astrOldValues に 新しい値を設定しておきます。 455 if( !bRet ) { 456 for( int nIndex=0; nIndex<groupCols.length; nIndex++ ) { 457 astrOldValues[nIndex] = getValue(nRowIndex, groupCols[nIndex]); 458 } 459 } 460 } 461 462 return bRet; 463 } 464 465 /** 466 * DBTableModel から テーブルのタグ文字列を作成して返します。 467 * 468 * @og.rev 3.5.0.0 (2003/09/17) <tr>属性は、元のフォーマットのまま使用します。 469 * @og.rev 3.5.1.0 (2003/10/03) Noカラムに、numberType 属性を追加 470 * @og.rev 3.5.2.0 (2003/10/20) ヘッダー繰り返し部をgetHeadLine()へ移動 471 * @og.rev 3.5.3.1 (2003/10/31) VERCHAR2 を VARCHAR2 に修正。 472 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。 473 * @og.rev 3.5.6.5 (2004/08/09) thead に、id="header" を追加 474 * @og.rev 4.0.0.0 (2005/01/31) DBColumn の 属性(CLS_NM)から、DBTYPEに変更 475 * @og.rev 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応 476 * @og.rev 5.9.1.2 (2015/10/23) 自己終了警告対応 477 * @og.rev 6.4.4.1 (2016/03/18) NUMBER_DISPLAYを、static final 定数化します。 478 * @og.rev 6.4.4.2 (2016/04/01) StringBuilderの代わりに、OgBuilderを使用する。 479 * @og.rev 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄) 480 * @og.rev 6.4.9.1 (2016/08/05) colgroupのHTML5対応(No欄)時の対応ミス修正 481 * @og.rev 6.8.1.0 (2017/07/14) HTML5対応ヘッダー出力設定時に、ブラウザを互換設定したときの対応。 482 * @og.rev 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。 483 * @og.rev 7.0.4.0 (2019/05/31) colgroup 廃止 484 * 485 * @return テーブルのタグ文字列 486 * @og.rtnNotNull 487 */ 488 @Override 489 protected String getTableHead() { 490 headerFormat.makeFormat( getDBTableModel() ); 491 // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応 492 setFormatNoDisplay( headerFormat ); 493 494 // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄) 495 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 496 497// // 7.0.4.0 (2019/05/31) colgroup 廃止 498// if( isNumberDisplay() ) { 499// // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄) 500// buf.append( NUMBER_DISPLAY ); // 6.8.1.0 (2017/07/14) HTML5ネイティブ時でも、出力します。 501// // 6.8.1.0 (2017/07/14) HTML5対応ヘッダー出力設定時に、ブラウザを互換設定したときの対応。 502// // 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。 503// // if( !useIE7Header ) { 504// // buf.append( "<style type=\"text/css\">" ).append( CR ); 505// // makeNthChild( buf,2,"BIT" ); 506// // makeNthChild( buf,3,"S9" ); 507// // buf.append( "</style>" ).append( CR ); // 6.4.9.1 (2016/08/05) 508// // } 509// } 510 511 // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄)の関係で、修正します。 512 return new OgBuilder() 513 .appendIf( isNumberDisplay() , buf.toString() ) 514 .appendCR( "<thead id=\"header\">" ) // 3.5.6.5 (2004/08/09) 515 .append( getHeadLine() ) 516 .appendCR( "</thead>" ) 517 .toString(); 518 } 519 520 /** 521 * ヘッダー繰り返し部を、getTableHead()メソッドから分離。 522 * 523 * @og.rev 6.1.2.0 (2015/01/24) キャッシュを返すのを、#getHeadLine() に移動。 524 * 525 * @return テーブルのタグ文字列 526 * @og.rtnNotNull 527 */ 528 @Override 529 protected String getHeadLine() { 530 if( headerLine == null ) { // キャッシュになければ、設定する。 531 headerLine = getHeadLine( "<th" + headerFormat.getRowspan() ) ; 532 } 533 534 return headerLine ; 535 } 536 537 /** 538 * ヘッダー繰り返し部を、getTableHead()メソッドから分離。 539 * 540 * @og.rev 3.5.2.0 (2003/10/20) 新規作成 541 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。 542 * @og.rev 3.5.4.3 (2004/01/05) useCheckControl 属性の機能を追加 543 * @og.rev 3.5.4.6 (2004/01/30) numberType="none" 時の処理を追加(Noラベルを出さない) 544 * @og.rev 3.5.4.7 (2004/02/06) ヘッダーにソート機能用のリンクを追加します。 545 * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応 546 * @og.rev 3.7.0.1 (2005/01/31) 全件チェックコントロール処理変更 547 * @og.rev 5.0.0.3 (2009/09/22) itdの有無を取得します。 548 * @og.rev 6.1.2.0 (2015/01/24) キャッシュを返すのを、#getHeadLine() に移動。 549 * 550 * @param thTag タグの文字列 551 * 552 * @return テーブルのタグ文字列 553 * @og.rtnNotNull 554 */ 555 @Override 556 protected String getHeadLine( final String thTag ) { 557 558 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 559 .append( headerFormat.getTrTag() ).append( CR ); 560 561 // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加 562 if( isNumberDisplay() ) { 563 // 6.1.2.0 (2015/01/24) thTag に、headerFormat.getRowspan() を含ませて受け取る。 564 // 3.5.4.3 (2004/01/05) 追加分 565 if( isUseCheckControl() && "checkbox".equals( getSelectedType() ) ) { 566 buf.append( thTag ).append( "></th>" ) 567 .append( thTag ).append( '>' ).append( getAllCheckControl() ).append( "</th>" ) // 6.0.2.5 (2014/10/31) char を append する。 568 .append( thTag ).append( '>' ).append( getNumberHeader() ).append( "</th>" ); // 6.0.2.5 (2014/10/31) char を append する。 569 } 570 else { 571 buf.append( thTag ).append(" colspan=\"3\">").append( getNumberHeader() ).append( "</th>" ); // 6.0.2.5 (2014/10/31) char を append する。 572 } 573 } 574 575 int cl = 0; 576 for( ; cl<headerFormat.getLocationSize(); cl++ ) { 577 buf.append( headerFormat.getFormat(cl) ); 578 final int loc = headerFormat.getLocation(cl); 579 if( loc >= 0 ) { buf.append( getSortedColumnLabel(loc) ); } 580 } 581 buf.append( headerFormat.getFormat(cl) ).append( CR ); 582 583 // 5.0.0.3 (2009/09/22) ITD_MARKERの条件判断追加 584 if( buf.indexOf( TableFormatter.HYBS_ITD_MARKER ) >= 0 ){ 585 useItd = true; 586 } 587 588 return StringUtil.replace( buf.toString(), TableFormatter.HYBS_ITD_MARKER, ganttHeadLine ); 589 } 590 591 /** 592 * ガントチャートヘッダー繰り返し部を、getTableHead()メソッドから分離。 593 * このメソッドは、durationColumn を利用して、連続日付けデータを作成します。 594 * データは、開始日と期間データを持ち、ヘッダーは連続日付けになります。 595 * ヘッダーの期間(headerDuration)とデータの最小期間(minDuration)が異なる為、 596 * 最初の行データより、最終日を求め、headerDuration で割り算した個数の連続日数を 597 * 表示させています。 598 * 599 * @og.rev 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更 600 * @og.rev 3.5.6.0 (2004/06/18) ithFormat 変数削除 601 * 602 * @param startNo 開始行番号 603 * @param lastNo 最終行番号 604 * 605 * @return テーブルのタグ文字列 606 * @og.rtnNotNull 607 */ 608 private String getGanttHead(final int startNo, final int lastNo) { 609 String[] astrOldGroupKeys = new String[groupCols.length]; 610 for( int nIndex =0; nIndex<astrOldGroupKeys.length; nIndex++ ) { 611 astrOldGroupKeys[nIndex] = ""; 612 } 613 614 // 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更 615 final Locale local = new Locale( headerLocale ); 616 final SimpleDateFormat fmtDate = new SimpleDateFormat( formatDystart,local ); 617 618 double nSumDuration = 0.0d ; 619 Date dFirst = null ; 620 621 for( int nRowUp=startNo; nRowUp<lastNo; nRowUp++ ) { 622 // ガントでは、データのスキップは行いません。 623 624 // 最初の行 または、ブレイクするまで 625 if( isSameGroup(nRowUp, astrOldGroupKeys) || nRowUp == startNo ) { 626 nSumDuration += StringUtil.parseDouble( getValue(nRowUp, posDuration) ); 627 try { 628 if( dFirst == null ) { 629 dFirst = fmtDate.parse(getValue(nRowUp, posDystart)); 630 } 631 } 632 catch( final ParseException ex) { 633 final String errMsg = "DYSTARTに指定した日付形式と異なるデータが存在しています。" 634 + "[" + getValue(nRowUp , posDystart) + "] => [" 635 + fmtDate.toPattern() + "]" ; 636 throw new HybsSystemException( errMsg,ex ); 637 } 638 } 639 else { break; } 640 } 641 642 String thVal = "" ; // <td ・・・> の <td 以下の ・・・部分の属性文字列。 643 String ymdForm = "MM/dd" ; // td タグの BODY 部 の 日付けフォーマット 644 645 if( headerFormat != null ) { 646 final String format = headerFormat.getItdBody().trim() ; 647 final Matcher matcher = TDTH_PTN.matcher(format); 648 if( matcher.find() ) { 649 thVal = matcher.group(2); 650 ymdForm = matcher.group(3); 651 } 652 } 653 654 try { 655 fmtDate.applyPattern(ymdForm); 656 } 657 catch( final IllegalArgumentException ex ) { 658 final String errMsg = "theadの内のitdの内側に指定された日付の形式が不正です。(" + ymdForm + ")"; 659 throw new HybsSystemException( errMsg,ex ); 660 } 661 662 final int colspan = (int)Math.round( headerDuration / minDuration ); 663 final String th ; 664 if( colspan == 1 ) { th = "<th " + thVal + ">"; } 665 else { th = "<th colspan=\"" + colspan + "\" " + thVal + ">" ; } 666 667 final Calendar cal = Calendar.getInstance() ; 668 cal.setTime( dFirst ); 669 670 maxDayCnt = (int)Math.round(nSumDuration / headerDuration) ; // 3.5.5.9 (2004/06/07) 671 int addDate ; 672 int field ; 673 if( headerDuration >= 1.0d ) { 674 addDate = (int)Math.round( headerDuration ); 675 field = Calendar.DATE ; 676 } 677 else { 678 addDate = (int)Math.round( 24.0d * headerDuration ); 679 field = Calendar.HOUR_OF_DAY ; 680 } 681 682 // 端数を指定すると、積算誤差がでます。 683 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 684 685 for( int nIndex=0; nIndex<maxDayCnt; nIndex++ ) { 686 buf.append( th ) 687 .append( fmtDate.format( cal.getTime() ) ) 688 .append( "</th>" ); 689 cal.add( field ,addDate ); 690 } 691 692 return buf.toString(); 693 } 694 695 /** 696 * ガントチャートヘッダー繰り返し部を、getTableHead()メソッドから分離。 697 * このメソッドは、durationColumn が指定されていない場合の処理を行います。 698 * データは、すべての行に関して、同じ日付けのデータとして扱われます。 699 * よって、行間で、日付け違いの並び順になっているとずれることがあります。 700 * ヘッダーは、最初の行の日付けをそのまま表示させます。よって、データと 701 * 日付けが同期されていれば、不連続な日付けのヘッダーを表示させることも可能です。 702 * ヘッダーの期間(headerDuration)とデータの最小期間(minDuration)は、 703 * ともに、'1' であることが前提です。 704 * useSeqDay 属性に、"true" を設定すると、開始日(startDay)と終了日(endDay) 705 * の日付けを連続した日付けとします。開始日(startDay)や終了日(endDay)が指定 706 * されていない場合は、dystartColumn 属性で指定されたカラムの値の最大値、最小値を 707 * 自動セットします。 708 * 709 * @og.rev 3.5.5.9 (2004/06/07) 新規作成 710 * @og.rev 3.5.6.0 (2004/06/18) ithFormat 変数削除 711 * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加 712 * 713 * @param startNo 開始行番号 714 * @param lastNo 最終行番号 715 * 716 * @return テーブルのタグ文字列 717 * @og.rtnNotNull 718 */ 719 private String getGanttHeadNoDuration(final int startNo, final int lastNo) { 720 String[] astrOldGroupKeys = new String[groupCols.length]; 721 for( int nIndex=0; nIndex<astrOldGroupKeys.length; nIndex++ ) { 722 astrOldGroupKeys[nIndex] = ""; 723 } 724 725 String thVal = "" ; // <td ・・・> の <td 以下の ・・・部分の属性文字列。 726 String ymdForm = "MM/dd" ; // td タグの BODY 部 の 日付けフォーマット 727 728 if( headerFormat != null ) { 729 final String format = headerFormat.getItdBody().trim() ; 730 final Matcher matcher = TDTH_PTN.matcher(format); 731 if( matcher.find() ) { 732 thVal = matcher.group(2); 733 ymdForm = matcher.group(3); 734 } 735 } 736 737 // 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更 738 final Locale local = new Locale( headerLocale ); 739 final SimpleDateFormat dataFmt = new SimpleDateFormat( formatDystart,local ); 740 741 final TreeSet<String> daySet = new TreeSet<>(); 742 for( int nRowUp=startNo; nRowUp<lastNo; nRowUp++ ) { 743 // ガントでは、データのスキップは行いません。 744 745 final String day = getValue(nRowUp, posDystart); 746 daySet.add( day ); 747 } 748 // 3.6.1.0 (2005/01/05) 749 if( useSeqDay ) { 750 if( startDay == null ) { startDay = daySet.first() ; } 751 if( endDay == null ) { endDay = daySet.last() ; } 752 753 try { 754 final Calendar startCal = Calendar.getInstance() ; 755 final Date dStart = dataFmt.parse( startDay ); 756 startCal.setTime( dStart ); 757 758 final Calendar endCal = Calendar.getInstance() ; 759 final Date dEnd = dataFmt.parse( endDay ); 760 endCal.setTime( dEnd ); 761 endCal.set( Calendar.HOUR_OF_DAY,12 ); // 日付け比較する為、12時間進めておく。 762 763 while( startCal.before( endCal ) ) { 764 daySet.add( dataFmt.format( startCal.getTime() ) ); 765 startCal.add( Calendar.DATE ,1 ); 766 } 767 } 768 catch( final ParseException ex) { 769 final String errMsg = "startDay,endDayに指定した日付形式と異なるデータが存在しています。" 770 + "[" + startDay + "],[" 771 + "[" + endDay + "] => [" 772 + dataFmt.toPattern() + "]" ; 773 throw new HybsSystemException( errMsg,ex ); 774 } 775 } 776 777 headDays = daySet.toArray( new String[daySet.size()] ); // 4.0.0 (2005/01/31) 778 779 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 780 final String th = "<th " + thVal + ">"; // 6.1.0.0 (2014/12/26) ソースの統一のため、一旦 781 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point. 782 final SimpleDateFormat headFmt = new SimpleDateFormat( ymdForm,Locale.JAPAN ); 783 final Calendar cal = Calendar.getInstance() ; 784 // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop 785 for( final String hDay : headDays ) { 786// for( int i=0; i<headDays.length; i++ ) { 787 try { 788// cal.setTime( dataFmt.parse(headDays[i]) ); 789 cal.setTime( dataFmt.parse(hDay) ); 790 buf.append( th ) 791 .append( headFmt.format( cal.getTime() ) ) 792 .append( "</th>" ); 793 } 794 catch( final ParseException ex) { 795 final String errMsg = "DYSTARTに指定した日付形式と異なるデータが存在しています。" 796// + "[" + headDays[i] + "] => [" 797 + "[" + hDay + "] => [" 798 + dataFmt.toPattern() + "]" ; 799 throw new HybsSystemException( errMsg,ex ); 800 } 801 } 802 803 return buf.toString(); 804 } 805 806 /** 807 * itaタグの中身を形式化する。 808 * 809 * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応 810 * @og.rev 3.5.5.9 (2004/06/07) durationColumn を指定しない場合の処理を追加 811 * @og.rev 3.5.6.0 (2004/06/18) itdタグの[$xx] , [#xx]対応 812 * @og.rev 3.5.6.0 (2004/06/18) '!' 値のみ 追加 既存の '$' は、レンデラー 813 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 814 * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。 815 * 816 * @param nTblRow テーブルモデルの行番号 817 * @param myIteFormat TableFormatteオブジェクト 818 * @param strBuf 出力データバーファ(not null , 同じオブジェクトを return する) 819 * 820 */ 821 private void formatItd( final int nTblRow, final TableFormatter myIteFormat, final StringBuilder strBuf ) { 822 if( myIteFormat == null ) { strBuf.setLength(0); } 823 824 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 825 final int colspan ; 826 if( posDuration >= 0 ) { 827 // 3.7.0.0 (2005/01/18) 小数点の桁落ち対策 828 colspan = (int)Math.round( StringUtil.parseDouble( getValue(nTblRow, posDuration) ) / minDuration ); 829 } 830 else { // 日付けヘッダー未満は、空タグを出力しておく 831 final String today = getValue(nTblRow, posDystart); 832 int comp = headDays[headDaysCnt].compareTo( today ); 833 headDaysCnt++ ; 834 while( comp < 0 && headDaysCnt < headDays.length ) { 835 strBuf.append( "<td></td>" ); 836 comp = headDays[headDaysCnt].compareTo( today ); 837 headDaysCnt++ ; 838 } 839 if( comp != 0 ) { // 見つからなかった(先に日付けが無くなった) 840 final String errMsg = "日付けヘッダーと日付けデータに矛盾が発生しています。" 841 + CR 842 + "groupColumns で日付けがユニークになっていない可能性があります。" 843 + "row=[" + (nTblRow +1) + "] , today=[" + today + "]" ; 844 throw new HybsSystemException( errMsg ); 845 } 846 colspan = 1; // 6.3.9.1 (2015/11/27) 847 } 848 849 int cl = 0; 850 for( ; cl<myIteFormat.getLocationSize(); cl++ ) { 851 String fmt = myIteFormat.getFormat(cl) ; 852 final int loc = myIteFormat.getLocation(cl); // 3.5.6.0 853 if( cl == 0 && colspan != 1 ) { 854 fmt = StringUtil.replace( fmt , "<td", "<td colspan=\"" + colspan + "\"" ); 855 } 856 strBuf.append( fmt ); // 3.5.6.0 857 if( loc >= 0 ) { 858 // 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。 859 strBuf.append( getTypeCaseValue( myIteFormat.getType(cl),nTblRow,loc ) ); 860 } 861 else { 862 strBuf.append( myIteFormat.getSystemFormat(nTblRow,loc) ); 863 } 864 } 865 strBuf.append( myIteFormat.getFormat(cl) ); 866 } 867 868 /** 869 * フォーマットを設定します。 870 * 871 * @og.rev 3.5.4.0 (2003/11/25) 新規作成 872 * @og.rev 3.5.4.4 (2004/01/16) 配列の最大数を変更 873 * @og.rev 3.5.6.0 (2004/06/18) ithFormat , itdFormat 変数削除 874 * 875 * @param list TableFormatterのリスト 876 */ 877 @Override 878 public void setFormatterList( final List<TableFormatter> list ) { // 4.3.3.6 (2008/11/15) Generics警告対応 879 bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT]; 880 881 bodyFormatsCount = 0; 882 // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop 883 for( final TableFormatter format : list ) { 884// for( int i=0; i<list.size(); i++ ) { 885// final TableFormatter format = list.get( i ); // 4.3.3.6 (2008/11/15) Generics警告対応 886 887 switch( format.getFormatType() ) { 888 case TYPE_HEAD : headerFormat = format; break; 889 case TYPE_BODY : bodyFormats[bodyFormatsCount++] = format; break; 890 case TYPE_FOOT : footerFormat = format; break; 891 default : final String errMsg = "FormatterType の定義外の値が指定されました。"; 892 // 4.3.4.4 (2009/01/01) 893 throw new HybsSystemException( errMsg ); 894 } 895 // 3.5.6.0 (2004/06/18) 廃止 896 } 897 898 // 3.5.6.0 (2004/06/18) itdFormats 処理追加 899 // tbody 配列分だけ設定しておきます。 900 itdFormats = new TableFormatter[bodyFormatsCount]; 901 for( int i=0; i<bodyFormatsCount; i++ ) { 902 final String format = bodyFormats[i].getItdBody().trim(); 903 itdFormats[i] = new TableFormatter(); 904 itdFormats[i].setFormat( format ); 905 } 906 } 907 908 /** 909 * フォーマットメソッドを使用できるかどうかを問い合わせます。 910 * 911 * @return 使用可能(true)/ 使用不可能 (false) 912 */ 913 @Override 914 public boolean canUseFormat() { 915 return true; 916 } 917 918 /** 919 * 表示項目の編集(並び替え)が可能かどうかを返します。 920 * 921 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 922 * 923 * @return 表示項目の編集(並び替え)が可能かどうか(false:不可能) 924 */ 925 @Override 926 public boolean isEditable() { 927 return false; 928 } 929}