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.io.BufferedReader; 019import java.io.BufferedWriter; 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileNotFoundException; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.OutputStreamWriter; 026import java.io.UnsupportedEncodingException; 027import java.nio.channels.FileChannel; 028import java.nio.charset.CharacterCodingException; // 6.3.1.0 (2015/06/28) 029import java.util.List; 030import java.util.ArrayList; 031import java.util.Map; 032import java.util.HashMap; 033import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 034import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 035import java.util.Locale; 036import java.util.Set; 037 038import org.opengion.fukurou.system.OgCharacterException ; // 6.5.0.1 (2016/10/21) 039import org.opengion.fukurou.model.NativeType; 040import org.opengion.fukurou.util.StringUtil; // 6.2.0.0 (2015/02/27) 041import org.opengion.fukurou.system.Closer; 042import org.opengion.fukurou.util.FileUtil; 043import org.opengion.fukurou.util.QrcodeImage; 044import org.opengion.hayabusa.common.HybsSystem; 045import org.opengion.hayabusa.common.HybsSystemException; 046import org.opengion.hayabusa.db.DBTableModel; // 6.1.1.0 (2015/01/17) 047import static org.opengion.fukurou.system.HybsConst.CR ; // 6.1.0.0 (2014/12/26) 048import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 049 050/** 051 * 指定されたパスに存在するODSの各XMLファイルをパースし、帳票定義及び 052 * 帳票データから書き換えます。 053 * 書き換えは読み取り先と同じファイルであるため、一旦読み取った各XMLを 054 * メモリ上に格納したからパース後のXMLファイルの書き込みを行います。 055 * 056 * パース対象となるファイルは以下の3つです。 057 * content.xml シートの中身を定義 058 * meta.xml メタデータを定義 059 * style.xml 帳票ヘッダーフッターを定義 060 * 061 * content.xmlのパース処理として、まずxmlファイルをシート+行単位に分解します。 062 * その後、分解された行毎に帳票データを埋め込み、出力先のXMLに書き込みを行います。 063 * 書き込みは行単位に行われます。 064 * 065 * また、Calcの特性として、関数の引数に不正な引数が指定された場合、(Text関数の 066 * 引数にnullが指定された場合等)、エラー:XXXという文字が表示されます。 067 * ここでは、これを回避するため、全ての関数にisError関数を埋め込み、エラー表示を 068 * 行わないようにしています。 069 * 070 * @og.group 帳票システム 071 * 072 * @version 4.0 073 * @author Hiroki.Nakamura 074 * @since JDK1.6 075 */ 076class OdsContentParser { 077 078 //======== content.xmlのパースで使用 ======================================== 079 /* シートの開始終了タグ */ 080 private static final String BODY_START_TAG = "<table:table "; 081 private static final String BODY_END_TAG = "</table:table>"; 082 083 /* 行の開始終了タグ */ 084 private static final String ROW_START_TAG = "<table:table-row "; 085 086 /* ページエンドカットの際に、行を非表示にするためのテーブル宣言 */ 087 private static final String ROW_START_TAG_INVISIBLE = "<table:table-row table:visibility=\"collapse\" "; 088 089 /* セルの開始タグ */ 090 private static final String TABLE_CELL_START_TAG = "<table:table-cell"; 091 private static final String TABLE_CELL_END_TAG = "</table:table-cell>"; 092 093 /* シート名を取得するための開始終了文字 */ 094 private static final String SHEET_NAME_START = "table:name=\""; 095 096 /* オブジェクトの終了位置(シート名)を見つけるための開始文字 */ 097 private static final String OBJECT_SEARCH_STR = "table:end-cell-address=\""; 098 099 /* 印刷範囲指定の開始終了文字 */ 100 // 4.3.3.5 (2008/11/08) 空白ページ対策で追加 101 private static final String PRINT_RANGE_START = "table:print-ranges=\""; 102 private static final String PRINT_RANGE_END = "\""; 103 104 /* 表紙印刷用のページ名称 */ 105 private static final String FIRST_PAGE_NAME = "FIRST"; 106 107 /* シートブレイク用のキー 5.1.7.0 (2010/06/01) */ 108 private static final String SHEET_BREAK = "SHEETBREAK"; 109 110 /* 変数定義の開始終了文字及び区切り文字 */ 111 private static final String VAR_START = "{@"; 112 private static final String VAR_END = "}"; 113 private static final String VAR_CON = "_"; 114 115 /* ページエンドカットのカラム文字列 */ 116 private static final String PAGE_END_CUT = "PAGEENDCUT"; 117 118 /* ページブレイクのカラム文字列 */ 119 private static final String PAGE_BREAK = "PAGEBREAK"; 120 121 /* ページ番号出力用文字列 5.1.6.0 (2010/05/01) */ 122 private static final String PAGE_NO= "PAGENO"; 123 124 /* 行番号出力用文字列 5.1.6.0 (2010/05/01) */ 125 private static final String ROW_NO= "ROWNO"; 126 127 /* 画像のリンクを取得するための開始終了文字 */ 128 private static final String DRAW_IMG_START_TAG = "<draw:image xlink:href=\""; 129 private static final String DRAW_IMG_END_TAG = "</draw:image>"; 130 private static final String DRAW_IMG_HREF_END = "\""; 131 132 /* 画像ファイルを保存するためのパス */ 133 private static final String IMG_DIR = "Pictures"; 134 135 /* QRコードを処理するためのカラム名 */ 136 private static final String QRCODE_PREFIX = "QRCODE."; 137 138 /* 作成したQRコードのフォルダ名及び拡張子 */ 139 private static final String QRCODE_FILETYPE = ".png"; 140 141 /* 7.0.5.1 (2019/09/27) QRコードのパラメータをシステムリソースで設定できるようにします(ただし、staticとします) */ 142 private static final int QR_VERSION = HybsSystem.sysInt( "REPORT_QR_VERSION" ); // 7.0.5.1 (2019/09/27) バージョン 143 private static final char QR_ENCMODE_CH = HybsSystem.sys( "REPORT_QR_ENCMODE" ).charAt(0); // 7.0.5.1 (2019/09/27) エンコードモード 144 private static final char QR_ERRCRCT_CH = HybsSystem.sys( "REPORT_QR_ERRCRCT" ).charAt(0); // 7.0.5.1 (2019/09/27) エラー訂正レベル 145 private static final String QR_IMAGE_TYPE = "PNG"; // 7.0.5.1 (2019/09/27) 出力イメージのタイプ(PNG/JPEG) 146 private static final int QR_PIXEL = HybsSystem.sysInt( "REPORT_QR_PIXEL" ); // 7.0.5.1 (2019/09/27) 1セル辺りの塗りつぶしピクセル数 147 private static final QrcodeImage.EncMode QR_ENCMODE = QrcodeImage.EncMode.get( QR_ENCMODE_CH ); // 7.0.5.1 (2019/09/27) 148 private static final QrcodeImage.ErrCrct QR_ERRCRCT = QrcodeImage.ErrCrct.get( QR_ERRCRCT_CH ); // 7.0.5.1 (2019/09/27) 149 private static final String QR_TXT_ENC = HybsSystem.sys( "REPORT_QR_TEXT_ENCODE" ); // 7.2.3.0 (2020/04/10) 帳票出力のQRコード作成時のテキストのエンコード指定 150 151 /* 4.3.3.5 (2008/11/08) 動的に画像を入れ替えるためのパスを記述するカラム名 */ 152 private static final String IMG_PREFIX = "IMG."; 153 154 /* ファンクション定義を見つけるための開始終了文字 */ 155 private static final String OOOC_FUNCTION_START = "oooc:="; 156 private static final String OOOC_FUNCTION_START_3 = "of:="; // 4.3.7.2 (2009/06/15) ODS仕様変更につき追加 157 private static final String OOOC_FUNCTION_END = ")\" "; 158 159 /* セル内の改行を定義する文字列 5.0.2.0 (2009/11/01) */ 160 private static final String OOO_CR = "</text:p><text:p>"; 161 162 /* グラフオブジェクトの書き換えを行うための開始終了文字 5.1.8.0 (2010/07/01) */ 163 private static final String GRAPH_START_TAG = "<draw:frame "; 164 private static final String GRAPH_END_TAG = "</draw:frame>"; 165 /* グラフの範囲指定の書き換えを行うための開始終了文字 5.1.8.0 (2010/07/01) */ 166 private static final String GRAPH_UPDATE_RANGE_START = "draw:notify-on-update-of-ranges=\""; 167 private static final String GRAPH_UPDATE_RANGE_END = "\""; 168 /* グラフのオブジェクトへのリンクの書き換えを行うための開始終了文字 5.1.8.0 (2010/07/01) */ 169 private static final String GRAPH_HREF_START = "xlink:href=\"./"; 170 private static final String GRAPH_HREF_END = "\""; 171 private static final String GRAPH_OBJREPL = "ObjectReplacements"; 172 /* グラフのオブジェクト毎のcontent.xmlに記述してあるシート名の書き換えを行うための開始終了文字 5.1.8.0 (2010/07/01) */ 173 private static final String GRAPH_CONTENT_START = "-address=\""; 174 private static final String GRAPH_CONTENT_END = "\""; 175 /* 生成したグラフのオブジェクトをMETA-INF/manifest.xmlに登録するための開始終了文字列 5.1.8.0 (2010/07/01) */ 176 private static final String MANIFEST_START_TAG = "<manifest:file-entry "; 177 private static final String MANIFEST_END_TAG = "/>"; // XML なので、このまま。 178 179 /* 数値タイプ置き換え用の文字列 5.1.8.0 (2010/07/01) */ 180 private static final String TABLE_CELL_STRING_TYPE = "office:value-type=\"string\""; 181 private static final String TABLE_CELL_FLOAT_TYPE = "office:value-type=\"float\""; 182 private static final String TABLE_CELL_FLOAT_VAL_START = "office:value=\""; 183 private static final String TABLE_CELL_FLOAT_VAL_END = "\""; 184 185 /* テキスト文字列の開始終了文字列 5.1.8.0 (2010/07/01) */ 186 private static final String TEXT_START_TAG = "<text:p>"; 187 private static final String TEXT_END_TAG = "</text:p>"; 188 189 /* コメント(アノテーション)を処理するためのカラム名 5.1.8.0 (2010/07/01) */ 190 private static final String ANNOTATION_PREFIX = "ANO."; 191 private static final String TEXT_START_ANO_TAG = "<text:p"; // アノテーションの場合の置き換えを 192 private static final String TEXT_START_END_ANO_TAG = ">"; // アノテーションの場合の置き換えを 193 194 /* コメント(アノテーション)の開始・終了タグ 5.1.8.0 (2010/07/01) */ 195 private static final String ANNOTATION_START_TAG = "<office:annotation"; 196 private static final String ANNOTATION_END_TAG = "</office:annotation>"; 197 198 /* オブジェクトを検索するための文字列 5.1.8.0 (2010/07/01) */ 199 private static final String DRAW_START_KEY = "<draw:"; 200 private static final String DRAW_END_KEY = "</draw:"; 201 202 /* シートの開始終了タグ 5.2.2.0 (2010/11/01) */ 203 private static final String STYLE_START_TAG = "<style:style "; 204 private static final String STYLE_END_TAG = "</style:style>"; 205 206 /* シート名称 5.2.2.0 (2010/11/01) */ 207 private static final String STYLE_NAME_START_TAG = "style:name=\""; 208 private static final String STYLE_NAME_END_TAG = "\""; 209 210 /* テーブル内シート名称 5.2.2.0 (2010/11/01) */ 211 private static final String TABLE_STYLE_NAME_START_TAG = "table:style-name=\""; 212 private static final String TABLE_STYLE_NAME_END_TAG = "\""; // 5.6.3.1 (2013/04/05) 213 214 //=========================================================================== 215 216 //======== meta.xmlのパースで使用 =========================================== 217 /* 総シートカウント数 */ 218 private static final String TABLE_COUNT_START_TAG = "meta:table-count=\""; 219 private static final String TABLE_COUNT_END_TAG = "\""; 220 221 /* 総セルカウント数 */ 222 private static final String CELL_COUNT_START_TAG = "meta:cell-count=\""; 223 private static final String CELL_COUNT_END_TAG = "\""; 224 225 /* 総オブジェクトカウント数 */ 226 private static final String OBJECT_COUNT_START_TAG = "meta:object-count=\""; 227 private static final String OBJECT_COUNT_END_TAG = "\""; 228 //=========================================================================== 229 230 /* 231 * 処理中の行番号の状態 232 * NORMAL : 通常 233 * LASTROW : 最終行 234 * OVERFLOW : 終了 235 */ 236 private static final int NORMAL = 0; 237 private static final int LASTROW = 1; 238 private static final int OVERFLOW = 2; 239 private int status = NORMAL; 240 241 /* 242 * 各雛形ファイルを処理する際の基準となる行数 243 * 初期>0 2行({@XXX_1}まで)処理後>2 ・・・ 244 * 各雛形で定義されている行番号 + [baseRow] の値がDBTableModel上の行番号に相当する 245 * currentMaxRowは各シート処理後の[baseRow]と同じ 246 */ 247 private int currentBaseRow ; 248 private int currentMaxRow ; 249 250 /* 処理したページ数 */ 251 private int pages ; 252 253 /* 処理行がページエンドカットの対象かどうか */ 254 private boolean isPageEndCut ; // 4.3.1.1 (2008/08/23) ローカル変数化 255 256 /* ページブレイクの処理中かどうか */ 257 private boolean isPageBreak ; 258 259 /* XML宣言の文字列。各XMLで共通なのでクラス変数として定義 */ 260 private String xmlHeader ; 261 262 /* シートブレイク対象かどうか 5.1.7.0 (2010/06/01) */ 263 private int sheetBreakClm = -1; 264 265 /* シート名カラム 5.7.6.2 (2014/05/16) */ 266 private int sheetNameClm = -1; // 今は、ページブレイクカラムと同じカラムを使用しています。 267 268 /* シートのヘッダー部分の再パースを行うかどうか 5.2.2.0 (2010/11/01) */ 269 private boolean isNeedsReparse ; 270 271 /* ページ名のマッピング(元のシート名に対する新しいシート名) 5.2.2.0 (2010/11/01) */ 272 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 273 private final ConcurrentMap<String,List<String>> pageNameMap = new ConcurrentHashMap<>(); 274 275 /* ページ名に依存しているスタイル名称のリスト 5.2.2.0 (2010/11/01) */ 276 private final List<String> repStyleList = new ArrayList<>(); 277 278 /* manifest.xmlに追加が必要なオブジェクトのマップ 5.3.1.0 (2011/01/01) */ 279 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 280 private final ConcurrentMap<String,String> addObjMap = new ConcurrentHashMap<>(); 281 282 private final ExecQueue queue; 283 private final String path; 284 285 private final boolean useChangeType ; // 6.8.3.1 (2017/12/01) 286 287 /** 288 * コンストラクタ 289 * 290 * @og.rev 5.1.2.0 (2010/01/01) 処理した行数をQueueオブジェクトから取得(シート数が256を超えた場合の対応) 291 * @og.rev 6.8.3.1 (2017/12/01) ローカルリソースの文字型⇒数値型変換の処理の有無 292 * 293 * @param qu ExecQueueオブジェクト 294 * @param pt パス 295 */ 296 OdsContentParser( final ExecQueue qu, final String pt ) { 297 queue = qu; 298 path = pt; 299 300 currentBaseRow = queue.getExecRowCnt(); 301 useChangeType = !queue.isFglocal() || HybsSystem.sysBool( "REPORT_USE_CHANGETYPE" ); // 6.8.3.1 (2017/12/01) 302 } 303 304 /** 305 * パース処理を実行します。(1) 306 * 307 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 308 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 309 */ 310 public void exec() { 311 /* 312 * 雛形ヘッダーフッターの定義 313 * OOoではページ毎にヘッダーフッターが設定できないよう。 314 * なので、全てヘッダー扱いで処理 315 */ 316 execStyles(); // (2) 317 318 /* 中身の変換 */ 319 execContent(); // (3) 320 321 /* ヘッダー部分にシート情報がある場合に書き換え */ 322 if( isNeedsReparse ) { 323 /* ヘッダーファイルの再パース */ 324 execContentHeader(); // (4) 325 /* ヘッダーファイルとそれ以降のファイルの連結 */ 326 execMergeContent(); // (5) 327 } 328 329 /* メタデータの変換 */ 330 execMeta(); // (6) 331 332 // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 333 /* 追加した画像、オブジェクトをmanifest.xmlに追加 */ 334 if( !addObjMap.isEmpty() ) { // 6.1.1.0 (2015/01/17) refactoring 335 execManifest(); // (7) 336 } 337 } 338 339 /** 340 * 帳票処理キューを元に、content.xmlを書き換えます。(3) 341 * まず、XMLを一旦メモリ上に展開した後、シート単位に分解し、データの埋め込みを行います。 342 * 343 * @og.rev 4.3.0.0 (2008/07/18) ページ数が256を超えた場合のエラー処理 344 * @og.rev 5.0.0.2 (2009/09/15) LINECOPY機能追加 345 * @og.rev 5.1.2.0 (2010/01/01) 処理したページ数、行数をQueueオブジェクトにセット(シート数が256を超えた場合の対応) 346 * @og.rev 5.1.7.0 (2010/06/01) 複数シート対応 347 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 348 * @og.rev 5.7.6.2 (2014/05/16) PAGEBREAKカラムの値を、シート名として使う場合の処理追加 349 * @og.rev 5.7.6.3 (2014/05/23) PAGEBREAKカラムの値を、シート名として使う場合の、FIRST雛形への適用 350 * @og.rev 6.1.1.0 (2015/01/17) 内部ロジックの見直し。queue.getBody() を、ローカル変数で定義他。 351 * @og.rev 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。 352 */ 353 private void execContent() { 354 final String fileName = path + "content.xml"; 355 final String content = readOOoXml( fileName ); 356 // ファイルの解析し、シート+行単位に分解 357 final String[] tags = tag2Array( content, BODY_START_TAG, BODY_END_TAG ); 358 359 // 5.2.2.0 (2010/11/01) 条件付書式対応 360 // content.xmlのヘッダー部分のみ書き出し 361 final String contentHeader = tags[0]; 362 363 BufferedWriter bw = null; 364 try { 365 bw = getWriter( fileName ); 366 bw.write( xmlHeader ); 367 bw.write( '\n' ); 368 bw.write( contentHeader ); 369 bw.flush(); 370 } 371 catch( final IOException ex ) { 372 queue.addMsg( "[ERROR]PARSE:error occurer while content.xml(header) " + fileName + CR ); 373 throw new HybsSystemException( ex ); 374 } 375 finally { 376 Closer.ioClose( bw ); 377 bw = null; 378 } 379 380 final String contentFooter = tags[1]; 381 382 final List<OdsSheet> firstSheets = new ArrayList<>(); 383 final Map<String, OdsSheet> sheetMap = new HashMap<>(); 384 385 final DBTableModel bodyModel = queue.getBody(); // 6.1.1.0 (2015/01/17) 386 final int rowCount = bodyModel.getRowCount(); // 6.1.1.0 (2015/01/17) 387 388 OdsSheet defaultSheet = null; 389 for( int i=2; i<tags.length; i++ ) { 390 final OdsSheet sheet = new OdsSheet(); 391 // sheet.analyze( tags[i] ); 392 sheet.analyze( tags[i],rowCount ); // 6.1.1.0 (2015/01/17) ループから出す。 393 // 5.1.7.0 (2010/06/01) 複数シート対応 394 final String sheetName = sheet.getSheetName(); 395 if( sheetName.startsWith( FIRST_PAGE_NAME ) ) { 396 firstSheets.add( sheet ); 397 } 398 else { 399 sheetMap.put( sheetName, sheet ); 400 // 一番初めに見つかった表紙以外のシートをデフォルトシートとして設定 401 if( defaultSheet == null ) { 402 defaultSheet = sheet; 403 } 404 } 405 406 // 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。 407 final String orgShtName = sheet.getOrigSheetName(); 408 409 // 5.2.2.0 (2010/11/01) 条件付書式対応 410 if( !isNeedsReparse && contentHeader.indexOf( "=\"" + orgShtName + "." ) >= 0 ) { 411 isNeedsReparse = true; 412 } 413 414 // 5.2.2.0 (2010/11/01) 条件付書式対応 415 pageNameMap.put( orgShtName, new ArrayList<>() ); 416 } 417 418 // content.xmlの書き出し 419 try { 420 // 5.2.2.0 (2010/11/01) 条件付書式対応 421 if( isNeedsReparse ) { 422 // ヘッダーを再パースする場合は、ボディ部分を 423 // content.xml.tmpに書き出して、後でマージする 424 bw = getWriter( fileName + ".tmp" ); 425 getRepStyleList( contentHeader ); 426 } 427 else { 428 // ヘッダーを再パースしない場合は、ボディ部分を 429 // content.xml追加モードで書き込みする 430 bw = getWriter( fileName, true ); 431 } 432 433 // 5.7.6.3 (2014/05/23) PAGEBREAKカラムの値を、シート名として使うかどうか。 434 if( queue.isUseSheetName() ) { 435 sheetNameClm = bodyModel.getColumnNo( PAGE_BREAK, false ); // 6.1.1.0 (2015/01/17) 436 } 437 438 // 表紙ページの出力 439 if( queue.getExecPagesCnt() == 0 ) { 440 for( final OdsSheet firstSheet : firstSheets ) { 441 if( currentBaseRow >= rowCount ) { // 6.1.1.0 (2015/01/17) ループから出す。 442 break; 443 } 444 writeParsedSheet( firstSheet, bw ); 445 } 446 } 447 448 // 5.1.7.0 (2010/06/01) 複数シート対応 449 sheetBreakClm = bodyModel.getColumnNo( SHEET_BREAK, false ); // 6.1.1.0 (2015/01/17) 450 451 // 5.7.6.3 (2014/05/23) 表紙ページも、PAGEBREAKカラムの値を、シート名として使えるようにする。 452 453 // 繰り返しページの出力 454 while( currentBaseRow < rowCount ) { // 6.1.1.0 (2015/01/17) ループから出す。 455 // 4.3.0.0 (2008/07/18) ページ数が256を超えた場合にエラーとする 456 // 5.1.2.0 (2010/01/01) 256シートを超えた場合の対応 457 if( pages >= ExecQueue.MAX_SHEETS_PER_FILE ) { 458 queue.setEnd( false ); 459 break; 460 } 461 462 OdsSheet sheet = null; 463 if( sheetBreakClm >= 0 ) { 464 final String sheetName = bodyModel.getValue( currentBaseRow, sheetBreakClm ); // 6.1.1.0 (2015/01/17) 465 if( sheetName != null && sheetName.length() > 0 ) { 466 sheet = sheetMap.get( sheetName ); 467 } 468 } 469 if( sheet == null ) { sheet = defaultSheet; } 470 471 writeParsedSheet( sheet, bw ); 472 } 473 474 // 5.1.2.0 (2010/01/01) 256シートを超えた場合の対応 475 queue.addExecPageCnt( pages ); 476 queue.setExecRowCnt( currentBaseRow ); 477 478 // フッター 479 bw.write( contentFooter ); 480 bw.flush(); 481 } 482 catch( final IOException ex ) { 483 queue.addMsg( "[ERROR]PARSE:error occurer while write Parsed Sheet " + fileName + CR ); 484 throw new HybsSystemException( ex ); 485 } 486 finally { 487 Closer.ioClose( bw ); 488 } 489 } 490 491 /** 492 * シート単位にパースされた文書データを書き込みます 493 * 出力されるシート名には、ページ番号と基底となる行番号をセットします。 494 * 495 * @og.rev 4.2.4.0 (2008/07/04) 行単位にファイルに書き込むように変更 496 * @og.rev 5.2.0.0 (2010/09/01) 表紙の場合は、BODY部分のデータが含まれていなくてもOK 497 * @og.rev 5.2.1.0 (2010/10/01) シート名定義対応 498 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 499 * @og.rev 5.7.6.2 (2014/05/16) PAGEBREAKカラムの値を、シート名として使う場合の処理追加 500 * @og.rev 5.7.6.3 (2014/05/23) FIRST雛形シート名が、FIRST**** の場合、**** 部分をシート名に使う。 501 * @og.rev 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。 502 * @og.rev 7.3.0.1 (2021/01/22) 画像ファイルの置き方によって、ヘッダー部に {@IMG.XXX} が書かれることがある。 503 * 504 * @param sheet シート 505 * @param bw BufferedWriterオブジェクト 506 * @throws IOException 書き込みに失敗した場合 507 */ 508 private void writeParsedSheet( final OdsSheet sheet, final BufferedWriter bw ) throws IOException { 509 // シート名 510 String outputSheetName = null; 511 512 // 5.7.6.2 (2014/05/16) PAGEBREAKカラムの値を、シート名として使うかどうか。 513 if( sheetNameClm >= 0 ) { 514 final String sheetName = queue.getBody().getValue( currentBaseRow, sheetNameClm ); 515 if( sheetName != null ) { 516 outputSheetName = sheetName; 517 } 518 } 519 520 // 5.7.6.3 (2014/05/23) FIRST雛形シート名が、FIRST**** の場合、**** 部分をシート名に使う。 521 if( outputSheetName == null ) { 522 String sheetName = sheet.getSheetName(); 523 if( sheetName.startsWith( FIRST_PAGE_NAME ) ) { 524 sheetName = sheetName.substring( FIRST_PAGE_NAME.length() ).trim(); 525 // 小細工。"FIRST_****" の場合は、"_" を外す。長さ0判定の前に行う。 526 if( StringUtil.startsChar( sheetName , '_' ) ) { // 6.2.0.0 (2015/02/27) 1文字 String.startsWith 527 sheetName = sheetName.substring( 1 ); 528 } 529 530 // 長さ0の場合(例えば、FIRSTだけとか)は、設定しない。 531 if( sheetName.length() > 0 ) { outputSheetName = sheetName; } 532 } 533 } 534 535 // 従来からあるシート名の値 536 if( outputSheetName == null ) { 537 if( sheet.getConfSheetName() == null ) { 538 outputSheetName = "Page" + ( queue.getExecPagesCnt() + pages ) + "_Row" + currentBaseRow ; 539 } 540 else { 541 outputSheetName = sheet.getConfSheetName() + ( queue.getExecPagesCnt() + pages + 1 ) ; 542 } 543 } 544 // ページブレイク変数を初期化 545 isPageBreak = false; 546 547 // 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。 548 final String orgShtName = sheet.getOrigSheetName(); 549 550 // シートのヘッダー部分を書き込み(シート名も書き換え) 551 String headerStr = sheet.getHeader().replace( SHEET_NAME_START + orgShtName, SHEET_NAME_START + outputSheetName ); 552 // 印刷範囲指定部分のシート名を変更 553 // 4.3.3.5 (2008/11/08) 空白ページ出力の対策。印刷範囲のシート名書き換えを追加 554 final int printRangeStart = headerStr.indexOf( PRINT_RANGE_START ); 555 if( printRangeStart >= 0 ) { 556 final int printRangeEnd = headerStr.indexOf( PRINT_RANGE_END, printRangeStart + PRINT_RANGE_START.length() ); 557 String rangeStr = headerStr.substring( printRangeStart, printRangeEnd ); 558 rangeStr = rangeStr.replace( orgShtName, outputSheetName ); 559 headerStr = headerStr.substring( 0, printRangeStart ) + rangeStr + headerStr.substring( printRangeEnd ); 560 } 561 562 // 7.3.0.1 (2021/01/22) 画像ファイルの置き方によって、ヘッダー部に {@IMG.XXX} が書かれることがある。 563 writeParsedRow( headerStr, bw, orgShtName, outputSheetName ); 564// bw.write( headerStr ); 565 566 // シートのボディ部分を書き込み 567 final String[] rows = sheet.getRows(); 568 for( int i=0; i<rows.length; i++ ) { 569 // 4.3.4.4 (2009/01/01) 570 writeParsedRow( rows[i], bw, orgShtName, outputSheetName ); 571 } 572 // {@XXXX}が埋め込まれていない場合はエラー 573 // 5.2.0.0 (2010/09/01) 表紙の場合は、BODY部分のデータが含まれていなくてもOK 574 if( currentBaseRow == currentMaxRow && !orgShtName.startsWith( FIRST_PAGE_NAME ) ) { 575 queue.addMsg( "[ERROR]PARSE:No Data defined on Template ODS(" + queue.getListId() + ")" + CR ); 576 throw new HybsSystemException(); 577 } 578 currentBaseRow = currentMaxRow; 579 580 // シートのフッター部分を書き込み 581 bw.write( sheet.getFooter() ); 582 583 pages++; 584 585 // 5.2.2.0 (2010/11/01) 条件付書式対応 586 pageNameMap.get( orgShtName ).add( outputSheetName ); 587 } 588 589 /** 590 * 行単位にパースされた文書データを書き込みます。 591 * 592 * @og.rev 4.2.3.1 (2008/06/19) 関数エラーを表示させないため、ISERROR関数を埋め込み 593 * @og.rev 4.2.4.0 (2008/07/04) 行単位にファイルに書き込むように変更 594 * @og.rev 4.3.0.0 (2008/07/17) {@と}の整合性チェック追加 595 * @og.rev 4.3.0.0 (2008/07/22) 行最後の{@}整合性エラーハンドリング追加 596 * @og.rev 4.3.3.5 (2008/11/08) 画像の動的な入れ替えに対応 597 * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更 598 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 599 * @og.rev 5.4.2.0 (2011/12/01) ページブレイク、シートブレイク中でもページエンドカットが適用されるようにする。 600 * @og.rev 5.6.3.1 (2013/04/05) 条件付書式の属性終了文字対応 601 * @og.rev 6.8.3.1 (2017/12/01) ローカルリソースの文字型⇒数値型変換の処理の有無 602 * 603 * @param row 行データ 604 * @param bw BufferedWriterオブジェクト 605 * @param sheetNameOrig 元シート名 606 * @param sheetNameNew 新シート名 607 * @throws IOException 書き込みに失敗した場合 608 */ 609 private void writeParsedRow( final String row, final BufferedWriter bw, final String sheetNameOrig, final String sheetNameNew ) throws IOException { 610 isPageEndCut = false; 611 612 String rowStr = new TagParser() { 613 /** 614 * 開始タグから終了タグまでの文字列の処理を定義します。 615 * 616 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 617 * @param buf 出力を行う文字列バッファ 618 * @param offset 終了タグのオフセット(ここでは使っていません) 619 */ 620 @Override 621 protected void exec( final String str, final StringBuilder buf, final int offset ) { 622 String key = TagParser.checkKey( str, buf ); 623 624 // 4.3.0.0 (2008/07/15) "<"が入っていた場合には{@不整合}エラー 625 if( key.indexOf( '<' ) >= 0 ){ 626 queue.addMsg( "[ERROR]PARSE:{@と}の整合性が不正です。変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key + CR ); 627 throw new HybsSystemException(); 628 } 629 630 // QRコードの処理、処理後はoffsetが進むため、offsetを再セット 631 if( key.startsWith( QRCODE_PREFIX ) ) { 632 setOffset( makeQRImage( row, offset, key.substring( QRCODE_PREFIX.length() ), buf ) ); 633 } 634 // 画像置き換えの処理、処理後はoffsetが進むため、offsetを再セット 635 else if( key.startsWith( IMG_PREFIX ) ) { 636 setOffset( changeImage( row, offset, key.substring( IMG_PREFIX.length() ), buf ) ); 637 } 638 // コメント(アノテーション)による置き換え処理、処理後はoffsetが進むため、offsetを再セット 639 else if( key.startsWith( ANNOTATION_PREFIX ) ) { 640 setOffset( parseByAnnotation( row, offset, key.substring( ANNOTATION_PREFIX.length() ), buf ) ); 641 } 642 else { 643 String val = getValue( key ); 644 // 5.5.2.4 (2012/05/16) String key は使われていないので、削除します。 645 if( useChangeType ) { // 6.8.3.1 (2017/12/01) 646 // 5.5.2.4 (2012/05/16) String key は使われていないので、削除します。 647 changeType( row, offset, val, getNativeType( key, val ), buf ); 648 } 649 buf.append( val ); 650 } 651 652 // 処理行がページエンドカットの対象か 653 if( queue.isFgcut() && PAGE_END_CUT.equals( key ) ) { 654 isPageEndCut = true; 655 } 656 } 657 }.doParse( row, VAR_START, VAR_END, false ); 658 659 //==== ここからは後処理 ========================================================= 660 /* 661 * ページエンドカットの判定は最後で処理する。 662 * {@PAGEENDCUT}が行の最初に書かれている場合は、OVERFLOWになっていない可能性が 663 * あるため行の途中では判断できない 664 */ 665 // 5.4.2.0 (2011/12/01) シートブレイク中でもページエンドカットが適用されるようにする。 666 // (通常のページブレイクは先読み判定のためページエンドカットすると、ブレイク発生行自身が 667 // 削除されてしまうため現時点では未対応) 668// if( isPageEndCut && ( status == OVERFLOW || ( sheetBreakClm >= 0 && isPageBreak ) ) ) { 669 if( isPageEndCut && ( status == OVERFLOW || sheetBreakClm >= 0 && isPageBreak ) ) { // 6.9.7.0 (2018/05/14) PMD Useless parentheses. 670 // ページエンドカットの場合は、非表示状態にする。 671 rowStr = rowStr.replace( ROW_START_TAG, ROW_START_TAG_INVISIBLE ) ; 672 } 673 674 /* 675 * オブジェクトで定義されているテーブル名を変更 676 */ 677 if( rowStr.indexOf( OBJECT_SEARCH_STR ) >= 0 ) { 678 rowStr = rowStr.replace( OBJECT_SEARCH_STR + sheetNameOrig, OBJECT_SEARCH_STR + sheetNameNew ); 679 } 680 681 /* 682 * 関数エラーを表示されないため、ISERROR関数を埋め込み 4.2.3.1 (2008/06/19) 683 */ 684 rowStr = replaceOoocError( rowStr ); 685 686 /* 687 * グラフをシート毎にコピー 5.1.8.0 (2010/07/01) 688 */ 689 rowStr = replaceGraphInfo( rowStr, sheetNameOrig, sheetNameNew ); 690 691 /* 692 * アノテーション(コメント)を削除 5.1.8.0 (2010/07/01) 693 * (コメントが存在すると起動が異常に遅くなる) 694 */ 695 if( rowStr.indexOf( ANNOTATION_START_TAG ) >= 0 ) { 696 rowStr = new TagParser() {}.doParse( rowStr, ANNOTATION_START_TAG, ANNOTATION_END_TAG ); 697 } 698 699 /* 700 * 条件付書式対応 5.2.2.0 (2010/11/01) 701 * テーブル内に存在するスタイル名称を書き換え 702 */ 703 if( isNeedsReparse ) { 704 for( final String name : repStyleList ) { 705 // 5.6.3.1 (2013/04/05) 属性終了追加 706 if( rowStr.indexOf( TABLE_STYLE_NAME_START_TAG + name + TABLE_STYLE_NAME_END_TAG ) >= 0 ) { 707 rowStr = rowStr.replace( TABLE_STYLE_NAME_START_TAG + name + TABLE_STYLE_NAME_END_TAG, TABLE_STYLE_NAME_START_TAG + name + "_" + sheetNameNew + TABLE_STYLE_NAME_END_TAG ); 708 } 709 } 710 } 711 //============================================================================== 712 713 bw.write( rowStr ); 714 } 715 716 /** 717 * 帳票データに応じて、カラムの属性を変更(文字型⇒数値型)に変更します。 718 * 719 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 720 * @og.rev 5.5.2.4 (2012/05/16) String key は使われていないので、削除します。 721 * 722 * @param row 行データ 723 * @param curOffset オフセット 724 * @param val 設定値 725 * @param type ネイティブタイプ 726 * @param sb StringBuilderオブジェクト 727 */ 728 private void changeType( final String row, final int curOffset 729 , final String val, final NativeType type, final StringBuilder sb ) { 730 if( val == null || val.isEmpty() ) { 731 return; 732 } 733 // 書き換え対象は数値型のみ 734 if( type != NativeType.INT && type != NativeType.LONG && type != NativeType.DOUBLE ) { 735 return; 736 } 737 // 処理対象がセルでない(オブジェクト)は書き換えしない 738 if( !isCell( row, curOffset ) ) { 739 return; 740 } 741 742 // セルの文字が{@xxxx_n}のみであった場合だけ、数値定義の判定を行う。 743 // (関数内に{@xxxx_n}等があった場合は、判定しない(<text:p>{@xxxx_n}</text:p>の場合のみ)) 744 if( sb.lastIndexOf( TEXT_START_TAG ) + TEXT_START_TAG.length() == sb.length() 745 && row.indexOf( TEXT_END_TAG, curOffset ) == curOffset ) { 746 final int typeIdx = sb.lastIndexOf( TABLE_CELL_STRING_TYPE ); 747 final int cellIdx = sb.lastIndexOf( TABLE_CELL_START_TAG ); 748 if( typeIdx >= 0 && cellIdx >= 0 && typeIdx > cellIdx ) { 749 // office:value-type="string" を office:value-type="float" office:value="xxx" に変換 750 sb.replace( typeIdx, typeIdx + TABLE_CELL_STRING_TYPE.length() 751 ,TABLE_CELL_FLOAT_TYPE + " " + TABLE_CELL_FLOAT_VAL_START + val + TABLE_CELL_FLOAT_VAL_END ); 752 } 753 } 754 } 755 756 /** 757 * 引数に指定された文字列のNativeタイプを返します。 758 * 759 * リソース使用時は、各DBTypeで定義されたNativeタイプを、 760 * 未使用時は、値からNativeタイプを取得して返します。 761 * 762 * @og.rev 5.1.8.0 (2010/07/01) NativeType#getType(String) のメソッドを使用するように変更。 763 * @og.rev 5.10.5.1 (2018/11/09) intだけでなくlongの0始まりも文字列として扱う 764 * 765 * @param key キー 766 * @param val 文字列 767 * 768 * @return NATIVEの型の識別コード 769 * @og.rtnNotNull 770 * @see org.opengion.fukurou.model.NativeType 771 */ 772 private NativeType getNativeType( final String key, final String val ) { 773 if( val == null || val.isEmpty() ) { 774 return NativeType.STRING; 775 } 776 777 NativeType type = null; 778 if( queue.isFglocal() ) { 779 String name = key; 780 final int conOffset = key.lastIndexOf( VAR_CON ); 781 if( conOffset >= 0 ) { 782 int rownum = -1; 783 try { 784 rownum = Integer.parseInt( name.substring( conOffset + VAR_CON.length(), name.length() ) ); // 6.0.2.4 (2014/10/17) メソッド間違い 785 } 786 // '_'以降の文字が数字でない場合は、'_'以降の文字もカラム名の一部として扱う 787 catch( final NumberFormatException ex ) { 788 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks 789 final String errMsg = "'_'以降の文字をカラム名の一部として扱います。カラム名=[" + key + "]" + CR + ex.getMessage() ; 790 System.err.println( errMsg ); 791 } 792 if( rownum >= 0 ) { 793 name = name.substring( 0, conOffset ); 794 } 795 } 796 final int col = queue.getBody().getColumnNo( name, false ); 797 if( col >= 0 ) { 798 type = queue.getBody().getDBColumn( col ).getNativeType(); 799 } 800 } 801 802 if( type == null ) { 803 // ,は削除した状態で判定 804 final String repVal = val.replace( ",", "" ); 805 type = NativeType.getType( repVal ); // 5.1.8.0 (2010/07/01) NativeType#getType(String) のメソッドを使用 806 // 整数型で、0nnnとなっている場合は、文字列をして扱う 807// if( type == NativeType.INT && repVal.length() >= 2 && repVal.charAt(0) == '0' ) { 808 if( ( type == NativeType.INT || type == NativeType.LONG ) && repVal.length() >= 2 && repVal.charAt(0) == '0' ) { // 5.10.5.1 (2018/11/09) LONGを含む 809 type = NativeType.STRING; 810 } 811 } 812 813 return type; 814 } 815 816 /** 817 * コメント(アノテーションによる置き換え処理を行います) 818 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。 819 * 820 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 821 * @og.rev 6.8.3.1 (2017/12/01) ローカルリソースの文字型⇒数値型変換の処理の有無 822 * 823 * @param row 行データ 824 * @param curOffset オフセット 825 * @param key キー 826 * @param sb StringBuilderオブジェクト 827 * 828 * @return 処理後のオフセット 829 */ 830 private int parseByAnnotation( final String row, final int curOffset, final String key, final StringBuilder sb ) { 831 int offset = curOffset; 832 final boolean isCell = isCell( row, offset ); 833 834 // セルの場合のみ置き換えの判定を行う(オブジェクトの場合は判定しない) 835 if( isCell ) { 836 final int cellStrIdx = sb.lastIndexOf( TABLE_CELL_START_TAG, offset ); 837 // office:value-type="float" office:value="xxx" を office:value-type="string" に変換 838 // 数値型の場合は、後で再度変換を行う。 839 // (文字型に変換しておかないと、値がnullの場合でも"0"が表示されてしまうため) 840 final int floatIdx = sb.indexOf( TABLE_CELL_FLOAT_TYPE, cellStrIdx ); 841 if( floatIdx >= 0 ) { 842 sb.replace( floatIdx, floatIdx + TABLE_CELL_FLOAT_TYPE.length(), TABLE_CELL_STRING_TYPE ); 843 844 final int floatStrIdx = sb.indexOf( TABLE_CELL_FLOAT_VAL_START, floatIdx ); 845 if( floatStrIdx >= 0 ) { 846 final int floatEndIdx = sb.indexOf( TABLE_CELL_FLOAT_VAL_END, floatStrIdx + TABLE_CELL_FLOAT_VAL_START.length() ); 847 if( floatEndIdx >= 0 ) { 848 sb.replace( floatStrIdx, floatEndIdx + TABLE_CELL_FLOAT_VAL_END.length(), "" ); 849 } 850 } 851 } 852 } 853 854 // アノテーションの値から、セルの文字列部分を置き換え 855 final int endIdx = isCell ? row.indexOf( TABLE_CELL_END_TAG, offset ) : row.indexOf( DRAW_END_KEY, offset ); 856 if( endIdx >= 0 ) { 857 int textStrIdx = row.indexOf( TEXT_START_ANO_TAG, offset ); 858 // セルのコメントの場合、<text:pで検索すると、オブジェクトのテキストが検索されている可能性がある。 859 // このため、セルの<text:pが見つかるまで検索を繰り返す 860 if( isCell ) { 861 while( !isCell( row, textStrIdx ) && textStrIdx >= 0 ) { 862 textStrIdx = row.indexOf( TEXT_START_ANO_TAG, textStrIdx + 1 ); 863 } 864 } 865 if( textStrIdx >= 0 && textStrIdx < endIdx ) { 866 // セルのコメントの場合、</text:p>で検索すると、オブジェクトのテキストが検索されている可能性がある。 867 // このため、セルの</text:p>が見つかるまで検索を繰り返す 868 int textEndIdx = row.lastIndexOf( TEXT_END_TAG, endIdx ); 869 if( isCell ) { 870 while( !isCell( row, textEndIdx ) && textEndIdx >= 0 ) { 871 textEndIdx = row.lastIndexOf( TEXT_END_TAG, textEndIdx - 1 ); 872 } 873 } 874 if( textEndIdx >= 0 && textStrIdx < textEndIdx && textEndIdx < endIdx ) { 875 // <text:p xxxx> の xxxx> の部分(style定義など)を書き込み 876 final int textStyleEnd = row.indexOf( TEXT_START_END_ANO_TAG, textStrIdx + TEXT_START_ANO_TAG.length() ) + TEXT_START_END_ANO_TAG.length(); 877 sb.append( row.substring( offset, textStyleEnd ) ); 878 879 // <text:pの中身(spanタグなどを取り除いた状態の文字列 880 final String textVal = TagParser.checkKey( row.substring( textStyleEnd, textEndIdx ), sb ); 881 // 取得したテキスト内にタグ文字が含まれている場合は、処理しない 882 if( textVal.indexOf( '<' ) < 0 && textVal.indexOf( '>' ) < 0 ) { 883 // <text:p xxxx>を書き出し 884 final String val = getValue( key ); 885 if( useChangeType ) { // 6.8.3.1 (2017/12/01) 886 // 5.5.2.4 (2012/05/16) String key は使われていないので、削除します。 887 changeType( row, textEndIdx, val, getNativeType( key, textVal ), sb ); 888 } 889 sb.append( val ); 890 } 891 offset = textEndIdx; 892 } 893 } 894 } 895 896 return offset; 897 } 898 899 /** 900 * 現在のオフセットがセルかどうかを返します。 901 * 902 * trueの場合はセルを、falseの場合はオブジェクトを意味します。 903 * 904 * セルとして判定されるための条件は以下の通りです。 905 * 現在のoffsetを基準として、 906 * ①前に<draw:(オブジェクトの開始)が見つからない 907 * ②前に<table:table-cell(セルの始まり)が<draw:(オブジェクトの始まり)より後方にある 908 * ③後に</draw:(オブジェクトの終わり)が見つからない 909 * ④後に</draw:(オブジェクトの終わり)が</table:table-cell>(セルの終わり)より後方にある 910 * 911 * @param row 行データ 912 * @param offset オフセット 913 * 914 * @return 現在のオフセットがセルかどうか(falseの場合はオブジェクト) 915 */ 916 private boolean isCell( final String row, final int offset ) { 917 final int drawStartOffset = row.lastIndexOf( DRAW_START_KEY, offset ); 918 if( drawStartOffset < 0 ) { 919 return true; 920 } 921 else { 922 final int cellStartOffset = row.lastIndexOf( TABLE_CELL_START_TAG, offset ); 923 if( drawStartOffset < cellStartOffset ) { 924 return true; 925 } 926 else { 927 final int drawEndOffset = row.indexOf( DRAW_END_KEY, offset ); 928 if( drawEndOffset < 0 ) { 929 return true; 930 } 931 else { 932 final int cellEndOffset = row.indexOf( TABLE_CELL_END_TAG, offset ); 933 // 5.1.8.0 (2010/07/01) Avoid unnecessary if..then..else statements when returning a boolean 934 return cellEndOffset >= 0 && cellEndOffset < drawEndOffset ; 935 } 936 } 937 } 938 } 939 940 /** 941 * QRコードを作成します。 942 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。 943 * 944 * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定 945 * @og.rev 4.3.3.5 (2008/11/08) ↑の判定は存在チェックを行ってから処理する。ファイル名に処理行を付加 946 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 947 * @og.rev 7.0.5.1 (2019/09/27) QRコードのパラメータをシステムリソースで設定できるようにします(ただし、staticとします) 948 * @og.rev 7.2.3.0 (2020/04/10) QRコードのパラメータをシステムリソースで設定(帳票出力のQRコード作成時のテキストのエンコード指定) 949 * 950 * @param row 行データ 951 * @param curOffset オフセット 952 * @param key キー 953 * @param sb StringBuilderオブジェクト 954 * 955 * @return 処理後のオフセット 956 */ 957 private int makeQRImage( final String row, final int curOffset, final String key, final StringBuilder sb ) { 958 int offset = curOffset; 959 960 // {@QRCODE.XXXX}から実際に画像のパスが書かれている部分までを書き込む 961 offset = row.indexOf( DRAW_IMG_START_TAG, offset ) + DRAW_IMG_START_TAG.length(); 962 sb.append( row.substring( curOffset, offset ) ); 963 // 画像のパスの終了インデックスを求める 964 offset = row.indexOf( DRAW_IMG_HREF_END, offset ) + DRAW_IMG_HREF_END.length(); 965 966 // QRCODEの画像ファイル名を求め書き込む 967 // 4.3.3.5 (2008/11/08) ファイル名に処理行を付加 968 final String fileName = IMG_DIR + '/' + key + "_" + currentBaseRow + QRCODE_FILETYPE; 969 sb.append( fileName ).append( DRAW_IMG_HREF_END ); 970 971 // QRCODEに書き込む値を求める 972 final String value = getValue( key ); 973 974 // QRCODEの作成 975 // 4.3.3.5 (2008/11/08) ファイル名に処理行を付加 976 final String fileNameAbs = 977 new File( path ).getAbsolutePath() + File.separator + IMG_DIR + File.separator + key + "_" + currentBaseRow + QRCODE_FILETYPE; 978 979 // 画像リンクが無効となっている場合は、Picturesのフォルダが作成されていない可能性がある 980 // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定 981 // 4.3.3.5 (2008/11/08) 存在チェック追加 982 // 6.9.7.0 (2018/05/14) PMD These nested if statements could be combined 983// if( !new File( fileNameAbs ).getParentFile().exists() ) { 984// if( new File( fileNameAbs ).getParentFile().mkdirs() ) { 985 if( !new File( fileNameAbs ).getParentFile().exists() 986 && new File( fileNameAbs ).getParentFile().mkdirs() ) { 987 System.err.println( fileNameAbs + " の ディレクトリ作成に失敗しました。" ); 988// } 989 } 990 991 final QrcodeImage qrImage = new QrcodeImage(); 992// qrImage.init( value, fileNameAbs ); 993// qrImage.init( value, fileNameAbs, QR_VERSION, QR_ENCMODE, QR_ERRCRCT, QR_IMAGE_TYPE, QR_PIXEL ); // 7.0.5.1 (2019/09/27) 994 qrImage.init( value, fileNameAbs, QR_VERSION, QR_ENCMODE, QR_ERRCRCT, QR_IMAGE_TYPE, QR_PIXEL, QR_TXT_ENC ); // 7.2.3.0 (2020/04/10) 995 qrImage.saveImage(); 996 997 // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 998 addObjMap.put( fileName, QRCODE_FILETYPE.substring( 1 ) ); 999 1000 // 読み込みOffsetを返します 1001 return offset; 1002 } 1003 1004 /** 1005 * DBTableModelに設定されたパスから画像データを取得し、内部に取り込みます 1006 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。 1007 * 1008 * @og.rev 4.3.3.5 (2008/11/08) 新規追加 1009 * @og.rev 4.3.3.6 (2008/11/15) 画像パスが存在しない場合は、リンクタグ(draw:image)自体を削除 1010 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 1011 * @og.rev 7.3.0.1 (2021/01/22) 画像ファイル名が漢字の場合、うまくいかないので、置き換える。 1012 * 1013 * @param row 行データ 1014 * @param curOffset オフセット 1015 * @param key キー 1016 * @param sb StringBuilderオブジェクト 1017 * 1018 * @return 処理後のオフセット 1019 */ 1020 private int changeImage( final String row, final int curOffset, final String key, final StringBuilder sb ) { 1021 int offset = curOffset; 1022 File imgFile = null; 1023 1024 // 画像ファイルを読み込むパスを求める 1025 final String value = getValue( key ); 1026 1027 if( value != null && value.length() > 0 ) { 1028 imgFile = new File( HybsSystem.url2dir( value ) ); 1029 } 1030 1031 // 画像ファイルのパスが入っていて、実際に画像が存在する場合 1032 if( imgFile != null && imgFile.exists() ) { 1033 // {@IMG.XXXX}から実際に画像のパスが書かれている部分までを書き込む 1034 offset = row.indexOf( DRAW_IMG_START_TAG, offset ) + DRAW_IMG_START_TAG.length(); 1035 sb.append( row.substring( curOffset, offset ) ); 1036 1037 // 画像のパスの終了インデックスを求める 1038 offset = row.indexOf( DRAW_IMG_HREF_END, offset ) + DRAW_IMG_HREF_END.length(); 1039 1040 // 7.3.0.1 (2021/01/22) 画像ファイル名が漢字の場合、うまくいかないので、置き換える。 1041// final String fileNameOut = IMG_DIR + '/' + imgFile.getName(); 1042 final String extension = value.substring( value.lastIndexOf('.') ); // 7.3.0.1 (2021/01/22) 拡張子( .付き ) 1043 // 7.3.0.1 (2021/01/22) 同一ファイルは同一名にしておきます。マイナスが気持ち悪いのでハッシュ値は絶対値にしておきます。 1044 // 8.0.0.0 (2021/07/31) spotbugs:ハッシュコードが Integer.MIN_VALUE なら結果は同様に負です (Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE なので)。 1045// final String fileNameOut = IMG_DIR + '/' + Math.abs( imgFile.hashCode() ) + extension; 1046 final String fileNameOut = IMG_DIR + '/' + Integer.toUnsignedString( imgFile.hashCode() ) + extension; 1047 1048 sb.append( fileNameOut ).append( DRAW_IMG_HREF_END ); 1049 1050 final File fileOutAbs = new File( path,fileNameOut ); 1051 if( !fileOutAbs.getParentFile().exists() && fileOutAbs.getParentFile().mkdirs() ) { 1052 System.err.println( fileOutAbs + " の ディレクトリ作成に失敗しました。" ); 1053 } 1054 1055 // final String fileNameOutAbs = 1056// new File( path ).getAbsolutePath() + File.separator + IMG_DIR + File.separator + imgFile.getName(); 1057 // new File( path ).getAbsolutePath() + '/' + fileNameOut; 1058 // // 6.9.7.0 (2018/05/14) PMD These nested if statements could be combined 1059// if( !new File( fileNameOutAbs ).getParentFile().exists() ) { 1060// if( new File( fileNameOutAbs ).getParentFile().mkdirs() ) { 1061 // if( !new File( fileNameOutAbs ).getParentFile().exists() 1062 // && new File( fileNameOutAbs ).getParentFile().mkdirs() ) { 1063 // System.err.println( fileNameOutAbs + " の ディレクトリ作成に失敗しました。" ); 1064// } 1065 // } 1066 // FileUtil.copy( imgFile, new File( fileNameOutAbs ) ); // imgFile → fileNameOutAbs copy 1067 FileUtil.copy( imgFile, fileOutAbs ); // imgFile → fileOutAbs copy 1068 1069 // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 1070 addObjMap.put( fileNameOut, getSuffix( imgFile.getName() ) ); 1071 } 1072 // 画像パスが設定されていない、又は画像が存在しない場合 1073 else { 1074 // {@IMG.XXXX}から見て、<draw:image> ... </draw:image>までをスキップする 1075 offset = row.indexOf( DRAW_IMG_START_TAG, offset ); 1076 sb.append( row.substring( curOffset, offset ) ); 1077 1078 offset = row.indexOf( DRAW_IMG_END_TAG, offset ) + DRAW_IMG_END_TAG.length(); 1079 } 1080 1081 // 読み込みOffsetを返します 1082 return offset; 1083 } 1084 1085 /** 1086 * 変換後の行データで定義されている関数にISERROR関数を埋め込みます。 1087 * 1088 * これは、OOoの関数の動作として、不正な引数等が入力された場合(null値など)に、 1089 * エラー:xxxと表示されてしまうため、これを防ぐために関数エラーのハンドリングを行い、 1090 * エラーの場合は、空白文字を返すようにします。 1091 * 1092 * @og.rev 4.3.7.2 (2009/06/15) 開始文字が変更になったため対応 1093 * @og.rev 5.0.2.0 (2009/11/01) 関数内の"(quot)は、メタ文字に変換する 1094 * @og.rev 5.1.7.0 (2010/06/01) 関数の終わりが)出ない場合にエラーとなるバグを修正 1095 * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更 1096 * 1097 * @param row 行データ 1098 * 1099 * @return 変換後の行データ 1100 */ 1101 private String replaceOoocError( final String row ) { 1102 // 4.3.7.2 (2009/06/15) OOOC_FUNCTION_START3の条件判定追加。どちらか分からないので変数で受ける。 1103 final String functionStart; 1104 if( row.indexOf( OOOC_FUNCTION_START_3 ) >= 0 ) { functionStart = OOOC_FUNCTION_START_3; } 1105 else if( row.indexOf( OOOC_FUNCTION_START ) >= 0 ) { functionStart = OOOC_FUNCTION_START; } 1106 else { return row; } 1107 1108 final String rowStr = new TagParser() { 1109 /** 1110 * 開始タグから終了タグまでの文字列の処理を実行するかどうかを定義します。 1111 * 1112 * @param strOffset 開始タグのオフセット 1113 * @param endOffset 終了タグのオフセット 1114 * 1115 * @return 処理を行うかどうか(true:処理を行う false:処理を行わない) 1116 */ 1117 @Override 1118 protected boolean checkIgnore( final int strOffset, final int endOffset ) { 1119 // 5.1.7.0 (2010/06/01) 関数の終わりが)出ない場合にエラーとなるバグを修正 1120 // 単なる行参照でも、of:=で始まるがこの場合は、関数でないため終わりが)でない 1121 // このため、)が見つからないまたは、タグの終わり(>)が先に見つかった場合は、エラー関数を 1122 // 埋め込まないようにする。 1123 int tmpOffset = row.indexOf( ">", strOffset + 1 ); 1124 return endOffset >= 0 && endOffset < tmpOffset ; 1125 } 1126 1127 /** 1128 * 開始タグから終了タグまでの文字列の処理を定義します。 1129 * 1130 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 1131 * @param buf 出力を行う文字列バッファ 1132 * @param offset 終了タグのオフセット(ここでは使っていません) 1133 */ 1134 @Override 1135 protected void exec( final String str, final StringBuilder buf, final int offset ) { 1136 String key = str.substring( functionStart.length(), str.length() - OOOC_FUNCTION_END.length() ) + ")"; 1137 key = key.replace( "\"", """" ).replace( OOO_CR, "" ); 1138 // 6.4.2.1 (2016/02/05) PMD refactoring. 1139 buf.append( functionStart ).append( "IF(ISERROR(" ).append( key ).append( ");"";" ).append( key ).append( OOOC_FUNCTION_END ); 1140 } 1141 }.doParse( row, functionStart, OOOC_FUNCTION_END ); 1142 1143 return rowStr; 1144 } 1145 1146 /** 1147 * グラフ表示データ部分を更新します。 1148 * 1149 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 1150 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 1151 * 1152 * @param row 行データ 1153 * @param sheetOrig 元シート 1154 * @param sheetNew 新シート 1155 * 1156 * @return 変換後の行データ 1157 */ 1158 private String replaceGraphInfo( final String row, final String sheetOrig, final String sheetNew ) { 1159 if( row.indexOf( GRAPH_START_TAG ) < 0 || row.indexOf( GRAPH_UPDATE_RANGE_START ) < 0 ) { return row; } 1160 1161 // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX' 1162 return new TagParser() { 1163// final String rowStr = new TagParser() { 1164 /** 1165 * 開始タグから終了タグまでの文字列の処理を定義します。 1166 * 1167 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 1168 * @param buf 出力を行う文字列バッファ 1169 * @param offset 終了タグのオフセット(ここでは使っていません) 1170 */ 1171 @Override 1172 protected void exec( final String str, final StringBuilder buf, final int offset ) { 1173 // <draw:object ... /> の部分 1174 String graphTag = str; 1175 1176 if( graphTag.indexOf( GRAPH_UPDATE_RANGE_START ) >= 0 ) { 1177 final String nameOrig = TagParser.getValueFromTag( graphTag, GRAPH_HREF_START, GRAPH_HREF_END ); 1178 if( new File( path + nameOrig ).exists() ) { 1179 final String nameNew = nameOrig + "_" + pages; 1180 1181 // グラフオブジェクトの定義ファイルをコピー(./Object X/* ⇒ ./Object X_n/*) 1182 FileUtil.copyDirectry( path + nameOrig, path + nameNew ); 1183 graphTag = graphTag.replace( GRAPH_HREF_START + nameOrig, GRAPH_HREF_START + nameNew ); 1184 1185 // グラフオブジェクトの画像イメージをコピー(./ObjectReplacements/Object X ⇒ ./ObjectReplacements/Object X_n) 1186 // ※実体はコピーしない(リンクの参照を無効にしておくことで、次回起動時にグラフの再描画が行われる) 1187 graphTag = graphTag.replace( GRAPH_HREF_START + GRAPH_OBJREPL + "/" + nameOrig, GRAPH_HREF_START + GRAPH_OBJREPL + "/" + nameNew ); 1188 1189 // OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する 1190 addObjMap.put( nameNew, "graph" ); 1191 1192 // グラフオブジェクトの定義ファイルに記述されている定義ファイルをパースし、シート名と{@XXXX}を置き換え 1193 parseGraphContent( path + nameNew + File.separator + "content.xml", sheetOrig, sheetNew ); 1194 1195 // グラフの参照範囲のシート名を置き換え 1196 final String range = TagParser.getValueFromTag( str, GRAPH_UPDATE_RANGE_START, GRAPH_UPDATE_RANGE_END ); 1197 graphTag = graphTag.replace( GRAPH_UPDATE_RANGE_START + range, GRAPH_UPDATE_RANGE_START + range.replace( sheetOrig, sheetNew ) ); 1198 } 1199 } 1200 1201 buf.append( graphTag ); 1202 } 1203 }.doParse( row, GRAPH_START_TAG, GRAPH_END_TAG ); 1204 1205// return rowStr; 1206 } 1207 1208 /** 1209 * グラフデータのcontent.xmlをパースします。 1210 * 1211 * @og.rev 5.1.8.0 (2010/07/01) 新規作成 1212 * 1213 * @param fileName ファイル名 1214 * @param sheetOrig 元シート 1215 * @param sheetNew 新シート 1216 */ 1217 private void parseGraphContent( final String fileName, final String sheetOrig, final String sheetNew ) { 1218 String graphContent = readOOoXml( fileName ); 1219 1220 // シート名の置き換え 1221 if( graphContent.indexOf( GRAPH_CONTENT_START ) >= 0 ) { 1222 graphContent = new TagParser() { 1223 /** 1224 * 開始タグから終了タグまでの文字列の処理を定義します。 1225 * 1226 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません) 1227 * サブクラスでオーバーライドして実際の処理を実装して下さい。 1228 * 1229 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 1230 * @param buf 出力を行う文字列バッファ 1231 * @param offset 終了タグのオフセット(ここでは使っていません) 1232 */ 1233 @Override 1234 protected void exec( final String str, final StringBuilder buf, final int offset ) { 1235 buf.append( str.replace( sheetOrig, sheetNew ) ); 1236 } 1237 }.doParse( graphContent, GRAPH_CONTENT_START, GRAPH_CONTENT_END ); 1238 } 1239 1240 // {@XXXX}の置き換え 1241 if( graphContent.indexOf( VAR_START ) >= 0 ) { 1242 graphContent = new TagParser() { 1243 /** 1244 * 開始タグから終了タグまでの文字列の処理を定義します。 1245 * 1246 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません) 1247 * サブクラスでオーバーライドして実際の処理を実装して下さい。 1248 * 1249 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 1250 * @param buf 出力を行う文字列バッファ 1251 * @param offset 終了タグのオフセット(ここでは使っていません) 1252 */ 1253 @Override 1254 public void exec( final String str, final StringBuilder buf, final int offset ) { 1255 buf.append( getHeaderFooterValue( str ) ); 1256 } 1257 }.doParse( graphContent, VAR_START, VAR_END, false ); 1258 } 1259 1260 writeOOoXml( fileName, graphContent ); 1261 } 1262 1263 /** 1264 * 指定されたキーの値を返します。 1265 * 1266 * @og.rev 4.3.0.0 (2008/07/18) アンダースコアの処理変更 1267 * @og.rev 4.3.5.0 (2008/02/01) カラム名と行番号文字の位置は最後から検索する 4.3.3.4 (2008/11/01) 修正分 1268 * 1269 * @param key キー 1270 * 1271 * @return 値 1272 */ 1273 private String getValue( final String key ) { 1274 final int conOffset = key.lastIndexOf( VAR_CON ); 1275 1276 String value = null; 1277 1278 if( conOffset < 0 ) { 1279 value = getHeaderFooterValue( key ); 1280 } 1281 else { 1282 final String name = key.substring( 0, conOffset ); 1283 int rownum = -1; 1284 try { 1285 rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) ) + currentBaseRow; // 6.0.2.4 (2014/10/17) メソッド間違い 1286 } 1287 catch( final NumberFormatException ex ) { 1288 // 4.3.0.0 (2008/07/18) エラーが起きてもなにもしない。 1289 // queue.addMsg( "[ERROR]雛形の変数定義が誤っています。カラム名=" + name + CR ); 1290 // throw new Exception( ex ); 1291 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks 1292 final String errMsg = "雛形の変数定義で、行番号文字が取得できません。カラム名=[" + key + "]" + CR + ex.getMessage() ; 1293 System.err.println( errMsg ); 1294 } 1295 1296 // 4.3.0.0 (2008/07/18) アンダースコア後が数字に変換できない場合はヘッダフッタとして認識 1297 if( rownum < 0 ){ 1298 value = getHeaderFooterValue( key ); 1299 } 1300 else{ 1301 value = getBodyValue( name, rownum ); 1302 } 1303 } 1304 1305 return checkValue( value ); 1306 } 1307 1308 /** 1309 * 指定されたキーのヘッダー、フッター値を返します。 1310 * 1311 * @og.rev 4.3.6.0 (2009/04/01) レンデラー適用されていないバグを修正 1312 * @og.rev 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。 1313 * @og.rev 5.1.6.0 (2010/05/01) ページNO出力対応 1314 * @og.rev 6.1.1.0 (2015/01/17) getRendererValue の代わりに、getWriteValue を使うように変更。 1315 * @og.rev 6.1.1.0 (2015/01/17) 内部ロジックの見直し。queue.getFooter(),queue.getHeader() を、ローカル変数で定義。 1316 * 1317 * @param key キー 1318 * 1319 * @return 値 1320 */ 1321 private String getHeaderFooterValue( final String key ) { 1322 String value = ""; 1323 1324 // 5.1.6.0 (2010/05/01) ページNO出力対応 1325 if( PAGE_NO.equals( key ) ) { 1326 value = String.valueOf( pages + 1 ); 1327 } 1328 // 6.1.1.0 (2015/01/17) 内部ロジックの見直し。queue.getFooter(),queue.getHeader() を、ローカル変数で定義。 1329 else { 1330 // 最後の行かオーバーフロー時はフッター。最後の行にきていない場合はヘッダー 1331 final DBTableModel headerFooterModel = status >= LASTROW ? queue.getFooter() : queue.getHeader() ; 1332 1333 if( headerFooterModel != null ) { 1334 final int clmno = headerFooterModel.getColumnNo( key, false ); 1335 if( clmno >= 0 ) { 1336 value = headerFooterModel.getValue( 0, clmno ); 1337 // 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。 1338 if( queue.isFglocal() ) { 1339 // 4.3.6.0 (2009/04/01) 1340 // 6.1.1.0 (2015/01/17) getRendererValue の代わりに、getWriteValue を使うように変更。 1341 value = headerFooterModel.getDBColumn( clmno ).getWriteValue( value ); 1342 } 1343 } 1344 } 1345 } 1346 1347 return value; 1348 } 1349 1350 /** 1351 * 指定された行番号、キーのボディー値を返します。 1352 * 1353 * @og.rev 4.3.6.0 (2009/04/01) レンデラー適用されていないバグを修正 1354 * @og.rev 4.3.6.2 (2009/04/15) 行番号のより小さいカラム定義を読んだ際に、内部カウンタがクリアされてしまうバグを修正 1355 * @og.rev 4.3.6.2 (2009/04/15) 一度オーバーフローした場合に移行が全て空文字で返ってしまうバグを修正 1356 * @og.rev 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。 1357 * @og.rev 5.1.6.0 (2010/05/01) 行番号出力対応 1358 * @og.rev 5.1.7.0 (2010/06/01) 複数シート対応 1359 * @og.rev 5.1.9.0 (2010/08/01) 最終行で正しくシートブレイクされないバグを修正 1360 * @og.rev 6.1.1.0 (2015/01/17) getRendererValue の代わりに、getWriteValue を使うように変更。 1361 * @og.rev 6.1.1.0 (2015/01/17) 内部ロジックの見直し。queue.getBody() を、ローカル変数で定義他。 1362 * 1363 * @param key キー 1364 * @param rownum 行番号 1365 * 1366 * @return キーのボディー値 1367 * @og.rtnNotNull 1368 */ 1369 private String getBodyValue( final String key, final int rownum ) { 1370 // if( status == OVERFLOW || isPageBreak ) { return ""; } 1371 if( isPageBreak ) { return ""; } // 4.3.6.2 (2009/04/15) OVERFLOW時バグ修正 1372 1373 final DBTableModel bodyModel = queue.getBody(); // 6.1.1.0 (2015/01/17) 1374 1375 final int clmno = bodyModel.getColumnNo( key, false ); // 6.1.1.0 (2015/01/17) 1376 if( clmno < 0 && !ROW_NO.equals( key ) ) { return ""; } 1377 1378 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point. 1379 final int rowCount = bodyModel.getRowCount(); // 6.1.1.0 (2015/01/17) 1380 // ページブレイク判定、先読みして判断 1381 if( PAGE_BREAK.equals( key ) ) { 1382 // 6.9.7.0 (2018/05/14) PMD These nested if statements could be combined 1383// if( rownum < rowCount - 1 ) { // 6.1.1.0 (2015/01/17) 1384// if( !( bodyModel.getValue( rownum, clmno ).equals( bodyModel.getValue( rownum + 1, clmno ) ) ) ) { 1385 if( rownum < rowCount - 1 // 6.1.1.0 (2015/01/17) 1386 && !( bodyModel.getValue( rownum, clmno ).equals( bodyModel.getValue( rownum + 1, clmno ) ) ) ) { 1387 isPageBreak = true; 1388// } 1389 } 1390 return ""; 1391 } 1392 1393 // 5.1.7.0 (2010/06/01) 複数シート対応 1394 // シートブレイクは後読みして判断(前の行と異なっていた場合にブレイク) 1395 if( sheetBreakClm >= 0 ) { 1396 // 5.1.9.0 (2010/08/01) 最終行で正しくシートブレイクされないバグを修正 1397 // 6.9.7.0 (2018/05/14) PMD These nested if statements could be combined 1398// if( rownum < rowCount && currentBaseRow != rownum ) { 1399// if( !( bodyModel.getValue( currentBaseRow, sheetBreakClm ).equals( bodyModel.getValue( rownum, sheetBreakClm ) ) ) ) { 1400 if( rownum < rowCount && currentBaseRow != rownum 1401 && !( bodyModel.getValue( currentBaseRow, sheetBreakClm ).equals( bodyModel.getValue( rownum, sheetBreakClm ) ) ) ) { 1402 isPageBreak = true; 1403 return ""; 1404// } 1405 } 1406 } 1407 1408 if( rownum >= rowCount ) { // 6.1.1.0 (2015/01/17) 1409 status = OVERFLOW; 1410 return ""; 1411 } 1412 1413 if( rownum == rowCount - 1 ) { // 6.1.1.0 (2015/01/17) 1414 // status = LASTROW; 1415 status = Math.max( LASTROW, status ); // 4.3.6.2 (2009/04/15) 自身のステータスと比べて大きい方を返す 1416 } 1417 1418 String value = null; 1419 // 5.1.6.0 (2010/05/01) ページNO出力対応 1420 if( ROW_NO.equals( key ) ) { 1421 value = String.valueOf( rownum + 1 ); 1422 } 1423 else { 1424 value = bodyModel.getValue( rownum, clmno ); // 6.1.1.0 (2015/01/17) 1425 // 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。 1426 if( queue.isFglocal() ) { 1427 // 4.3.6.0 (2009/04/01) 1428 // 6.1.1.0 (2015/01/17) getRendererValue の代わりに、getWriteValue を使うように変更。 1429 value = bodyModel.getDBColumn( clmno ).getWriteValue( value ); // 6.1.1.0 (2015/01/17) 1430 } 1431 } 1432 1433 // 4.3.6.2 (2009/04/15) 1434 if( currentMaxRow < rownum + 1 ) { 1435 currentMaxRow = rownum + 1; 1436 } 1437 1438 return value; 1439 } 1440 1441 /** 1442 * 値に'<'や'>','&'が含まれていた場合にメタ文字に変換します。 1443 * 1444 * @og.rev 5.0.2.0 (2009/11/01) 改行Cの変換ロジックを追加 1445 * @og.rev 5.0.2.0 (2009/11/01) リソース変換時のspanタグを除去 1446 * 1447 * @param value 変換前の値 1448 * 1449 * @return 変換後の値 1450 * @og.rtnNotNull 1451 */ 1452 private String checkValue( final String value ) { 1453 String rtn = value; 1454 1455 // 5.0.2.0 (2009/11/01) 1456 if( queue.isFglocal() ) { 1457 // 6.0.2.5 (2014/10/31) refactoring 1458 final int idx = rtn.indexOf( "<span" ); 1459 if( idx >= 0 ) { 1460 final String spanStart = rtn.substring( idx, rtn.indexOf( '>', idx ) + 1 ); 1461 rtn = rtn.replace( spanStart, "" ).replace( "</span>", "" ); 1462 } 1463 } 1464 1465 if( rtn.indexOf( '&' ) >= 0 ) { 1466 rtn = rtn.replace( "&", "&" ); 1467 } 1468 if( rtn.indexOf( '<' ) >= 0 ) { 1469 rtn = rtn.replace( "<", "<" ); 1470 } 1471 if( rtn.indexOf( '>' ) >= 0 ) { 1472 rtn = rtn.replace( ">", ">" ); 1473 } 1474 if( rtn.indexOf( '\n' ) >= 0 ) { 1475 rtn = rtn.replace( "\r\n", "\n" ).replace( "\n", OOO_CR ); 1476 } 1477 1478 return rtn; 1479 } 1480 1481 /** 1482 * 引数の文字列を指定された開始タグ、終了タグで解析し配列として返します。 1483 * 開始タグより前の文字列は0番目に、終了タグより後の文字列は1番目に格納されます。 1484 * 2番目以降に、開始タグ、終了タグの部分が格納されます。 1485 * 1486 * @param str 文字列 1487 * @param startTag 開始タグ 1488 * @param endTag 終了タグ 1489 * 1490 * @return 解析結果の配列 1491 * @og.rtnNotNull 1492 */ 1493 private static String[] tag2Array( final String str, final String startTag, final String endTag ) { 1494 String header = null; 1495 String footer = null; 1496 final List<String> body = new ArrayList<>(); 1497 1498 int preOffset = -1; 1499 int curOffset = 0; 1500 1501 while( true ) { 1502 curOffset = str.indexOf( startTag, preOffset + 1 ); 1503 if( curOffset < 0 ) { 1504 curOffset = str.lastIndexOf( endTag ) + endTag.length(); 1505 body.add( str.substring( preOffset, curOffset ) ); 1506 1507 footer = str.substring( curOffset ); 1508 break; 1509 } 1510 else if( preOffset == -1 ) { 1511 header = str.substring( 0, curOffset ); 1512 } 1513 else { 1514 body.add( str.substring( preOffset, curOffset ) ); 1515 } 1516 preOffset = curOffset; 1517 } 1518 1519 String[] arr = new String[body.size()+2]; 1520 arr[0] = header; 1521 arr[1] = footer; 1522 for( int i=0; i<body.size(); i++ ) { 1523 arr[i+2] = body.get(i); 1524 } 1525 1526 return arr; 1527 } 1528 1529 /** 1530 * 帳票処理キューを元に、style.xml(ヘッダー、フッター)を書き換えます。(2) 1531 * 1532 * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更 1533 */ 1534 private void execStyles() { 1535 final String fileName = path + "styles.xml"; 1536 String content = readOOoXml( fileName ); 1537 1538 if( content.indexOf( VAR_START ) < 0 ) { return; } 1539 1540 content = new TagParser() { 1541 /** 1542 * 開始タグから終了タグまでの文字列の処理を定義します。 1543 * 1544 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません) 1545 * サブクラスでオーバーライドして実際の処理を実装して下さい。 1546 * 1547 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む) 1548 * @param buf 出力を行う文字列バッファ 1549 * @param offset 終了タグのオフセット(ここでは使っていません) 1550 */ 1551 @Override 1552 public void exec( final String str, final StringBuilder buf, final int offset ) { 1553 buf.append( getHeaderFooterValue( str ) ); 1554 } 1555 }.doParse( readOOoXml( fileName ), VAR_START, VAR_END, false ); 1556 1557 writeOOoXml( fileName, content ); 1558 } 1559 1560 /** 1561 * 帳票処理キューを元に、meta.xmlを書き換えます。(6) 1562 * 1563 * @og.rev 5.1.6.0 (2010/05/01) 画面帳票作成機能対応(API経由では出力されないことがある) 1564 */ 1565 private void execMeta() { 1566 final String fileName = path + "meta.xml"; 1567 1568 String meta = readOOoXml( fileName ); 1569 1570 // シート数書き換え 1571 // 5.1.6.0 (2010/05/01) 1572 if( meta.indexOf( TABLE_COUNT_START_TAG ) >=0 ){ 1573 final String tableCount = TagParser.getValueFromTag( meta, TABLE_COUNT_START_TAG, TABLE_COUNT_END_TAG ); 1574 meta = meta.replace( TABLE_COUNT_START_TAG + tableCount, TABLE_COUNT_START_TAG + pages ); 1575 } 1576 1577 // セル数書き換え 1578 // 5.1.6.0 (2010/05/01) 1579 if( meta.indexOf( CELL_COUNT_START_TAG ) >=0 ){ 1580 final String cellCount = TagParser.getValueFromTag( meta, CELL_COUNT_START_TAG, CELL_COUNT_END_TAG ); 1581 meta = meta.replace( CELL_COUNT_START_TAG + cellCount, CELL_COUNT_START_TAG + ( Integer.parseInt( cellCount ) * pages ) ); 1582 } 1583 1584 // オブジェクト数書き換え 1585 // 5.1.6.0 (2010/05/01) 1586 if( meta.indexOf( OBJECT_COUNT_START_TAG ) >= 0 ){ 1587 final String objectCount = TagParser.getValueFromTag( meta, OBJECT_COUNT_START_TAG, OBJECT_COUNT_END_TAG ); 1588 //4.2.4.0 (2008/06/02) 存在しない場合はnullで帰ってくるので無視する 1589 if( objectCount != null){ 1590 meta = meta.replace( OBJECT_COUNT_START_TAG + objectCount, OBJECT_COUNT_START_TAG + ( Integer.parseInt( objectCount ) * pages ) ); 1591 } 1592 } 1593 1594 writeOOoXml( fileName, meta ); 1595 } 1596 1597 /** 1598 * 書き換え対象のスタイルリストを取得します。 1599 * 1600 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 1601 * 1602 * @param header content.xmlのヘッダー 1603 */ 1604 private void getRepStyleList( final String header ) { 1605 final String[] tags = tag2Array( header, STYLE_START_TAG, STYLE_END_TAG ); 1606 final Set<String> origNameSet = pageNameMap.keySet(); 1607 for( int i=2; i<tags.length; i++ ) { 1608 for( final String origName : origNameSet ) { 1609 if( tags[i].indexOf( "=\"" + origName + "." ) >= 0 ) { 1610 final String styleName = TagParser.getValueFromTag( tags[i], STYLE_NAME_START_TAG, STYLE_NAME_END_TAG ); 1611 repStyleList.add( styleName ); 1612 break; 1613 } 1614 } 1615 } 1616 } 1617 1618 /** 1619 * 帳票処理キューを元に、content.xmlを書き換えます。(4) 1620 * まず、XMLを一旦メモリ上に展開した後、シート単位に分解し、データの埋め込みを行います。 1621 * 1622 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 1623 */ 1624 private void execContentHeader() { 1625 final String fileName = path + "content.xml"; 1626 final String content = readOOoXml( fileName ); 1627 1628 // ファイルの解析し、シート+行単位に分解 1629 final String[] tags = tag2Array( content, STYLE_START_TAG, STYLE_END_TAG ); 1630 final String header = tags[0]; 1631 final String footer = tags[1]; 1632 1633 BufferedWriter bw = null; 1634 try { 1635 bw = getWriter( fileName ); 1636 bw.write( xmlHeader ); 1637 bw.write( '\n' ); 1638 bw.write( header ); 1639 1640 // スタイル情報にシート依存の情報がある場合は、ページ分だけコピーする。 1641 // 6.3.9.0 (2015/11/06) entrySet イテレータではなく効率が悪い keySet イテレータを使用している 1642 for( int i=2; i<tags.length; i++ ) { 1643 boolean isReplace = false; 1644 for( final Map.Entry<String,List<String>> entry : pageNameMap.entrySet() ) { 1645 final String origName = entry.getKey(); 1646 if( tags[i].indexOf( "=\"" + origName + "." ) >= 0 ) { 1647 for( final String newName : entry.getValue() ) { 1648 String styleStr = tags[i].replace( "=\"" + origName + "." , "=\"" + newName + "." ); 1649 // シート名の書き換え 1650 final String styleName = TagParser.getValueFromTag( styleStr, STYLE_NAME_START_TAG, STYLE_NAME_END_TAG ); 1651 styleStr = styleStr.replace( STYLE_NAME_START_TAG + styleName, STYLE_NAME_START_TAG + styleName + "_" + newName ); 1652 bw.write( styleStr ); 1653 isReplace = true; 1654 } 1655 break; 1656 } 1657 } 1658 if( !isReplace ) { 1659 bw.write( tags[i] ); 1660 } 1661 } 1662 1663 bw.write( footer ); 1664 bw.flush(); 1665 } 1666 catch( final IOException ex ) { 1667 queue.addMsg( "[ERROR]PARSE:error occurer while write ReParsed Sheet " + fileName + CR ); 1668 throw new HybsSystemException( ex ); 1669 } 1670 finally { 1671 Closer.ioClose( bw ); 1672 } 1673 } 1674 1675 /** 1676 * content.xmlのヘッダー部分を出力したcontent.xmlに、ヘッダー部分以降を出力した content.xml.bakをマージします。(5) 1677 * 1678 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応 1679 */ 1680 private void execMergeContent() { 1681 FileChannel srcChannel = null; 1682 FileChannel destChannel = null; 1683 try { 1684 srcChannel = new FileInputStream( path + "content.xml.tmp" ).getChannel(); 1685 destChannel = new FileOutputStream( path + "content.xml", true ).getChannel(); 1686 srcChannel.transferTo(0, srcChannel.size(), destChannel); 1687 } 1688 catch( final IOException ex ) { 1689 queue.addMsg( "[ERROR]PARSE:error occurer while merge content.xml" + CR ); 1690 throw new HybsSystemException( ex ); 1691 } 1692 finally { 1693 Closer.ioClose( srcChannel ); 1694 Closer.ioClose( destChannel ); 1695 } 1696 FileUtil.deleteFiles( new File( path + "content.xml.tmp" ) ); 1697 } 1698 1699 /** 1700 * META-INF/manifest.xmlに、追加したオブジェクト(グラフ、画像)を登録します。(7) 1701 * 1702 * @og.rev 5.3.1.0 (2011/12/01) 新規作成 1703 */ 1704 private void execManifest() { 1705 final String fileName = path + "META-INF" + File.separator + "manifest.xml"; 1706 final String[] conArr = TagParser.tag2Array( readOOoXml( fileName ), MANIFEST_START_TAG, MANIFEST_END_TAG ); 1707 1708 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 1709 buf.append( conArr[0] ); 1710 for( int i=2; i<conArr.length; i++ ) { 1711 buf.append( conArr[i] ); 1712 } 1713 for( final Map.Entry<String,String> entry : addObjMap.entrySet() ) { 1714 if( "graph".equals( entry.getValue() ) ) { 1715 buf.append( "<manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" ) 1716 .append( entry.getKey() ) 1717 .append( "/content.xml\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" ) 1718 .append( entry.getKey() ) 1719 .append( "/styles.xml\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" ) 1720 .append( entry.getKey() ) 1721 .append( "/meta.xml\"/><manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.chart\" manifest:full-path=\"" ) 1722 .append( entry.getKey() ).append( "/\"/>" ); // XML なので、このまま。 1723 } 1724 else { 1725 buf.append( "<manifest:file-entry manifest:media-type=\"image/" ) 1726 .append( entry.getValue() ).append( "\" manifest:full-path=\"" ) 1727 .append( entry.getKey() ).append( "\"/>" ); // XML なので、このまま。 1728 } 1729 } 1730 buf.append( conArr[1] ); 1731 1732 writeOOoXml( fileName, buf.toString() ); 1733 } 1734 1735 /** 1736 * XMLファイルを読み取り、結果を返します。 1737 * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、 1738 * ここでは、2行目の内容部分を返します。 1739 * 1740 * @og.rev 4.3.6.0 (2009/04/01) meta.xmlでコンテンツの部分が改行されている場合があるため、ループを回して読込み 1741 * @og.rev 6.2.0.0 (2015/02/27) new BufferedReader … を、FileUtil.getBufferedReader … に変更。 1742 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 1743 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 1744 * 1745 * @param fileName ファイル名 1746 * 1747 * @return 読み取った文字列 1748 * @og.rtnNotNull 1749 */ 1750 private String readOOoXml( final String fileName ) { 1751 final File file = new File ( fileName ); 1752 1753 BufferedReader br = null; 1754 String tmp = null; 1755 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 1756 try { 1757 br = FileUtil.getBufferedReader( file, "UTF-8" ); // 6.2.0.0 (2015/02/27) 1758 xmlHeader = br.readLine(); 1759 while( ( tmp = br.readLine() ) != null ) { // 4.3.6.0 (2009/04/01) 1760 buf.append( tmp ); 1761 } 1762 } 1763 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 1764 catch( final CharacterCodingException ex ) { 1765 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 1766 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 1767 + " [" + fileName + "] , Encode=[UTF-8]" ; 1768 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 1769 } 1770 catch( final IOException ex ) { 1771 queue.addMsg( "[ERROR]PARSE:Failed to read " + fileName + CR ); 1772 throw new HybsSystemException( ex ); 1773 } 1774 finally { 1775 Closer.ioClose( br ); 1776 } 1777 1778 final String str = buf.toString(); 1779 if( xmlHeader == null || xmlHeader.isEmpty() || str == null || str.isEmpty() ) { 1780 queue.addMsg( "[ERROR]PARSE:Maybe " + fileName + " is Broken!" + CR ); 1781 throw new HybsSystemException(); 1782 } 1783 1784 return str; 1785 } 1786 1787 /** 1788 * XMLファイルを書き込みます。 1789 * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、 1790 * ここでは、2行目の内容部分を渡すことで、XMLファイルを作成します。 1791 * 1792 * @param fileName 書き込むXMLファイル名 1793 * @param str 書き込む文字列 1794 */ 1795 private void writeOOoXml( final String fileName, final String str ) { 1796 BufferedWriter bw = null; 1797 try { 1798 bw = getWriter( fileName ); 1799 bw.write( xmlHeader ); 1800 bw.write( '\n' ); 1801 bw.write( str ); 1802 bw.flush(); 1803 } 1804 catch( final IOException ex ) { 1805 queue.addMsg( "[ERROR]PARSE:Failed to write " + fileName + CR ); 1806 throw new HybsSystemException( ex ); 1807 } 1808 finally { 1809 Closer.ioClose( bw ); 1810 } 1811 } 1812 1813 /** 1814 * XMLファイル書き込み用のライターを返します。 1815 * 1816 * @param fileName ファイル名 1817 * 1818 * @return ライター 1819 * @og.rtnNotNull 1820 */ 1821 private BufferedWriter getWriter( final String fileName ) { 1822 return getWriter( fileName, false ); 1823 } 1824 1825 /** 1826 * XMLファイル書き込み用のライターを返します。 1827 * 1828 * @param fileName ファイル名 1829 * @param append アベンドするか 1830 * 1831 * @return ライター 1832 * @og.rtnNotNull 1833 */ 1834 private BufferedWriter getWriter( final String fileName, final boolean append ) { 1835 final File file = new File ( fileName ); 1836 BufferedWriter bw; 1837 try { 1838 bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file, append ), "UTF-8" ) ); 1839 } 1840 catch( final UnsupportedEncodingException ex ) { 1841 queue.addMsg( "[ERROR] Input File is written by Unsupported Encoding" ); 1842 throw new HybsSystemException( ex ); 1843 } 1844 catch( final FileNotFoundException ex ) { 1845 queue.addMsg( "[ERROR] File not Found" ); 1846 throw new HybsSystemException( ex ); 1847 } 1848 return bw; 1849 } 1850 1851 /** 1852 * ファイル名から拡張子(小文字)を求めます。 1853 * 1854 * @param fileName 拡張子を取得する為のファイル名 1855 * 1856 * @return 拡張子(小文字) 1857 */ 1858 public static String getSuffix( final String fileName ) { 1859 String suffix = null; 1860 if( fileName != null ) { 1861 final int sufIdx = fileName.lastIndexOf( '.' ); 1862 if( sufIdx >= 0 ) { 1863 suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN ); 1864 } 1865 } 1866 return suffix; 1867 } 1868}