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