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