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.hayabusa.report2; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Map; // 8.0.3.0 (2021/12/17) 021import java.util.HashMap; // 8.0.3.0 (2021/12/17) 022 023import org.opengion.hayabusa.common.HybsSystemException; 024import static org.opengion.fukurou.system.HybsConst.CR ; // 8.0.3.0 (2021/12/17) 025// import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 8.0.3.0 (2021/12/17) 026 027import org.opengion.hayabusa.report2.TagParser.SplitKey; 028 029/** 030 * シート単位のcontent.xmlを管理するためのクラスです。 031 * シートのヘッダー、行の配列、フッター及びシート名を管理します。 032 * 033 * 7.0.1.5 (2018/12/10) 034 * FORMAT_LINEでは、同一行をコピーするため、セルの選択行(A5とか$C7とか)までコピーされ 035 * 行レベルの計算が出来ません。その場合は、INDIRECT(ADDRESS(ROW();列番号))関数を 036 * 使用することでセルのアドレスが指定可能です。 037 * 列番号は、A=1 です。 038 * ※ OpenOffice系は、区切り文字が『;』 EXCELの場合は、『,』 要注意 039 * 040 * ※ 繰り返しを使用する場合で、ヘッダー部分の印刷領域を繰り返したい場合は、 041 * 1.「書式(O)」-「印刷範囲(N)」-「編集(E)」で「印刷範囲の編集」ダイアログを表示 042 * 2.「繰り返す行」の右の方にある矢印のついたボタンをクリック 043 * 3.ページごとに表示したい行をドラックして指定する 044 * (テキストボックスに$1:$2などと直接入力しても良い->$1:$2の場合であれば1-2行目が繰り返し印刷される) 045 * 046 * 8.0.3.0 (2021/12/17) 047 * {@FORMATLINE} を指定した行は、BODY(GE51)行のフォーマットを指定できます。 048 * {@FORMATLINE_1}で、GE51のKBTEXT=B1 で指定した行をひな形にします。 049 * {@FORMATLINE_2}で、GE51のKBTEXT=B2 です。 050 * 引数の数字を指定しない場合は、KBTEXT=B です。 051 * {@DUMMYLINE} は、先のフォーマット行をその行と交換して出力します。 052 * ただし、データが存在しない場合は、このDUMMYLINEそのものが使用されます。 053 * {@COPYLINE} は、先のフォーマット行をデータの数だけ繰り返しその場にコピーします。 054 * イメージ的には、DUMMYLINE は、1ページ分のフォーマットを指定する場合、COPYLINE は 055 * 無制限の連続帳票を想定しています。 056 * 057 * @og.group 帳票システム 058 * 059 * @version 4.0 060 * @author Hiroki.Nakamura 061 * @since JDK1.6 062 */ 063class OdsSheet { 064 065 //======== content.xmlのパースで使用 ======================================== 066 067 /* 行の開始終了タグ */ 068 private static final String ROW_START_TAG = "<table:table-row "; 069 private static final String ROW_END_TAG = "</table:table-row>"; 070 071 /* シート名を取得するための開始終了文字 */ 072 private static final String SHEET_NAME_START = "table:name=\""; 073// private static final String SHEET_NAME_END = "\""; 074 private static final String END_KEY = "\""; // 8.0.3.0 (2021/12/17) 075 076 /* 変数定義の開始終了文字及び区切り文字 */ 077 private static final String VAR_START = "{@"; 078 private static final String VAR_END = "}"; 079// private static final String VAR_CON = "_"; 080 081 /* フォーマットライン文字列 5.0.0.2 (2009/09/15) */ 082 private static final String FORMAT_LINE = "FORMATLINE"; // 8.0.3.0 (2021/12/17) 083 084 /* ダミーライン文字列 8.0.3.0 (2021/12/17) */ 085 private static final String DUMMY_LINE = "DUMMYLINE"; // 8.0.3.0 (2021/12/17) 086 087 /* コピーライン文字列 8.0.3.0 (2021/12/17) */ 088 private static final String COPY_LINE = "COPYLINE"; // 8.0.3.0 (2021/12/17) 089 090 private final List<String> sheetRows = new ArrayList<>(); 091 private final Map<String,String> rowsMap = new HashMap<>(); 092 private int offsetCnt = -1; // 8.0.3.0 (2021/12/17) FORMAT_LINE が最初に現れた番号 093 private String[] bodyTypes ; // 8.0.3.0 (2021/12/17) 行番号に対応した、ボディタイプ(KBTEXT)配列 094 095 private String sheetHeader; 096 private String sheetFooter; 097 private String sheetName; 098 private String origSheetName; 099 private String confSheetName; 100 101 /** 102 * デフォルトコンストラクター 103 * 104 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 105 */ 106 public OdsSheet() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 107 108 /** 109 * シートを行単位に分解します。 110 * 111 * @og.rev 5.0.0.2 (2009/09/15) ボディ部のカウントを引数に追加し、LINECOPY機能実装。 112 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 113 * @og.rev 8.0.3.0 (2021/12/17) COPY_LINE機能の追加 114 * 115 * @param sheet シート名 116 * @param bodyTypes 行番号に対応した、ボディタイプ(KBTEXT)配列 117 */ 118// public void analyze( final String sheet, final int bodyRowCount ) { 119 public void analyze( final String sheet, final String[] bodyTypes ) { 120 this.bodyTypes = bodyTypes ; 121 122 final String[] tags = TagParser.tag2Array( sheet, ROW_START_TAG, ROW_END_TAG ); 123 sheetHeader = tags[0]; 124 sheetFooter = tags[1]; 125 for( int i=2; i<tags.length; i++ ) { 126// sheetRows.add( tags[i] ); 127// lineCopy( tags[i], bodyRowCount ); // 5.0.0.2 (2009/09/15) 128 lineCopy( tags[i] ); // 8.0.3.0 (2021/12/17) 129 } 130 131// sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, SHEET_NAME_END ); 132 sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, END_KEY ); // 8.0.3.0 (2021/12/17) 133 origSheetName = sheetName; 134 135 confSheetName = null; 136 if( sheetName != null ) { 137 final int cnfIdx = sheetName.indexOf( "__" ); 138 if( cnfIdx > 0 && !sheetName.endsWith( "__" ) ) { 139 confSheetName = sheetName.substring( cnfIdx + 2 ); 140 sheetName = sheetName.substring( 0, cnfIdx ); 141 } 142 } 143 } 144 145 /** 146 * ラインコピーに関する処理を行います。 147 * 148 * {@LINECOPY}が存在した場合に、テーブルモデル分だけ 149 * 行をコピーします。 150 * その際、{@XXX_番号}の番号をカウントアップしてコピーします。 151 * 152 * 整合性等のエラーハンドリングはこのメソッドでは行わず、 153 * 実際のパース処理中で行います。 154 * 155 * 7.0.1.5 (2018/12/10) 156 * LINECOPYでは、同一行をコピーするため、セルの選択行(A5とか$C7とか)までコピーされ 157 * 行レベルの計算が出来ません。その場合は、INDIRECT(ADDRESS(ROW(),列番号))関数を 158 * 使用することでセルのアドレスが指定可能です。 159 * 列番号は、A=1 です。 160 * 161 * @og.rev 5.0.0.2 (2009/09/15) 追加 162 * @og.rev 5.1.8.0 (2010/07/01) パース処理の内部実装を変更 163 * @og.rev 7.0.1.5 (2018/12/10) LINECOPYでの注意(JavaDocのみ追記) 164 * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します。 165 * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 166 * 167 * @param rowStr 行データ 168 */ 169// private void lineCopy( final String rowStr, final int rowCount ) { 170 private void lineCopy( final String rowStr ) { 171 // FORMAT_LINE を見つけて、引数をキーにマップに登録します。 172 final String cpLin = TagParser.splitSufix( rowStr,FORMAT_LINE ); 173 if( cpLin != null ) { 174 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 175 final String tmp = rowsMap.get( "B" + cpLin ); 176 if( tmp == null ) { 177 rowsMap.put( "B" + cpLin , rowStr ); // フォーマットのキーは、"B" + サフィックス 178 // sheetRows.add( rowStr ); // 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 179 } 180 else { 181 // セル結合時に、複数行を1行に再設定する。 182 rowsMap.put( "B" + cpLin , tmp + rowStr ); // フォーマットのキーは、"B" + サフィックス 183 } 184 return; 185 } 186 187 // DUMMY_LINE を見つける。 188 final int st1 = rowStr.indexOf( VAR_START + DUMMY_LINE ); 189 if( st1 >= 0 ) { // キーが見つかった場合 190 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 191 sheetRows.add( rowStr ); // DUMMY_LINE を登録 192 return ; 193 } 194 195 // COPY_LINE を見つける。 196 final int st2 = rowStr.indexOf( VAR_START + COPY_LINE ); 197 if( st2 >= 0 ) { // キーが見つかった場合 198 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 199 200 // COPY_LINEは、その場に全件コピーします(行数を確保するため) 201 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach 202// for( int row=0; row<bodyTypes.length; row++ ) { 203// sheetRows.add( rowStr ); // COPY_LINE を登録 204// } 205 // 8.5.5.1 (2024/02/29) spotbugs UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR (bodyTypes が null の場合を考慮) 206 if( bodyTypes != null ) { 207 final int cnt = bodyTypes.length; // 単にカウント数だけ add するので、拡張 for にする必要がない。 208 for( int i=0; i<cnt; i++ ) { 209 sheetRows.add( rowStr ); // COPY_LINE を登録 210 } 211 } 212 return ; 213 } 214 215 sheetRows.add( rowStr ); // rowStr を登録(通常行) 216 217// // この段階で存在しなければ即終了 218// final int lcStr = row.indexOf( VAR_START + LINE_COPY ); 219//// if( lcStrOffset < 0 ) { return; } 220// if( lcStr < 0 ) { sheetRows.add( row ); return 1; } 221// final int lcEnd = row.indexOf( VAR_END, lcStr ); 222//// if( lcEndOffset < 0 ) { return; } 223// if( lcEnd < 0 ) { sheetRows.add( row ); return 1; } 224// 225// final StringBuilder lcStrBuf = new StringBuilder( row ); 226// final String lcKey = TagParser.checkKey( row.substring( lcStr + VAR_START.length(), lcEnd ), lcStrBuf ); 227//// if( lcKey == null || !LINE_COPY.equals( lcKey ) ) { return; } 228// final SplitKey cpKey = new SplitKey( lcKey ); // 8.0.3.0 (2021/12/17) 229// final int copyCnt = cpKey.count( rowCount ); 230 231// // 存在すればテーブルモデル行数-1回ループ(自身を除くため) 232// for( int i=1; i<rowCount; i++ ) { 233// // 存在すればテーブルモデル行数回ループ(自身も含める必要がある) 234// for( int i=0; i<copyCnt; i++ ) { // {@LINECOPY_回数} で、繰り返し回数指定 235// final int cRow = i; // final 宣言しないと無名クラスに設定できない。 236// final String rowStr = new TagParser() { 237// /** 238// * 開始タグから終了タグまでの文字列の処理を定義します。 239// * 240// * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 241// * @param buf 出力を行う文字列バッファ 242// * @param offset 終了タグのオフセット(ここでは使っていません) 243// */ 244// @Override 245// protected void exec( final String str, final StringBuilder buf, final int offset ) { 246// String key = TagParser.checkKey( str, buf ); 247// if( key.indexOf( '<' ) >= 0 ){ 248// final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です。" + CR 249// + "変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key; 250// throw new HybsSystemException( errMsg ); 251// } 252// final SplitKey spKey = new SplitKey( key ); // 8.0.3.0 (2021/12/17) 253//// buf.append( VAR_START ).append( incrementKey( key, cRow ) ).append( VAR_END ); 254// buf.append( VAR_START ).append( spKey.incrementKey( cRow ) ).append( VAR_END ); 255// } 256// }.doParse( lcStrBuf.toString(), VAR_START, VAR_END, false ); 257// 258// sheetRows.add( rowStr ); 259// } 260// return copyCnt; 261 } 262 263// /** 264// * XXX_番号の番号部分を引数分追加して返します。 265// * 番号部分が数字でない場合や、_が無い場合はそのまま返します。 266// * 267// * @og.rev 5.0.0.2 (2009/09/15) LINE_COPYで利用するために追加 268// * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します。 269// * 270// * @param key キー文字列 271// * @param inc カウンタ部 272// * 273// * @return 変更後キー 274// */ 275// private String incrementKey( final String key, final int inc ) { 276// final int conOffset = key.lastIndexOf( VAR_CON ); 277// if( conOffset < 0 ) { return key; } 278// 279// final String name = key.substring( 0, conOffset ); 280// int rownum = -1; 281// try { 282// rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) ); // 6.0.2.4 (2014/10/17) メソッド間違い 283// } 284// // エラーが起きてもなにもしない。 285// catch( final NumberFormatException ex ) { 286// // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks 287// final String errMsg = "Not incrementKey. KEY=[" + key + "] " + ex.getMessage() ; 288// System.err.println( errMsg ); 289// } 290// 291// // アンダースコア後が数字に変換できない場合はヘッダフッタとして認識 292// if( rownum < 0 ){ return key; } 293// else { return name + VAR_CON + (rownum + inc) ; } 294// } 295 296 /** 297 * シートのヘッダー部分を返します。 298 * 299 * @return ヘッダー 300 */ 301 public String getHeader() { 302 return sheetHeader; 303 } 304 305 /** 306 * シートのフッター部分を返します。 307 * 308 * @return フッター 309 */ 310 public String getFooter() { 311 return sheetFooter; 312 } 313 314 /** 315 * シート名称を返します。 316 * 317 * @return シート名称 318 */ 319 public String getSheetName() { 320 return sheetName; 321 } 322 323 /** 324 * 定義済シート名称を返します。 325 * 326 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 327 * 328 * @return 定義済シート名称 329 */ 330 public String getConfSheetName() { 331 return confSheetName; 332 } 333 334 /** 335 * 定義名変換前のシート名称を返します。 336 * 337 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 338 * 339 * @return 定義済シート名称 340 */ 341 public String getOrigSheetName() { 342 return origSheetName; 343 } 344 345// /** 346// * シートの各行を配列で返します。 347// * 348// * @og.rev 4.3.1.1 (2008/08/23) あらかじめ、必要な配列の長さを確保しておきます。 349// * @og.rev 8.0.3.0 (2021/12/17) 廃止 350// * 351// * @return シートの各行の配列 352// * @og.rtnNotNull 353// */ 354// public String[] getRows() { 355// return sheetRows.toArray( new String[sheetRows.size()] ); 356// } 357 358 /** 359 * シートの行を返します。 360 * 361 * @og.rev 8.0.3.0 (2021/12/17) 新規追加 362 * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 363 * @og.rev 8.4.0.0 (2022/11/11) DUMMYLINE改善(複数のフォーマットについて複数行に跨って縦線がマチマチあるパターン)(問合・トラブル 43100-221109-01) 364 * 365 * @param idx シート内での行番号 366 * @param baseRow TableModelのベース行番号 367 * 368 * @return シートの行 369 * @og.rtnNotNull 370 */ 371 public String getRow( final int idx, final int baseRow ) { 372 final String rowStr = sheetRows.get( idx ); 373 374// 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 375// final boolean useFmt = rowStr.contains( VAR_START + FORMAT_LINE ) 376// || rowStr.contains( VAR_START + DUMMY_LINE ) 377// || rowStr.contains( VAR_START + COPY_LINE ) ; 378 379 final boolean useFmtD = rowStr.contains( VAR_START + DUMMY_LINE ); 380 final boolean useFmtC = rowStr.contains( VAR_START + COPY_LINE ) ; 381 382 if( useFmtD || useFmtC ) { // キーが見つかった場合 383 final int row = idx-offsetCnt+baseRow; 384 385// final String dummy = row < bodyTypes.length // 配列overチェック 386// ? rowsMap.getOrDefault( bodyTypes[row],rowStr ) // 存在しなかった場合の処置 387// : rowStr ; 388 // 8.4.0.0 (2022/11/11) DUMMYLINE改善 389 final String dummy; 390 // FORMATLINEが1種類のDUMMYLINE使用 391 if( useFmtD && rowsMap.size() == 1 ) { 392 dummy = rowsMap.get( "B" ) ; 393 } 394 // FORMATLINEが複数のDUMMYLINE使用 or COPYLINE使用 395 else { 396 // 8.5.5.1 (2024/02/29) spotbugs UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR (bodyTypes が null の場合を考慮) 397// dummy = row < bodyTypes.length // 配列overチェック 398 dummy = bodyTypes != null && row < bodyTypes.length // 配列overチェック 399 ? rowsMap.getOrDefault( bodyTypes[row],rowStr ) // 存在しなかった場合の処置 400 : rowStr ; 401 } 402 403 return new TagParser() { 404 /** 405 * 開始タグから終了タグまでの文字列の処理を定義します。 406 * 407 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 408 * @param buf 出力を行う文字列バッファ 409 * @param offset 終了タグのオフセット(ここでは使っていません) 410 */ 411 @Override 412 protected void exec( final String str, final StringBuilder buf, final int offset ) { 413 final String key = TagParser.checkKey( str, buf ); 414 if( key.indexOf( '<' ) >= 0 ){ 415 final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です。" + CR 416 + "変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key; 417 throw new HybsSystemException( errMsg ); 418 } 419 final SplitKey spKey = new SplitKey( key ); // 8.0.3.0 (2021/12/17) 420 buf.append( VAR_START ).append( spKey.incrementKey( idx-offsetCnt ) ).append( VAR_END ); 421 } 422 }.doParse( dummy, VAR_START, VAR_END, false ); 423 } 424 return rowStr; 425 } 426 427 /** 428 * シートに含まれている行数を返します。 429 * 430 * @og.rev 8.0.3.0 (2021/12/17) 新規追加 431 * 432 * @return シートに含まれている行数 433 */ 434 public int getRowCnt() { 435 return sheetRows.size(); 436 } 437}