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