2 * Copyright (c) 2009 The openGion Project.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
16 package org.opengion.hayabusa.report2;
18 import java.io.BufferedReader;
19 import java.io.BufferedWriter;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.io.UnsupportedEncodingException;
27 import java.nio.channels.FileChannel;
28 import java.nio.charset.CharacterCodingException; // 6.3.1.0 (2015/06/28)
29 import java.util.List;
30 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04)
34 import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring
35 import java.util.Locale;
38 import org.opengion.fukurou.system.OgCharacterException; // 6.5.0.1 (2016/10/21)
39 import org.opengion.fukurou.model.NativeType;
40 import org.opengion.fukurou.util.StringUtil; // 6.2.0.0 (2015/02/27)
41 import org.opengion.fukurou.system.Closer;
42 import org.opengion.fukurou.util.FileUtil;
43 import org.opengion.fukurou.util.QrcodeImage;
44 import org.opengion.hayabusa.common.HybsSystem;
45 import org.opengion.hayabusa.common.HybsSystemException;
46 import org.opengion.hayabusa.db.DBTableModel; // 6.1.1.0 (2015/01/17)
47 import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26)
48 import static org.opengion.fukurou.system.HybsConst.FS; // 8.0.3.0 (2021/12/17)
49 import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring
51 import org.opengion.hayabusa.report2.TagParser.SplitKey; // 8.0.3.0 (2021/12/17)
54 * 指定されたパスに存在するODSの各XMLファイルをパースし、帳票定義及び
56 * 書き換えは読み取り先と同じファイルであるため、一旦読み取った各XMLを
57 * メモリ上に格納したからパース後のXMLファイルの書き込みを行います。
59 * パース対象となるファイルは以下の3つです。
60 * content.xml シートの中身を定義
62 * style.xml 帳票ヘッダーフッターを定義
64 * content.xmlのパース処理として、まずxmlファイルをシート+行単位に分解します。
65 * その後、分解された行毎に帳票データを埋め込み、出力先のXMLに書き込みを行います。
68 * また、Calcの特性として、関数の引数に不正な引数が指定された場合、(Text関数の
69 * 引数にnullが指定された場合等)、エラー:XXXという文字が表示されます。
70 * ここでは、これを回避するため、全ての関数にisError関数を埋め込み、エラー表示を
76 * @author Hiroki.Nakamura
79 class OdsContentParser {
81 //======== content.xmlのパースで使用 ========================================
83 private static final String BODY_START_TAG = "<table:table ";
84 private static final String BODY_END_TAG = "</table:table>";
87 private static final String ROW_START_TAG = "<table:table-row ";
89 /* ページエンドカットの際に、行を非表示にするためのテーブル宣言 */
90 private static final String ROW_START_TAG_INVISIBLE = "<table:table-row table:visibility=\"collapse\" ";
93 private static final String TABLE_CELL_START_TAG = "<table:table-cell";
94 private static final String TABLE_CELL_END_TAG = "</table:table-cell>";
96 /* シート名を取得するための開始終了文字 */
97 private static final String SHEET_NAME_START = "table:name=\"";
99 /* オブジェクトの終了位置(シート名)を見つけるための開始文字 */
100 private static final String OBJECT_SEARCH_STR = "table:end-cell-address=\"";
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)
109 private static final String FIRST_PAGE_NAME = "FIRST";
111 /* シートブレイク用のキー 5.1.7.0 (2010/06/01) */
112 private static final String SHEET_BREAK = "SHEETBREAK";
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) '_' で、キーと行番号の分離を、インナークラス化します。
119 /* ページエンドカットのカラム文字列 */
120 private static final String PAGE_END_CUT = "PAGEENDCUT";
122 /* ページブレイクのカラム文字列 */
123 private static final String PAGE_BREAK = "PAGEBREAK";
125 /* ページ番号出力用文字列 5.1.6.0 (2010/05/01) */
126 private static final String PAGE_NO= "PAGENO";
128 /* 行番号出力用文字列 5.1.6.0 (2010/05/01) */
129 private static final String ROW_NO= "ROWNO";
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 = "\"";
136 /* 画像ファイルを保存するためのパス */
137 private static final String IMG_DIR = "Pictures";
139 /* QRコードを処理するためのカラム名 */
140 private static final String QRCODE_PREFIX = "QRCODE.";
142 /* 作成したQRコードのフォルダ名及び拡張子 */
143 private static final String QRCODE_FILETYPE = ".png";
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コード作成時のテキストのエンコード指定
155 /* 4.3.3.5 (2008/11/08) 動的に画像を入れ替えるためのパスを記述するカラム名 */
156 private static final String IMG_PREFIX = "IMG.";
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 = ")\" ";
163 /* セル内の改行を定義する文字列 5.0.2.0 (2009/11/01) */
164 private static final String OOO_CR = "</text:p><text:p>";
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>";
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 = "\"";
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";
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 = "\"";
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 なので、このまま。
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 = "\"";
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>";
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 = ">"; // アノテーションの場合の置き換えを
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>";
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:";
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>";
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 = "\"";
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)
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=\""; // 一時的にコメント化
228 //===========================================================================
230 //======== meta.xmlのパースで使用 ===========================================
232 private static final String TABLE_COUNT_START_TAG = "meta:table-count=\"";
233 // private static final String TABLE_COUNT_END_TAG = "\"";
236 private static final String CELL_COUNT_START_TAG = "meta:cell-count=\"";
237 // private static final String CELL_COUNT_END_TAG = "\"";
240 private static final String OBJECT_COUNT_START_TAG = "meta:object-count=\"";
241 // private static final String OBJECT_COUNT_END_TAG = "\"";
242 //===========================================================================
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;
256 * 各雛形ファイルを処理する際の基準となる行数
257 * 初期>0 2行({@XXX_1}まで)処理後>2 ・・・
258 * 各雛形で定義されている行番号 + [baseRow] の値がDBTableModel上の行番号に相当する
259 * currentMaxRowは各シート処理後の[baseRow]と同じ
261 private int currentBaseRow ;
262 private int currentMaxRow ;
267 /* 処理行がページエンドカットの対象かどうか */
268 private boolean isPageEndCut ; // 4.3.1.1 (2008/08/23) ローカル変数化
270 /* ページブレイクの処理中かどうか */
271 private boolean isPageBreak ;
273 /* XML宣言の文字列。各XMLで共通なのでクラス変数として定義 */
274 private String xmlHeader ;
276 /* シートブレイク対象かどうか 5.1.7.0 (2010/06/01) */
277 private int sheetBreakClm = -1;
279 /* シート名カラム 5.7.6.2 (2014/05/16) */
280 private int sheetNameClm = -1; // 今は、ページブレイクカラムと同じカラムを使用しています。
282 /* シートのヘッダー部分の再パースを行うかどうか 5.2.2.0 (2010/11/01) */
283 private boolean isNeedsReparse ;
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<>();
289 /* ページ名に依存しているスタイル名称のリスト 5.2.2.0 (2010/11/01) */
290 private final List<String> repStyleList = new ArrayList<>();
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<>();
296 private final ExecQueue queue;
297 private final String path;
299 private final boolean useChangeType ; // 6.8.3.1 (2017/12/01)
301 // /* 8.0.3.0 (2021/12/17) ods→xlsx変換時のシート毎の行数 */
302 // 【保留】private final List<Integer> sheetRows = new ArrayList<>();
307 * @og.rev 5.1.2.0 (2010/01/01) 処理した行数をQueueオブジェクトから取得(シート数が256を超えた場合の対応)
308 * @og.rev 6.8.3.1 (2017/12/01) ローカルリソースの文字型⇒数値型変換の処理の有無
310 * @param qu ExecQueueオブジェクト
313 OdsContentParser( final ExecQueue qu, final String pt ) {
317 currentBaseRow = queue.getExecRowCnt();
318 useChangeType = !queue.isFglocal() || HybsSystem.sysBool( "REPORT_USE_CHANGETYPE" ); // 6.8.3.1 (2017/12/01)
324 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
325 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
330 * OOoではページ毎にヘッダーフッターが設定できないよう。
336 execContent(); // (3)
338 /* ヘッダー部分にシート情報がある場合に書き換え */
339 if( isNeedsReparse ) {
340 /* ヘッダーファイルの再パース */
341 execContentHeader(); // (4)
342 /* ヘッダーファイルとそれ以降のファイルの連結 */
343 execMergeContent(); // (5)
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)
357 // * 【保留】シート毎の行数をリストで返します。
359 // * @og.rev 8.0.3.0 (2021/12/17) ods→xlsx変換時のシート毎の行数
361 // * @return シート毎の行数リスト
363 // public List<Integer> getSheetRowsList() {
368 * 帳票処理キューを元に、content.xmlを書き換えます。(3)
369 * まず、XMLを一旦メモリ上に展開した後、シート単位に分解し、データの埋め込みを行います。
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対応(数字が文字扱いの対応)
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 ); //
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];
395 // LibreOffice対応 8.1.2.1 (2022/03/25)
396 final int calcSt = contentHeader.indexOf( XMLNS_CALCEXT );
398 final int calcEd = contentHeader.indexOf( "\"", calcSt + XMLNS_CALCEXT.length() );
399 contentHeader = contentHeader.replace( contentHeader.substring( calcSt - 1, calcEd + 1 ) , "" );
402 BufferedWriter bw = null;
404 bw = getWriter( fileName );
405 bw.write( xmlHeader );
407 bw.write( contentHeader );
410 catch( final IOException ex ) {
411 queue.addMsg( "[ERROR]PARSE:error occurer while content.xml(header) " + fileName );
412 throw new HybsSystemException( ex );
415 Closer.ioClose( bw );
419 final String contentFooter = tags[1];
421 final List<OdsSheet> firstSheets = new ArrayList<>();
422 final Map<String, OdsSheet> sheetMap = new HashMap<>();
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)
427 OdsSheet defaultSheet = null;
428 for( int i=2; i<tags.length; i++ ) {
429 final OdsSheet sheet = new OdsSheet();
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 );
441 sheetMap.put( sheetName, sheet );
442 // 一番初めに見つかった表紙以外のシートをデフォルトシートとして設定
443 if( defaultSheet == null ) {
444 defaultSheet = sheet;
448 // 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。
449 final String orgShtName = sheet.getOrigSheetName();
451 // 5.2.2.0 (2010/11/01) 条件付書式対応
452 if( !isNeedsReparse && contentHeader.indexOf( "=\"" + orgShtName + "." ) >= 0 ) {
453 isNeedsReparse = true;
456 // 5.2.2.0 (2010/11/01) 条件付書式対応
457 pageNameMap.put( orgShtName, new ArrayList<>() );
462 // 5.2.2.0 (2010/11/01) 条件付書式対応
463 if( isNeedsReparse ) {
464 // ヘッダーを再パースする場合は、ボディ部分を
465 // content.xml.tmpに書き出して、後でマージする
466 bw = getWriter( fileName + ".tmp" );
467 getRepStyleList( contentHeader );
470 // ヘッダーを再パースしない場合は、ボディ部分を
471 // content.xml追加モードで書き込みする
472 bw = getWriter( fileName, true );
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)
480 final int rowCount = bodyModel.getRowCount(); // 6.1.1.0 (2015/01/17)
483 if( queue.getExecPagesCnt() == 0 ) {
484 for( final OdsSheet firstSheet : firstSheets ) {
485 if( currentBaseRow >= rowCount ) { // 6.1.1.0 (2015/01/17) ループから出す。
488 writeParsedSheet( firstSheet, bw );
492 // 5.1.7.0 (2010/06/01) 複数シート対応
493 sheetBreakClm = bodyModel.getColumnNo( SHEET_BREAK, false ); // 6.1.1.0 (2015/01/17)
495 // 5.7.6.3 (2014/05/23) 表紙ページも、PAGEBREAKカラムの値を、シート名として使えるようにする。
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 );
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 );
513 if( sheet == null ) { sheet = defaultSheet; }
515 writeParsedSheet( sheet, bw );
518 // 5.1.2.0 (2010/01/01) 256シートを超えた場合の対応
519 queue.addExecPageCnt( pages );
520 queue.setExecRowCnt( currentBaseRow );
523 bw.write( contentFooter );
526 catch( final IOException ex ) {
527 queue.addMsg( "[ERROR]PARSE:error occurer while write Parsed Sheet " + fileName );
528 throw new HybsSystemException( ex );
531 Closer.ioClose( bw );
536 * シート単位にパースされた文書データを書き込みます
537 * 出力されるシート名には、ページ番号と基底となる行番号をセットします。
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)
551 * @param bw BufferedWriterオブジェクト
552 * @throws IOException 書き込みに失敗した場合
554 private void writeParsedSheet( final OdsSheet sheet, final BufferedWriter bw ) throws IOException {
556 String outputSheetName = null;
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;
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 );
576 // 長さ0の場合(例えば、FIRSTだけとか)は、設定しない。
577 if( sheetName.length() > 0 ) { outputSheetName = sheetName; }
582 if( outputSheetName == null ) {
583 if( sheet.getConfSheetName() == null ) {
584 outputSheetName = "Page" + ( queue.getExecPagesCnt() + pages ) + "_Row" + currentBaseRow ;
587 outputSheetName = sheet.getConfSheetName() + ( queue.getExecPagesCnt() + pages + 1 ) ;
593 // 6.4.3.3 (2016/03/04) 気になったので、sheet.getOrigSheetName() を、変数に入れておきます。
594 final String orgShtName = sheet.getOrigSheetName();
596 // シートのヘッダー部分を書き込み(シート名も書き換え)
597 String headerStr = sheet.getHeader().replace( SHEET_NAME_START + orgShtName, SHEET_NAME_START + outputSheetName );
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 );
609 // 7.3.0.1 (2021/01/22) 画像ファイルの置き方によって、ヘッダー部に {@IMG.XXX} が書かれることがある。
610 writeParsedRow( headerStr, bw, orgShtName, outputSheetName );
611 // bw.write( headerStr );
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 );
620 // // シートのボディ部分を書き込み
621 // for( final String row : sheet.getRows() ) {
622 // writeParsedRow( row, bw, orgShtName, outputSheetName );
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 );
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();
636 currentBaseRow = currentMaxRow;
638 // 8.3.0.2 (2022/08/20) 条件付書式のシート名変換対応(ただし、xmlns:calcext 定義なので、現在未使用)
640 // bw.write( sheet.getFooter() );
641 String footerStr = sheet.getFooter();
642 final String sheetNew = outputSheetName; // final 化しないと、無名クラスで使えない。
644 // シート名の置き換えの検索に、GRAPH_CONTENT_START を使っているが、"-address=" を使いたかっただけ。
645 if( footerStr.indexOf( GRAPH_CONTENT_START ) >= 0 ) {
646 footerStr = new TagParser() {
648 * 開始タグから終了タグまでの文字列の処理を定義します。
650 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません)
651 * サブクラスでオーバーライドして実際の処理を実装して下さい。
653 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
654 * @param buf 出力を行う文字列バッファ
655 * @param offset 終了タグのオフセット(ここでは使っていません)
658 protected void exec( final String str, final StringBuilder buf, final int offset ) {
659 buf.append( str.replace( orgShtName, sheetNew ) );
661 }.doParse( footerStr, GRAPH_CONTENT_START, END_KEY );
664 bw.write( footerStr );
668 // 5.2.2.0 (2010/11/01) 条件付書式対応
669 pageNameMap.get( orgShtName ).add( outputSheetName );
673 * 行単位にパースされた文書データを書き込みます。
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" 文字列を実際の改行コードに置換して設定します。
688 * @param bw BufferedWriterオブジェクト
689 * @param sheetNameOrig 元シート名
690 * @param sheetNameNew 新シート名
691 * @throws IOException 書き込みに失敗した場合
693 private void writeParsedRow( final String row, final BufferedWriter bw, final String sheetNameOrig, final String sheetNameNew ) throws IOException {
694 isPageEndCut = false;
696 String rowStr = new TagParser() {
698 * 開始タグから終了タグまでの文字列の処理を定義します。
700 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
701 * @param buf 出力を行う文字列バッファ
702 * @param offset 終了タグのオフセット(ここでは使っていません)
705 protected void exec( final String str, final StringBuilder buf, final int offset ) {
706 final String key = TagParser.checkKey( str, buf );
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();
715 // QRコードの処理、処理後はoffsetが進むため、offsetを再セット
716 if( key.startsWith( QRCODE_PREFIX ) ) {
717 setOffset( makeQRImage( row, offset, key.substring( QRCODE_PREFIX.length() ), buf ) );
719 // 画像置き換えの処理、処理後はoffsetが進むため、offsetを再セット
720 else if( key.startsWith( IMG_PREFIX ) ) {
721 setOffset( changeImage( row, offset, key.substring( IMG_PREFIX.length() ), buf ) );
723 // コメント(アノテーション)による置き換え処理、処理後はoffsetが進むため、offsetを再セット
724 else if( key.startsWith( ANNOTATION_PREFIX ) ) {
725 setOffset( parseByAnnotation( row, offset, key.substring( ANNOTATION_PREFIX.length() ), buf ) );
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 );
734 // 8.0.3.0 (2021/12/17) "\n" 文字列を実際の改行コードに置換して設定します。
735 if( val.indexOf( "\\n" ) >= 0 ) {
736 val = val.replace( "\\n" , "\n" );
741 // 処理行がページエンドカットの対象か
742 if( queue.isFgcut() && PAGE_END_CUT.equals( key ) ) {
746 }.doParse( row, VAR_START, VAR_END, false );
748 //==== ここからは後処理 =========================================================
750 * ページエンドカットの判定は最後で処理する。
751 * {@PAGEENDCUT}が行の最初に書かれている場合は、OVERFLOWになっていない可能性が
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 ) ;
764 * オブジェクトで定義されているテーブル名を変更
766 if( rowStr.indexOf( OBJECT_SEARCH_STR ) >= 0 ) {
767 rowStr = rowStr.replace( OBJECT_SEARCH_STR + sheetNameOrig, OBJECT_SEARCH_STR + sheetNameNew );
771 * 関数エラーを表示されないため、ISERROR関数を埋め込み 4.2.3.1 (2008/06/19)
773 rowStr = replaceOoocError( rowStr );
776 * グラフをシート毎にコピー 5.1.8.0 (2010/07/01)
778 rowStr = replaceGraphInfo( rowStr, sheetNameOrig, sheetNameNew );
781 * アノテーション(コメント)を削除 5.1.8.0 (2010/07/01)
782 * (コメントが存在すると起動が異常に遅くなる)
784 if( rowStr.indexOf( ANNOTATION_START_TAG ) >= 0 ) {
785 rowStr = new TagParser() {}.doParse( rowStr, ANNOTATION_START_TAG, ANNOTATION_END_TAG );
789 * 条件付書式対応 5.2.2.0 (2010/11/01)
790 * テーブル内に存在するスタイル名称を書き換え
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 ;
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 );
803 if( rowStr.indexOf( from ) >= 0 ) {
804 rowStr = rowStr.replace( from, to );
808 //==============================================================================
814 * 帳票データに応じて、カラムの属性を変更(文字型⇒数値型)に変更します。
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対応(数字が文字扱いの対応)
821 * @param curOffset オフセット
823 * @param type ネイティブタイプ
824 * @param sb StringBuilderオブジェクト
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() ) {
832 if( type != NativeType.INT && type != NativeType.LONG && type != NativeType.DOUBLE ) {
835 // 処理対象がセルでない(オブジェクト)は書き換えしない
836 if( !isCell( row, curOffset ) ) {
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 ;
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 );
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 );
868 * 引数に指定された文字列のNativeタイプを返します。
870 * リソース使用時は、各DBTypeで定義されたNativeタイプを、
871 * 未使用時は、値からNativeタイプを取得して返します。
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) アンダーバーで、キーと行番号の分離を、インナークラス化します。
879 * @return NATIVEの型の識別コード
881 * @see org.opengion.fukurou.model.NativeType
883 private NativeType getNativeType( final String key, final String val ) {
884 if( val == null || val.isEmpty() ) {
885 return NativeType.STRING;
888 NativeType type = null;
889 if( queue.isFglocal() ) {
890 // String name = key;
891 // final int conOffset = key.lastIndexOf( VAR_CON );
892 // if( conOffset >= 0 ) {
895 // rownum = Integer.parseInt( name.substring( conOffset + VAR_CON.length(), name.length() ) ); // 6.0.2.4 (2014/10/17) メソッド間違い
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 );
903 // if( rownum >= 0 ) {
904 // name = name.substring( 0, conOffset );
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 );
911 type = queue.getBody().getDBColumn( col ).getNativeType();
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;
930 * コメント(アノテーションによる置き換え処理を行います)
931 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
933 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
934 * @og.rev 6.8.3.1 (2017/12/01) ローカルリソースの文字型⇒数値型変換の処理の有無
937 * @param curOffset オフセット
939 * @param sb StringBuilderオブジェクト
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 );
946 // セルの場合のみ置き換えの判定を行う(オブジェクトの場合は判定しない)
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 );
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, "" );
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 );
975 // アノテーションの値から、セルの文字列部分を置き換え
976 final int endIdx = isCell ? row.indexOf( TABLE_CELL_END_TAG, offset ) : row.indexOf( DRAW_END_TAG, offset );
978 int textStrIdx = row.indexOf( TEXT_START_ANO_TAG, offset );
979 // セルのコメントの場合、<text:pで検索すると、オブジェクトのテキストが検索されている可能性がある。
980 // このため、セルの<text:pが見つかるまで検索を繰り返す
982 while( !isCell( row, textStrIdx ) && textStrIdx >= 0 ) {
983 textStrIdx = row.indexOf( TEXT_START_ANO_TAG, textStrIdx + 1 );
986 if( textStrIdx >= 0 && textStrIdx < endIdx ) {
987 // セルのコメントの場合、</text:p>で検索すると、オブジェクトのテキストが検索されている可能性がある。
988 // このため、セルの</text:p>が見つかるまで検索を繰り返す
989 int textEndIdx = row.lastIndexOf( TEXT_END_TAG, endIdx );
991 while( !isCell( row, textEndIdx ) && textEndIdx >= 0 ) {
992 textEndIdx = row.lastIndexOf( TEXT_END_TAG, textEndIdx - 1 );
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 ) );
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 );
1012 offset = textEndIdx;
1021 * 現在のオフセットがセルかどうかを返します。
1023 * trueの場合はセルを、falseの場合はオブジェクトを意味します。
1025 * セルとして判定されるための条件は以下の通りです。
1027 * ①前に<draw:(オブジェクトの開始)が見つからない
1028 * ②前に<table:table-cell(セルの始まり)が<draw:(オブジェクトの始まり)より後方にある
1029 * ③後に</draw:(オブジェクトの終わり)が見つからない
1030 * ④後に</draw:(オブジェクトの終わり)が</table:table-cell>(セルの終わり)より後方にある
1033 * @param offset オフセット
1034 * @return 現在のオフセットがセルかどうか(falseの場合はオブジェクト)
1036 private boolean isCell( final String row, final int offset ) {
1037 final int drawStartOffset = row.lastIndexOf( DRAW_START_TAG, offset );
1038 if( drawStartOffset < 0 ) {
1042 final int cellStartOffset = row.lastIndexOf( TABLE_CELL_START_TAG, offset );
1043 if( drawStartOffset < cellStartOffset ) {
1047 final int drawEndOffset = row.indexOf( DRAW_END_TAG, offset );
1048 if( drawEndOffset < 0 ) {
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 ;
1062 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
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廃止)
1073 * @param curOffset オフセット
1075 * @param sb StringBuilderオブジェクト
1078 private int makeQRImage( final String row, final int curOffset, final String key, final StringBuilder sb ) {
1079 int offset = curOffset;
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;
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 );
1094 // // QRCODEに書き込む値を求める … 8.3.0.0 (2022/08/01) QrcodeImage 作成直前に移動
1095 // final String value = getValue( key );
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;
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 + " の ディレクトリ作成に失敗しました。" );
1116 final String value = getValue( key,false ); // 8.3.0.0 (2022/08/01) メタ文字変換 しない
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();
1125 // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1126 addObjMap.put( fileName, QRCODE_FILETYPE.substring( 1 ) );
1133 * DBTableModelに設定されたパスから画像データを取得し、内部に取り込みます
1134 * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
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) 画像ファイル名が漢字の場合、うまくいかないので、置き換える。
1142 * @param curOffset オフセット
1144 * @param sb StringBuilderオブジェクト
1147 private int changeImage( final String row, final int curOffset, final String key, final StringBuilder sb ) {
1148 int offset = curOffset;
1149 File imgFile = null;
1151 // 画像ファイルを読み込むパスを求める
1152 final String value = getValue( key );
1154 if( value != null && value.length() > 0 ) {
1155 imgFile = new File( HybsSystem.url2dir( value ) );
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 ) );
1164 // 画像のパスの終了インデックスを求める
1165 // offset = row.indexOf( DRAW_IMG_HREF_END, offset ) + DRAW_IMG_HREF_END.length();
1166 offset = row.indexOf( END_KEY, offset ) + 1;
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;
1176 // sb.append( fileNameOut ).append( DRAW_IMG_HREF_END );
1177 sb.append( fileNameOut ).append( END_KEY );
1179 final File fileOutAbs = new File( path,fileNameOut );
1180 if( !fileOutAbs.getParentFile().exists() && fileOutAbs.getParentFile().mkdirs() ) {
1181 System.err.println( fileOutAbs + " の ディレクトリ作成に失敗しました。" );
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 + " の ディレクトリ作成に失敗しました。" );
1195 // FileUtil.copy( imgFile, new File( fileNameOutAbs ) ); // imgFile → fileNameOutAbs copy
1196 FileUtil.copy( imgFile, fileOutAbs ); // imgFile → fileOutAbs copy
1198 // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1199 addObjMap.put( fileNameOut, getSuffix( imgFile.getName() ) );
1201 // 画像パスが設定されていない、又は画像が存在しない場合
1203 // {@IMG.XXXX}から見て、<draw:image> ... </draw:image>までをスキップする
1204 offset = row.indexOf( DRAW_IMG_START_TAG, offset );
1205 sb.append( row.substring( curOffset, offset ) );
1207 offset = row.indexOf( DRAW_IMG_END_TAG, offset ) + DRAW_IMG_END_TAG.length();
1215 * 変換後の行データで定義されている関数にISERROR関数を埋め込みます。
1217 * これは、OOoの関数の動作として、不正な引数等が入力された場合(null値など)に、
1218 * エラー:xxxと表示されてしまうため、これを防ぐために関数エラーのハンドリングを行い、
1219 * エラーの場合は、空白文字を返すようにします。
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) パース方法の内部実装変更
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; }
1236 final String rowStr = new TagParser() {
1238 * 開始タグから終了タグまでの文字列の処理を実行するかどうかを定義します。
1240 * @param strOffset 開始タグのオフセット
1241 * @param endOffset 終了タグのオフセット
1242 * @return 処理を行うかどうか(true:処理を行う false:処理を行わない)
1245 protected boolean checkIgnore( final int strOffset, final int endOffset ) {
1246 // 5.1.7.0 (2010/06/01) 関数の終わりが)出ない場合にエラーとなるバグを修正
1247 // 単なる行参照でも、of:=で始まるがこの場合は、関数でないため終わりが)でない
1248 // このため、)が見つからないまたは、タグの終わり(>)が先に見つかった場合は、エラー関数を
1250 int tmpOffset = row.indexOf( ">", strOffset + 1 );
1251 return endOffset >= 0 && endOffset < tmpOffset ;
1255 * 開始タグから終了タグまでの文字列の処理を定義します。
1257 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
1258 * @param buf 出力を行う文字列バッファ
1259 * @param offset 終了タグのオフセット(ここでは使っていません)
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 );
1269 }.doParse( row, functionStart, OOOC_FUNCTION_END );
1275 * グラフ表示データ部分を更新します。
1277 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
1278 * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1281 * @param sheetOrig 元シート
1282 * @param sheetNew 新シート
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; }
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() {
1292 * 開始タグから終了タグまでの文字列の処理を定義します。
1294 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
1295 * @param buf 出力を行う文字列バッファ
1296 * @param offset 終了タグのオフセット(ここでは使っていません)
1299 protected void exec( final String str, final StringBuilder buf, final int offset ) {
1300 // <draw:object ... /> の部分
1301 String graphTag = str;
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;
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 );
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 );
1317 // OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1318 addObjMap.put( nameNew, "graph" );
1320 // グラフオブジェクトの定義ファイルに記述されている定義ファイルをパースし、シート名と{@XXXX}を置き換え
1321 // parseGraphContent( path + nameNew + File.separator + "content.xml", sheetOrig, sheetNew );
1322 parseGraphContent( path + nameNew + FS + "content.xml", sheetOrig, sheetNew );
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 ) );
1331 buf.append( graphTag );
1333 }.doParse( row, GRAPH_START_TAG, GRAPH_END_TAG );
1339 * グラフデータのcontent.xmlをパースします。
1341 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
1343 * @param fileName ファイル名
1344 * @param sheetOrig 元シート
1345 * @param sheetNew 新シート
1347 private void parseGraphContent( final String fileName, final String sheetOrig, final String sheetNew ) {
1348 String graphContent = readOOoXml( fileName );
1351 if( graphContent.indexOf( GRAPH_CONTENT_START ) >= 0 ) {
1352 graphContent = new TagParser() {
1354 * 開始タグから終了タグまでの文字列の処理を定義します。
1356 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません)
1357 * サブクラスでオーバーライドして実際の処理を実装して下さい。
1359 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
1360 * @param buf 出力を行う文字列バッファ
1361 * @param offset 終了タグのオフセット(ここでは使っていません)
1364 protected void exec( final String str, final StringBuilder buf, final int offset ) {
1365 buf.append( str.replace( sheetOrig, sheetNew ) );
1367 // }.doParse( graphContent, GRAPH_CONTENT_START, GRAPH_CONTENT_END );
1368 }.doParse( graphContent, GRAPH_CONTENT_START, END_KEY );
1372 if( graphContent.indexOf( VAR_START ) >= 0 ) {
1373 graphContent = new TagParser() {
1375 * 開始タグから終了タグまでの文字列の処理を定義します。
1377 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません)
1378 * サブクラスでオーバーライドして実際の処理を実装して下さい。
1380 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
1381 * @param buf 出力を行う文字列バッファ
1382 * @param offset 終了タグのオフセット(ここでは使っていません)
1385 public void exec( final String str, final StringBuilder buf, final int offset ) {
1386 buf.append( getHeaderFooterValue( str ) );
1388 }.doParse( graphContent, VAR_START, VAR_END, false );
1391 writeOOoXml( fileName, graphContent );
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) '<','>','&'のメタ文字変換をデフォルト実行します。
1405 private String getValue( final String key ) {
1406 return getValue( key,true );
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) '<','>','&'のメタ文字変換をするかどうかを指定します。
1418 * @param useChange タ文字変換するかどうか[true:する]
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 );
1425 // String value = null;
1426 final String value ;
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 );
1434 // final String name = key.substring( 0, conOffset );
1437 // rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) ) + currentBaseRow; // 6.0.2.4 (2014/10/17) メソッド間違い
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 );
1448 // // 4.3.0.0 (2008/07/18) アンダースコア後が数字に変換できない場合はヘッダフッタとして認識
1449 // if( rownum < 0 ){
1450 // value = getHeaderFooterValue( key );
1453 // value = getBodyValue( name, rownum );
1454 value = getBodyValue( spKey.name, spKey.rownum + currentBaseRow );
1458 // return checkValue( value );
1459 return checkValue( value,useChange ); // 8.3.0.0 (2022/08/01)
1463 * 指定されたキーのヘッダー、フッター値を返します。
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() を、ローカル変数で定義。
1474 private String getHeaderFooterValue( final String key ) {
1477 // 5.1.6.0 (2010/05/01) ページNO出力対応
1478 if( PAGE_NO.equals( key ) ) {
1479 value = String.valueOf( pages + 1 );
1481 // 6.1.1.0 (2015/01/17) 内部ロジックの見直し。queue.getFooter(),queue.getHeader() を、ローカル変数で定義。
1483 // 最後の行かオーバーフロー時はフッター。最後の行にきていない場合はヘッダー
1484 final DBTableModel headerFooterModel = status >= LASTROW ? queue.getFooter() : queue.getHeader() ;
1486 if( headerFooterModel != null ) {
1487 final int clmno = headerFooterModel.getColumnNo( key, false );
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 );
1504 * 指定された行番号、キーのボディー値を返します。
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() を、ローカル変数で定義他。
1518 * @return キーのボディー値
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時バグ修正
1525 final DBTableModel bodyModel = queue.getBody(); // 6.1.1.0 (2015/01/17)
1527 final int clmno = bodyModel.getColumnNo( key, false ); // 6.1.1.0 (2015/01/17)
1528 if( clmno < 0 && !ROW_NO.equals( key ) ) { return ""; }
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 ) ) ) ) {
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 ) ) ) ) {
1560 if( rownum >= rowCount ) { // 6.1.1.0 (2015/01/17)
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) 自身のステータスと比べて大きい方を返す
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 );
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)
1585 // 4.3.6.2 (2009/04/15)
1586 if( currentMaxRow < rownum + 1 ) {
1587 currentMaxRow = rownum + 1;
1594 * 値に'<'や'>','&'が含まれていた場合にメタ文字に変換します。
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) '<','>','&'のメタ文字変換をするかどうかを指定します。
1600 * @param value 変換前の値
1601 * @param useChange タ文字変換するかどうか[true:する]
1605 // private String checkValue( final String value ) {
1606 private String checkValue( final String value,final boolean useChange ) {
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" );
1614 final String spanStart = rtn.substring( idx, rtn.indexOf( '>', idx ) + 1 );
1615 rtn = rtn.replace( spanStart, "" ).replace( "</span>", "" );
1619 if( useChange ) { // 8.3.0.0 (2022/08/01)
1620 if( rtn.indexOf( '&' ) >= 0 ) {
1621 rtn = rtn.replace( "&", "&" );
1623 if( rtn.indexOf( '<' ) >= 0 ) {
1624 rtn = rtn.replace( "<", "<" );
1626 if( rtn.indexOf( '>' ) >= 0 ) {
1627 rtn = rtn.replace( ">", ">" );
1629 if( rtn.indexOf( '\n' ) >= 0 ) {
1630 rtn = rtn.replace( "\r\n", "\n" ).replace( "\n", OOO_CR );
1638 // * 引数の文字列を指定された開始タグ、終了タグで解析し配列として返します。
1639 // * 開始タグより前の文字列は0番目に、終了タグより後の文字列は1番目に格納されます。
1640 // * 2番目以降に、開始タグ、終了タグの部分が格納されます。
1642 // * @og.rev 8.0.3.0 (2021/12/17) TagParser.tag2Array を使用します。
1645 // * @param startTag 開始タグ
1646 // * @param endTag 終了タグ
1647 // * @return 解析結果の配列
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<>();
1655 // int preOffset = -1;
1656 // int curOffset = 0;
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 ) );
1664 // footer = str.substring( curOffset );
1667 // else if( preOffset == -1 ) {
1668 // header = str.substring( 0, curOffset );
1671 // body.add( str.substring( preOffset, curOffset ) );
1673 // preOffset = curOffset;
1676 // String[] arr = new String[body.size()+2];
1679 // for( int i=0; i<body.size(); i++ ) {
1680 // arr[i+2] = body.get(i);
1687 * 帳票処理キューを元に、style.xml(ヘッダー、フッター)を書き換えます。(2)
1689 * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更
1691 private void execStyles() {
1692 final String fileName = path + "styles.xml";
1693 String content = readOOoXml( fileName );
1695 if( content.indexOf( VAR_START ) < 0 ) { return; }
1697 content = new TagParser() {
1699 * 開始タグから終了タグまでの文字列の処理を定義します。
1701 * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません)
1702 * サブクラスでオーバーライドして実際の処理を実装して下さい。
1704 * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
1705 * @param buf 出力を行う文字列バッファ
1706 * @param offset 終了タグのオフセット(ここでは使っていません)
1709 public void exec( final String str, final StringBuilder buf, final int offset ) {
1710 buf.append( getHeaderFooterValue( str ) );
1712 }.doParse( readOOoXml( fileName ), VAR_START, VAR_END, false );
1714 writeOOoXml( fileName, content );
1718 * 帳票処理キューを元に、meta.xmlを書き換えます。(6)
1720 * @og.rev 5.1.6.0 (2010/05/01) 画面帳票作成機能対応(API経由では出力されないことがある)
1722 private void execMeta() {
1723 final String fileName = path + "meta.xml";
1725 String meta = readOOoXml( fileName );
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 );
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 ) );
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 ) );
1754 writeOOoXml( fileName, meta );
1758 * 書き換え対象のスタイルリストを取得します。
1760 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1761 * @og.rev 8.0.3.0 (2021/12/17) TagParser.tag2Array を使用します。
1763 * @param header content.xmlのヘッダー
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 );
1782 * 帳票処理キューを元に、content.xmlを書き換えます。(4)
1783 * まず、XMLを一旦メモリ上に展開した後、シート単位に分解し、データの埋め込みを行います。
1785 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1786 * @og.rev 8.0.3.0 (2021/12/17) TagParser.tag2Array を使用します。
1788 private void execContentHeader() {
1789 final String fileName = path + "content.xml";
1790 final String content = readOOoXml( fileName );
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];
1798 BufferedWriter bw = null;
1800 bw = getWriter( fileName );
1801 bw.write( xmlHeader );
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 + "." );
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 );
1825 bw.write( tags[i] );
1832 catch( final IOException ex ) {
1833 queue.addMsg( "[ERROR]PARSE:error occurer while write ReParsed Sheet " + fileName );
1834 throw new HybsSystemException( ex );
1837 Closer.ioClose( bw );
1842 * content.xmlのヘッダー部分を出力したcontent.xmlに、ヘッダー部分以降を出力した content.xml.bakをマージします。(5)
1844 * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1846 private void execMergeContent() {
1847 FileChannel srcChannel = null;
1848 FileChannel destChannel = null;
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);
1854 catch( final IOException ex ) {
1855 queue.addMsg( "[ERROR]PARSE:error occurer while merge content.xml" );
1856 throw new HybsSystemException( ex );
1859 Closer.ioClose( srcChannel );
1860 Closer.ioClose( destChannel );
1862 FileUtil.deleteFiles( new File( path + "content.xml.tmp" ) );
1866 * META-INF/manifest.xmlに、追加したオブジェクト(グラフ、画像)を登録します。(7)
1868 * @og.rev 5.3.1.0 (2011/12/01) 新規作成
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 );
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] );
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 なので、このまま。
1892 buf.append( "<manifest:file-entry manifest:media-type=\"image/" )
1893 .append( entry.getValue() ).append( "\" manifest:full-path=\"" )
1894 .append( entry.getKey() ).append( "\"/>" ); // XML なので、このまま。
1897 buf.append( conArr[1] );
1899 writeOOoXml( fileName, buf.toString() );
1903 * XMLファイルを読み取り、結果を返します。
1904 * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、
1905 * ここでは、2行目の内容部分を返します。
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 に変換する。
1912 * @param fileName ファイル名
1916 private String readOOoXml( final String fileName ) {
1917 final File file = new File ( fileName );
1919 BufferedReader br = null;
1921 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
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)
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)
1936 catch( final IOException ex ) {
1937 queue.addMsg( "[ERROR]PARSE:Failed to read " + fileName );
1938 throw new HybsSystemException( ex );
1941 Closer.ioClose( br );
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();
1955 * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、
1956 * ここでは、2行目の内容部分を渡すことで、XMLファイルを作成します。
1958 * @param fileName 書き込むXMLファイル名
1959 * @param str 書き込む文字列
1961 private void writeOOoXml( final String fileName, final String str ) {
1962 BufferedWriter bw = null;
1964 bw = getWriter( fileName );
1965 bw.write( xmlHeader );
1970 catch( final IOException ex ) {
1971 queue.addMsg( "[ERROR]PARSE:Failed to write " + fileName );
1972 throw new HybsSystemException( ex );
1975 Closer.ioClose( bw );
1980 * XMLファイル書き込み用のライターを返します。
1982 * @param fileName ファイル名
1986 private BufferedWriter getWriter( final String fileName ) {
1987 return getWriter( fileName, false );
1991 * XMLファイル書き込み用のライターを返します。
1993 * @param fileName ファイル名
1994 * @param append アベンドするか
1998 private BufferedWriter getWriter( final String fileName, final boolean append ) {
1999 final File file = new File ( fileName );
2002 bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file, append ), "UTF-8" ) );
2004 catch( final UnsupportedEncodingException ex ) {
2005 queue.addMsg( "[ERROR]PARSE:Input File is written by Unsupported Encoding" );
2006 throw new HybsSystemException( ex );
2008 catch( final FileNotFoundException ex ) {
2009 queue.addMsg( "[ERROR]PARSE:File not Found" );
2010 throw new HybsSystemException( ex );
2016 * ファイル名から拡張子(小文字)を求めます。
2018 * @param fileName 拡張子を取得する為のファイル名
2021 public static String getSuffix( final String fileName ) {
2022 String suffix = null;
2023 if( fileName != null ) {
2024 final int sufIdx = fileName.lastIndexOf( '.' );
2026 suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );