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 * @param rowCount カウンタ 169 */ 170// private void lineCopy( final String rowStr, final int rowCount ) { 171 private void lineCopy( final String rowStr ) { 172 // FORMAT_LINE を見つけて、引数をキーにマップに登録します。 173 final String cpLin = TagParser.splitSufix( rowStr,FORMAT_LINE ); 174 if( cpLin != null ) { 175 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 176 final String tmp = rowsMap.get( "B" + cpLin ); 177 if( tmp == null ) { 178 rowsMap.put( "B" + cpLin , rowStr ); // フォーマットのキーは、"B" + サフィックス 179 // sheetRows.add( rowStr ); // 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 180 } 181 else { 182 // セル結合時に、複数行を1行に再設定する。 183 rowsMap.put( "B" + cpLin , tmp + rowStr ); // フォーマットのキーは、"B" + サフィックス 184 } 185 return; 186 } 187 188 // DUMMY_LINE を見つける。 189 final int st1 = rowStr.indexOf( VAR_START + DUMMY_LINE ); 190 if( st1 >= 0 ) { // キーが見つかった場合 191 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 192 sheetRows.add( rowStr ); // DUMMY_LINE を登録 193 return ; 194 } 195 196 // COPY_LINE を見つける。 197 final int st2 = rowStr.indexOf( VAR_START + COPY_LINE ); 198 if( st2 >= 0 ) { // キーが見つかった場合 199 if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); } // 初めてあらわれた位置 200 201 // COPY_LINEは、その場に全件コピーします(行数を確保するため) 202 for( int row=0; row<bodyTypes.length; row++ ) { 203 sheetRows.add( rowStr ); // COPY_LINE を登録 204 } 205 return ; 206 } 207 208 sheetRows.add( rowStr ); // rowStr を登録(通常行) 209 210// // この段階で存在しなければ即終了 211// final int lcStr = row.indexOf( VAR_START + LINE_COPY ); 212//// if( lcStrOffset < 0 ) { return; } 213// if( lcStr < 0 ) { sheetRows.add( row ); return 1; } 214// final int lcEnd = row.indexOf( VAR_END, lcStr ); 215//// if( lcEndOffset < 0 ) { return; } 216// if( lcEnd < 0 ) { sheetRows.add( row ); return 1; } 217// 218// final StringBuilder lcStrBuf = new StringBuilder( row ); 219// final String lcKey = TagParser.checkKey( row.substring( lcStr + VAR_START.length(), lcEnd ), lcStrBuf ); 220//// if( lcKey == null || !LINE_COPY.equals( lcKey ) ) { return; } 221// final SplitKey cpKey = new SplitKey( lcKey ); // 8.0.3.0 (2021/12/17) 222// final int copyCnt = cpKey.count( rowCount ); 223 224// // 存在すればテーブルモデル行数-1回ループ(自身を除くため) 225// for( int i=1; i<rowCount; i++ ) { 226// // 存在すればテーブルモデル行数回ループ(自身も含める必要がある) 227// for( int i=0; i<copyCnt; i++ ) { // {@LINECOPY_回数} で、繰り返し回数指定 228// final int cRow = i; // final 宣言しないと無名クラスに設定できない。 229// final String rowStr = new TagParser() { 230// /** 231// * 開始タグから終了タグまでの文字列の処理を定義します。 232// * 233// * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 234// * @param buf 出力を行う文字列バッファ 235// * @param offset 終了タグのオフセット(ここでは使っていません) 236// */ 237// @Override 238// protected void exec( final String str, final StringBuilder buf, final int offset ) { 239// String key = TagParser.checkKey( str, buf ); 240// if( key.indexOf( '<' ) >= 0 ){ 241// final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です。" + CR 242// + "変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key; 243// throw new HybsSystemException( errMsg ); 244// } 245// final SplitKey spKey = new SplitKey( key ); // 8.0.3.0 (2021/12/17) 246//// buf.append( VAR_START ).append( incrementKey( key, cRow ) ).append( VAR_END ); 247// buf.append( VAR_START ).append( spKey.incrementKey( cRow ) ).append( VAR_END ); 248// } 249// }.doParse( lcStrBuf.toString(), VAR_START, VAR_END, false ); 250// 251// sheetRows.add( rowStr ); 252// } 253// return copyCnt; 254 } 255 256// /** 257// * XXX_番号の番号部分を引数分追加して返します。 258// * 番号部分が数字でない場合や、_が無い場合はそのまま返します。 259// * 260// * @og.rev 5.0.0.2 LINE_COPYで利用するために追加 261// * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します。 262// * 263// * @param key キー文字列 264// * @param inc カウンタ部 265// * 266// * @return 変更後キー 267// */ 268// private String incrementKey( final String key, final int inc ) { 269// final int conOffset = key.lastIndexOf( VAR_CON ); 270// if( conOffset < 0 ) { return key; } 271// 272// final String name = key.substring( 0, conOffset ); 273// int rownum = -1; 274// try { 275// rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) ); // 6.0.2.4 (2014/10/17) メソッド間違い 276// } 277// // エラーが起きてもなにもしない。 278// catch( final NumberFormatException ex ) { 279// // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks 280// final String errMsg = "Not incrementKey. KEY=[" + key + "] " + ex.getMessage() ; 281// System.err.println( errMsg ); 282// } 283// 284// // アンダースコア後が数字に変換できない場合はヘッダフッタとして認識 285// if( rownum < 0 ){ return key; } 286// else { return name + VAR_CON + (rownum + inc) ; } 287// } 288 289 /** 290 * シートのヘッダー部分を返します。 291 * 292 * @return ヘッダー 293 */ 294 public String getHeader() { 295 return sheetHeader; 296 } 297 298 /** 299 * シートのフッター部分を返します。 300 * 301 * @return フッター 302 */ 303 public String getFooter() { 304 return sheetFooter; 305 } 306 307 /** 308 * シート名称を返します。 309 * 310 * @return シート名称 311 */ 312 public String getSheetName() { 313 return sheetName; 314 } 315 316 /** 317 * 定義済シート名称を返します。 318 * 319 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 320 * 321 * @return 定義済シート名称 322 */ 323 public String getConfSheetName() { 324 return confSheetName; 325 } 326 327 /** 328 * 定義名変換前のシート名称を返します。 329 * 330 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 331 * 332 * @return 定義済シート名称 333 */ 334 public String getOrigSheetName() { 335 return origSheetName; 336 } 337 338// /** 339// * シートの各行を配列で返します。 340// * 341// * @og.rev 4.3.1.1 (2008/08/23) あらかじめ、必要な配列の長さを確保しておきます。 342// * @og.rev 8.0.3.0 (2021/12/17) 廃止 343// * 344// * @return シートの各行の配列 345// * @og.rtnNotNull 346// */ 347// public String[] getRows() { 348// return sheetRows.toArray( new String[sheetRows.size()] ); 349// } 350 351 /** 352 * シートの行を返します。 353 * 354 * @og.rev 8.0.3.0 (2021/12/17) 新規追加 355 * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 356 * @og.rev 8.4.0.0 (2022/11/11) DUMMYLINE改善(複数のフォーマットについて複数行に跨って縦線がマチマチあるパターン)(問合・トラブル 43100-221109-01) 357 * 358 * @param idx シート内での行番号 359 * @param baseRow TableModelのベース行番号 360 * 361 * @return シートの行 362 * @og.rtnNotNull 363 */ 364 public String getRow( final int idx, final int baseRow ) { 365 final String rowStr = sheetRows.get( idx ); 366 367// 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。 368// final boolean useFmt = rowStr.contains( VAR_START + FORMAT_LINE ) 369// || rowStr.contains( VAR_START + DUMMY_LINE ) 370// || rowStr.contains( VAR_START + COPY_LINE ) ; 371 372 final boolean useFmtD = rowStr.contains( VAR_START + DUMMY_LINE ); 373 final boolean useFmtC = rowStr.contains( VAR_START + COPY_LINE ) ; 374 375 if( useFmtD || useFmtC ) { // キーが見つかった場合 376 final int row = idx-offsetCnt+baseRow; 377 378// final String dummy = row < bodyTypes.length // 配列overチェック 379// ? rowsMap.getOrDefault( bodyTypes[row],rowStr ) // 存在しなかった場合の処置 380// : rowStr ; 381 // 8.4.0.0 (2022/11/11) DUMMYLINE改善 382 final String dummy; 383 // FORMATLINEが1種類のDUMMYLINE使用 384 if( useFmtD && rowsMap.size() == 1 ) { 385 dummy = rowsMap.get( "B" ) ; 386 } 387 // FORMATLINEが複数のDUMMYLINE使用 or COPYLINE使用 388 else { 389 dummy = row < bodyTypes.length // 配列overチェック 390 ? rowsMap.getOrDefault( bodyTypes[row],rowStr ) // 存在しなかった場合の処置 391 : rowStr ; 392 } 393 394 return new TagParser() { 395 /** 396 * 開始タグから終了タグまでの文字列の処理を定義します。 397 * 398 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 399 * @param buf 出力を行う文字列バッファ 400 * @param offset 終了タグのオフセット(ここでは使っていません) 401 */ 402 @Override 403 protected void exec( final String str, final StringBuilder buf, final int offset ) { 404 final String key = TagParser.checkKey( str, buf ); 405 if( key.indexOf( '<' ) >= 0 ){ 406 final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です。" + CR 407 + "変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key; 408 throw new HybsSystemException( errMsg ); 409 } 410 final SplitKey spKey = new SplitKey( key ); // 8.0.3.0 (2021/12/17) 411 buf.append( VAR_START ).append( spKey.incrementKey( idx-offsetCnt ) ).append( VAR_END ); 412 } 413 }.doParse( dummy, VAR_START, VAR_END, false ); 414 } 415 return rowStr; 416 } 417 418 /** 419 * シートに含まれている行数を返します。 420 * 421 * @og.rev 8.0.3.0 (2021/12/17) 新規追加 422 * 423 * @return シートに含まれている行数 424 */ 425 public int getRowCnt() { 426 return sheetRows.size(); 427 } 428}