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