OSDN Git Service

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