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