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) のメソッドを使用するように変更。
715         *
716         * @param key
717         * @param val
718         *
719         * @return  NATIVEの型の識別コード
720         * @see org.opengion.fukurou.model.NativeType
721         */
722        private NativeType getNativeType( final String key, final String val ) {
723                if( val == null || val.length() == 0 ) {
724                        return NativeType.STRING;
725                }
726
727                NativeType type = null;
728                if( queue.isFglocal() ) {
729                        String name = key;
730                        int conOffset = key.lastIndexOf( VAR_CON );
731                        if( conOffset >= 0 ) {
732                                int rownum = -1;
733                                try {
734                                        rownum = Integer.valueOf( name.substring( conOffset + VAR_CON.length(), name.length() ) );
735                                }
736                                // '_'以降の文字が数字でない場合は、'_'以降の文字もカラム名の一部として扱う
737                                catch ( NumberFormatException ex ) {}
738                                if( rownum >= 0 ) {
739                                        name = name.substring( 0, conOffset );
740                                }
741                        }
742                        int col = queue.getBody().getColumnNo( name, false );
743                        if( col >= 0 ) {
744                                type = queue.getBody().getDBColumn( col ).getNativeType();
745                        }
746                }
747
748                if( type == null ) {
749                        // ,は削除した状態で判定
750                        String repVal = val.replace( ",", "" );
751                        type = NativeType.getType( repVal );                    // 5.1.8.0 (2010/07/01) NativeType#getType(String) のメソッドを使用
752                        // 整数型で、0nnnとなっている場合は、文字列をして扱う
753                        if( type == NativeType.INT && repVal.length() >= 2 && repVal.charAt(0) == '0' ) {
754                                type = NativeType.STRING;
755                        }
756                }
757
758                return type;
759        }
760
761        /**
762         * コメント(アノテーションによる置き換え処理を行います)
763         * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
764         *
765         * @og.rev 5.1.8.0 (2010/07/01) 新規作成
766         *
767         * @param row
768         * @param curOffset
769         * @param key
770         * @param sb
771         *
772         * @return 処理後のオフセット
773         */
774        private int parseByAnnotation( final String row, final int curOffset, final String key, final StringBuilder sb ) {
775                int offset = curOffset;
776                boolean isCell = isCell( row, offset );
777
778                // セルの場合のみ置き換えの判定を行う(オブジェクトの場合は判定しない)
779                if( isCell ) {
780                        int cellStrIdx = sb.lastIndexOf( TABLE_CELL_START_TAG, offset );
781                        // office:value-type="float" office:value="xxx" を office:value-type="string" に変換
782                        // 数値型の場合は、後で再度変換を行う。
783                        // (文字型に変換しておかないと、値がnullの場合でも"0"が表示されてしまうため)
784                        int floatIdx = sb.indexOf( TABLE_CELL_FLOAT_TYPE, cellStrIdx );
785                        if( floatIdx >= 0 ) {
786                                sb.replace( floatIdx, floatIdx + TABLE_CELL_FLOAT_TYPE.length(), TABLE_CELL_STRING_TYPE );
787
788                                int floatStrIdx = sb.indexOf( TABLE_CELL_FLOAT_VAL_START, floatIdx );
789                                if( floatStrIdx >= 0 ) {
790                                        int floatEndIdx = sb.indexOf( TABLE_CELL_FLOAT_VAL_END, floatStrIdx + TABLE_CELL_FLOAT_VAL_START.length() );
791                                        if( floatEndIdx >= 0 ) {
792                                                sb.replace( floatStrIdx, floatEndIdx + TABLE_CELL_FLOAT_VAL_END.length(), "" );
793                                        }
794                                }
795                        }
796                }
797
798                // アノテーションの値から、セルの文字列部分を置き換え
799                int endIdx = isCell ? row.indexOf( TABLE_CELL_END_TAG, offset ) : row.indexOf( DRAW_END_KEY, offset );
800                if( endIdx >= 0 ) {
801                        int textStrIdx = row.indexOf( TEXT_START_ANO_TAG, offset );
802                        // セルのコメントの場合、<text:pで検索すると、オブジェクトのテキストが検索されている可能性がある。
803                        // このため、セルの<text:pが見つかるまで検索を繰り返す
804                        if( isCell ) {
805                                while( !isCell( row, textStrIdx ) && textStrIdx >= 0 ) {
806                                        textStrIdx = row.indexOf( TEXT_START_ANO_TAG, textStrIdx + 1 );
807                                }
808                        }
809                        if( textStrIdx >= 0 && textStrIdx < endIdx ) {
810                                // セルのコメントの場合、</text:p>で検索すると、オブジェクトのテキストが検索されている可能性がある。
811                                // このため、セルの</text:p>が見つかるまで検索を繰り返す
812                                int textEndIdx = row.lastIndexOf( TEXT_END_TAG, endIdx );
813                                if( isCell ) {
814                                        while( !isCell( row, textEndIdx ) && textEndIdx >= 0  ) {
815                                                textEndIdx = row.lastIndexOf( TEXT_END_TAG, textEndIdx - 1 );
816                                        }
817                                }
818                                if( textEndIdx >= 0 && textStrIdx < textEndIdx && textEndIdx < endIdx ) {
819                                        // <text:p xxxx> の xxxx> の部分(style定義など)を書き込み
820                                        int textStyleEnd = row.indexOf( TEXT_START_END_ANO_TAG, textStrIdx + TEXT_START_ANO_TAG.length() ) + TEXT_START_END_ANO_TAG.length();
821                                        sb.append( row.substring( offset, textStyleEnd ) );
822
823                                        // <text:pの中身(spanタグなどを取り除いた状態の文字列
824                                        String textVal = TagParser.checkKey( row.substring( textStyleEnd, textEndIdx ), sb );
825                                        // 取得したテキスト内にタグ文字が含まれている場合は、処理しない
826                                        if( textVal.indexOf( '<' ) < 0 && textVal.indexOf( '>' ) < 0 ) {
827                                                // <text:p xxxx>を書き出し
828                                                String val = getValue( key );
829                                                // 5.5.2.4 (2012/05/16) String key は使われていないので、削除します。
830                                                changeType( row, textEndIdx, val, getNativeType( key, textVal ), sb );
831                                                sb.append( val );
832                                        }
833                                        offset = textEndIdx;
834                                }
835                        }
836                }
837
838                return offset;
839        }
840
841        /**
842         * 現在のオフセットがセルかどうかを返します。
843         *
844         * trueの場合はセルを、falseの場合はオブジェクトを意味します。
845         *
846         * セルとして判定されるための条件は以下の通りです。
847         *  現在のoffsetを基準として、
848         *  @前に<draw:(オブジェクトの開始)が見つからない
849         *  A前に<table:table-cell(セルの始まり)が<draw:(オブジェクトの始まり)より後方にある
850         *  B後に</draw:(オブジェクトの終わり)が見つからない
851         *  C後に</draw:(オブジェクトの終わり)が</table:table-cell>(セルの終わり)より後方にある
852         *
853         * @param row
854         * @param offset
855         *
856         * @return 現在のオフセットがセルかどうか(falseの場合はオブジェクト)
857         */
858        private boolean isCell( final String row, final int offset ) {
859                int drawStartOffset = row.lastIndexOf( DRAW_START_KEY, offset );
860                if( drawStartOffset < 0 ) {
861                        return true;
862                }
863                else {
864                        int cellStartOffset = row.lastIndexOf( TABLE_CELL_START_TAG, offset );
865                        if( drawStartOffset < cellStartOffset ) {
866                                return true;
867                        }
868                        else {
869                                int drawEndOffset = row.indexOf( DRAW_END_KEY, offset );
870                                if( drawEndOffset < 0 ) {
871                                        return true;
872                                }
873                                else {
874                                        int cellEndOffset = row.indexOf( TABLE_CELL_END_TAG, offset );
875                                        // 5.1.8.0 (2010/07/01) Avoid unnecessary if..then..else statements when returning a boolean
876                                        return cellEndOffset >= 0 && cellEndOffset < drawEndOffset ;
877                                }
878                        }
879                }
880        }
881
882        /**
883         * QRコードを作成します。
884         * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
885         *
886         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
887         * @og.rev 4.3.3.5 (2008/11/08) ↑の判定は存在チェックを行ってから処理する。ファイル名に処理行を付加
888         * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
889         *
890         * @param row
891         * @param curOffset
892         * @param key
893         * @param sb
894         *
895         * @return 処理後のオフセット
896         */
897        private int makeQRImage( final String row, final int curOffset, final String key, final StringBuilder sb ) {
898                int offset = curOffset;
899
900                // {@QRCODE.XXXX}から実際に画像のパスが書かれている部分までを書き込む
901                offset = row.indexOf( DRAW_IMG_START_TAG, offset ) + DRAW_IMG_START_TAG.length();
902                sb.append( row.substring( curOffset, offset ) );
903                // 画像のパスの終了インデックスを求める
904                offset = row.indexOf( DRAW_IMG_HREF_END, offset ) + DRAW_IMG_HREF_END.length();
905
906                // QRCODEの画像ファイル名を求め書き込む
907                // 4.3.3.5 (2008/11/08) ファイル名に処理行を付加
908                String fileName = IMG_DIR + '/' + key + "_" + currentBaseRow + QRCODE_FILETYPE;
909                sb.append( fileName ).append( DRAW_IMG_HREF_END );
910
911                // QRCODEに書き込む値を求める
912                String value = getValue( key );
913
914                // QRCODEの作成
915                // 4.3.3.5 (2008/11/08) ファイル名に処理行を付加
916                String fileNameAbs =
917                        new File( path ).getAbsolutePath() + File.separator + IMG_DIR + File.separator + key + "_" + currentBaseRow + QRCODE_FILETYPE;
918
919                // 画像リンクが無効となっている場合は、Picturesのフォルダが作成されていない可能性がある
920                // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
921                // 4.3.3.5 (2008/11/08) 存在チェック追加
922                if( !new File( fileNameAbs ).getParentFile().exists() ) {
923                        if( new File( fileNameAbs ).getParentFile().mkdirs() ) {
924                                System.err.println( fileNameAbs + " の ディレクトリ作成に失敗しました。" );
925                        }
926                }
927
928                QrcodeImage qrImage = new QrcodeImage();
929                qrImage.init( value, fileNameAbs );
930                qrImage.saveImage();
931
932                // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
933                addObjs.put( fileName, QRCODE_FILETYPE.substring( 1 ) );
934
935                // 読み込みOffsetを返します
936                return offset;
937        }
938
939        /**
940         * DBTableModelに設定されたパスから画像データを取得し、内部に取り込みます
941         * この処理では、offsetを進めるため、戻り値として処理後のoffsetを返します。
942         *
943         * @og.rev 4.3.3.5 (2008/11/08) 新規追加
944         * @og.rev 4.3.3.6 (2008/11/15) 画像パスが存在しない場合は、リンクタグ(draw:image)自体を削除
945         * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
946         *
947         * @param row
948         * @param curOffset
949         * @param key
950         * @param sb
951         *
952         * @return 処理後のオフセット
953         */
954        private int changeImage( final String row, final int curOffset, final String key, final StringBuilder sb ) {
955                int offset = curOffset;
956                File imgFile = null;
957
958                // 画像ファイルを読み込むパスを求める
959                String value = getValue( key );
960
961                if( value != null && value.length() > 0 ) {
962                        imgFile = new File( HybsSystem.url2dir( value ) );
963                }
964
965                // 画像ファイルのパスが入っていて、実際に画像が存在する場合
966                if( imgFile != null && imgFile.exists() ) {
967                        // {@IMG.XXXX}から実際に画像のパスが書かれている部分までを書き込む
968                        offset = row.indexOf( DRAW_IMG_START_TAG, offset ) + DRAW_IMG_START_TAG.length();
969                        sb.append( row.substring( curOffset, offset ) );
970
971                        // 画像のパスの終了インデックスを求める
972                        offset = row.indexOf( DRAW_IMG_HREF_END, offset ) + DRAW_IMG_HREF_END.length();
973
974                        String fileNameOut = IMG_DIR + '/' + imgFile.getName();
975                        sb.append( fileNameOut ).append( DRAW_IMG_HREF_END );
976
977                        String fileNameOutAbs =
978                                new File( path ).getAbsolutePath() + File.separator + IMG_DIR + File.separator + imgFile.getName();
979                        if( !new File( fileNameOutAbs ).getParentFile().exists() ) {
980                                if( new File( fileNameOutAbs ).getParentFile().mkdirs() ) {
981                                        System.err.println( fileNameOutAbs + " の ディレクトリ作成に失敗しました。" );
982                                }
983                        }
984                        FileUtil.copy( imgFile, new File( fileNameOutAbs ) );
985
986                        // 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
987                        addObjs.put( fileNameOut, getSuffix( imgFile.getName() ) );
988                }
989                // 画像パスが設定されていない、又は画像が存在しない場合
990                else {
991                        // {@IMG.XXXX}から見て、<draw:image> ... </draw:image>までをスキップする
992                        offset = row.indexOf( DRAW_IMG_START_TAG, offset );
993                        sb.append( row.substring( curOffset, offset ) );
994
995                        offset = row.indexOf( DRAW_IMG_END_TAG, offset ) + DRAW_IMG_END_TAG.length();
996                }
997
998                // 読み込みOffsetを返します
999                return offset;
1000        }
1001
1002        /**
1003         * 変換後の行データで定義されている関数にISERROR関数を埋め込みます。
1004         *
1005         * これは、OOoの関数の動作として、不正な引数等が入力された場合(null値など)に、
1006         * エラー:xxxと表示されてしまうため、これを防ぐために関数エラーのハンドリングを行い、
1007         * エラーの場合は、空白文字を返すようにします。
1008         *
1009         * @og.rev 4.3.7.2 (2009/06/15) 開始文字が変更になったため対応
1010         * @og.rev 5.0.2.0 (2009/11/01) 関数内の"(quot)は、メタ文字に変換する
1011         * @og.rev 5.1.7.0 (2010/06/01) 関数の終わりが)出ない場合にエラーとなるバグを修正
1012         * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更
1013         *
1014         * @param row
1015         *
1016         * @return 変換後の行データ
1017         */
1018        private String replaceOoocError( final String row ) {
1019                // 4.3.7.2 (2009/06/15) OOOC_FUNCTION_START3の条件判定追加。どちらか分からないので変数で受ける。
1020                final String functionStart;
1021                if( row.indexOf( OOOC_FUNCTION_START_3 ) >= 0 )              { functionStart = OOOC_FUNCTION_START_3; }
1022                else if( row.indexOf( OOOC_FUNCTION_START ) >= 0 )   { functionStart = OOOC_FUNCTION_START; }
1023                else { return row; }
1024
1025                String rowStr = new TagParser() {
1026                        @Override
1027                        protected boolean checkIgnore( final int strOffset, final int endOffset ) {
1028                                // 5.1.7.0 (2010/06/01) 関数の終わりが)出ない場合にエラーとなるバグを修正
1029                                // 単なる行参照でも、of:=で始まるがこの場合は、関数でないため終わりが)でない
1030                                // このため、)が見つからないまたは、タグの終わり(>)が先に見つかった場合は、エラー関数を
1031                                // 埋め込まないようにする。
1032                                int tmpOffset = row.indexOf( ">", strOffset + 1 );
1033                                return endOffset >= 0 && endOffset < tmpOffset ;
1034                        }
1035
1036                        @Override
1037                        protected void exec( final String str, final StringBuilder buf, final int offset ) {
1038                                String key = str.substring( functionStart.length(), str.length() - OOOC_FUNCTION_END.length() ) + ")";
1039                                key = key.replace( "\"", "&quot;&quot;" ).replace( OOO_CR, "" );
1040                                buf.append( functionStart + "IF(ISERROR(" + key + ");&quot;&quot;;" + key + OOOC_FUNCTION_END );
1041                        }
1042                }.doParse( row, functionStart, OOOC_FUNCTION_END );
1043
1044                return rowStr;
1045        }
1046
1047        /**
1048         * グラフ表示データ部分を更新します。
1049         *
1050         * @og.rev 5.1.8.0 (2010/07/01) 新規作成
1051         * @og.rev 5.3.1.0 (2011/01/01) OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1052         *
1053         * @param row
1054         * @param sheetOrig
1055         * @param sheetNew
1056         *
1057         * @return 変換後の行データ
1058         */
1059        private String replaceGraphInfo( final String row, final String sheetOrig, final String sheetNew  ) {
1060                if( row.indexOf( GRAPH_START_TAG ) < 0 || row.indexOf( GRAPH_UPDATE_RANGE_START ) < 0 ) { return row; }
1061
1062                String rowStr = new TagParser() {
1063                        @Override
1064                        protected void exec( final String str, final StringBuilder buf, final int offset ) {
1065                                // <draw:object ... /> の部分
1066                                String graphTag = str;
1067
1068                                if( graphTag.indexOf( GRAPH_UPDATE_RANGE_START ) >= 0 ) {
1069                                        String nameOrig = TagParser.getValueFromTag( graphTag, GRAPH_HREF_START, GRAPH_HREF_END );
1070                                        if( new File( path + nameOrig ).exists() ) {
1071                                                String nameNew = nameOrig + "_" + pages;
1072
1073                                                // グラフオブジェクトの定義ファイルをコピー(./Object X/* ⇒ ./Object X_n/*)
1074                                                FileUtil.copyDirectry( path + nameOrig, path + nameNew );
1075                                                graphTag = graphTag.replace( GRAPH_HREF_START + nameOrig, GRAPH_HREF_START + nameNew );
1076
1077                                                // グラフオブジェクトの画像イメージをコピー(./ObjectReplacements/Object X ⇒ ./ObjectReplacements/Object X_n)
1078                                                // ※実体はコピーしない(リンクの参照を無効にしておくことで、次回起動時にグラフの再描画が行われる)
1079                                                graphTag = graphTag.replace( GRAPH_HREF_START + GRAPH_OBJREPL + "/" + nameOrig, GRAPH_HREF_START + GRAPH_OBJREPL + "/" + nameNew );
1080
1081                                                // OpenOffice3.2対応 追加した画像をmanifest.xmlに登録する
1082                                                addObjs.put( nameNew, "graph" );
1083
1084                                                // グラフオブジェクトの定義ファイルに記述されている定義ファイルをパースし、シート名と{@XXXX}を置き換え
1085                                                parseGraphContent( path + nameNew + File.separator + "content.xml", sheetOrig, sheetNew );
1086
1087                                                // グラフの参照範囲のシート名を置き換え
1088                                                String range = TagParser.getValueFromTag( str, GRAPH_UPDATE_RANGE_START, GRAPH_UPDATE_RANGE_END );
1089                                                graphTag = graphTag.replace( GRAPH_UPDATE_RANGE_START + range, GRAPH_UPDATE_RANGE_START + range.replace( sheetOrig, sheetNew ) );
1090                                        }
1091                                }
1092
1093                                buf.append( graphTag );
1094                        }
1095                }.doParse( row, GRAPH_START_TAG, GRAPH_END_TAG );
1096
1097                return rowStr;
1098        }
1099
1100        /**
1101         * グラフデータのcontent.xmlをパースします。
1102         *
1103         * @og.rev 5.1.8.0 (2010/07/01) 新規作成
1104         *
1105         * @param fileName
1106         * @param sheetOrig
1107         * @param sheetNew
1108         */
1109        private void parseGraphContent( final String fileName, final String sheetOrig, final String sheetNew  ) {
1110                String graphContent = readOOoXml( fileName );
1111
1112                // シート名の置き換え
1113                if( graphContent.indexOf( GRAPH_CONTENT_START ) >= 0 ) {
1114                        graphContent = new TagParser() {
1115                                @Override
1116                                protected void exec( final String str, final StringBuilder buf, final int offset ) {
1117                                        buf.append( str.replace( sheetOrig, sheetNew ) );
1118                                }
1119                        }.doParse( graphContent, GRAPH_CONTENT_START, GRAPH_CONTENT_END );
1120                }
1121
1122                // {@XXXX}の置き換え
1123                if( graphContent.indexOf( VAR_START ) >= 0 ) {
1124                        graphContent = new TagParser() {
1125                                @Override
1126                                public void exec( final String str, final StringBuilder buf, final int offset ) {
1127                                        buf.append( getHeaderFooterValue( str ) );
1128                                }
1129                        }.doParse( graphContent, VAR_START, VAR_END, false );
1130                }
1131
1132                writeOOoXml( fileName, graphContent );
1133        }
1134
1135        /**
1136         * 指定されたキーの値を返します。
1137         *
1138         * @og.rev 4.3.0.0 (2008/07/18) アンダースコアの処理変更
1139         * @og.rev 4.3.5.0 (2008/02/01) カラム名と行番号文字の位置は最後から検索する 4.3.3.4 (2008/11/01) 修正分
1140         *
1141         * @param key
1142         *
1143         * @return 値
1144         */
1145        private String getValue( final String key ) {
1146                int conOffset = key.lastIndexOf( VAR_CON );
1147
1148                String value = null;
1149
1150                if( conOffset < 0 ) {
1151                        value = getHeaderFooterValue( key );
1152                }
1153                else {
1154                        String name = key.substring( 0, conOffset );
1155                        int rownum = -1;
1156                        try {
1157                                rownum = Integer.valueOf( key.substring( conOffset + VAR_CON.length(), key.length() ) ) + currentBaseRow;
1158                        }
1159                        catch ( NumberFormatException ex ) {
1160                                // 4.3.0.0 (2008/07/18) エラーが起きてもなにもしない。
1161                                // queue.addMsg( "[ERROR]雛形の変数定義が誤っています。カラム名=" + name + HybsSystem.CR );
1162                                // throw new Exception( ex );
1163                        }
1164
1165                        // 4.3.0.0 (2008/07/18) アンダースコア後が数字に変換できない場合はヘッダフッタとして認識
1166                        if( rownum < 0 ){
1167                                value = getHeaderFooterValue( key );
1168                        }
1169                        else{
1170                                value = getBodyValue( name, rownum );
1171                        }
1172                }
1173
1174                return checkValue( value );
1175        }
1176
1177        /**
1178         * 指定されたキーのヘッダー、フッター値を返します。
1179         *
1180         * @og.rev 4.3.6.0 (2009/04/01) レンデラー適用されていないバグを修正
1181         * @og.rev 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。
1182         * @og.rev 5.1.6.0 (2010/05/01) ページNO出力対応
1183         *
1184         * @param key
1185         *
1186         * @return 値
1187         */
1188        private String getHeaderFooterValue( final String key ) {
1189                String value = "";
1190
1191                // 5.1.6.0 (2010/05/01) ページNO出力対応
1192                if( PAGE_NO.equals( key ) ) {
1193                        value = String.valueOf( pages + 1 );
1194                }
1195                // 最後の行かオーバーフロー時はフッター
1196                else if( status >= LASTROW ) {
1197                        if( queue.getFooter() != null ) {
1198                                int clmno = queue.getFooter().getColumnNo( key, false );
1199                                if( clmno >= 0 ) {
1200                                        value = queue.getFooter().getValue( 0, clmno );
1201                                        // 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。
1202                                        if( queue.isFglocal() ) {
1203                                                // 4.3.6.0 (2009/04/01)
1204                                                value = queue.getFooter().getDBColumn( clmno ).getRendererValue( value );
1205                                        }
1206                                }
1207                        }
1208                }
1209                // 最後の行にきていない場合はヘッダー
1210                else {
1211                        if( queue.getHeader() != null ) {
1212                                int clmno = queue.getHeader().getColumnNo( key, false );
1213                                if( clmno >= 0 ) {
1214                                        value = queue.getHeader().getValue( 0, clmno );
1215                                        // 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。
1216                                        if( queue.isFglocal() ) {
1217                                                // 4.3.6.0 (2009/04/01)
1218                                                value = queue.getHeader().getDBColumn( clmno ).getRendererValue( value );
1219                                        }
1220                                }
1221                        }
1222                }
1223
1224                return value;
1225        }
1226
1227        /**
1228         * 指定された行番号、キーのボディー値を返します。
1229         *
1230         * @og.rev 4.3.6.0 (2009/04/01) レンデラー適用されていないバグを修正
1231         * @og.rev 4.3.6.2 (2009/04/15) 行番号のより小さいカラム定義を読んだ際に、内部カウンタがクリアされてしまうバグを修正
1232         * @og.rev 4.3.6.2 (2009/04/15) 一度オーバーフローした場合に移行が全て空文字で返ってしまうバグを修正
1233         * @og.rev 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。
1234         * @og.rev 5.1.6.0 (2010/05/01) 行番号出力対応
1235         * @og.rev 5.1.7.0 (2010/06/01) 複数シート対応
1236         * @og.rev 5.1.9.0 (2010/08/01) 最終行で正しくシートブレイクされないバグを修正
1237         *
1238         * @param key
1239         * @param rownum
1240         *
1241         * @return 値
1242         */
1243        private String getBodyValue( final String key, final int rownum ) {
1244                // if( status == OVERFLOW || isPageBreak ) { return ""; }
1245                if( isPageBreak ) { return ""; } // 4.3.6.2 (2009/04/15) OVERFLOW時バグ修正
1246
1247                int clmno = queue.getBody().getColumnNo( key, false );
1248                if( clmno < 0 && !ROW_NO.equals( key ) ) { return ""; }
1249
1250                // ページブレイク判定、先読みして判断
1251                if( PAGE_BREAK.equals( key ) ) {
1252                        if( rownum < queue.getBody().getRowCount() - 1 ) {
1253                                if( !( queue.getBody().getValue( rownum, clmno ).equals( queue.getBody().getValue( rownum + 1, clmno ) ) ) ) {
1254                                        isPageBreak = true;
1255                                }
1256                        }
1257                        return "";
1258                }
1259
1260                // 5.1.7.0 (2010/06/01) 複数シート対応
1261                // シートブレイクは後読みして判断(前の行と異なっていた場合にブレイク)
1262                if( sheetBreakClm >= 0 ) {
1263                        // 5.1.9.0 (2010/08/01) 最終行で正しくシートブレイクされないバグを修正
1264                        if( rownum < queue.getBody().getRowCount() && currentBaseRow != rownum ) {
1265                                if( !( queue.getBody().getValue( currentBaseRow, sheetBreakClm ).equals( queue.getBody().getValue( rownum, sheetBreakClm ) ) ) ) {
1266                                        isPageBreak = true;
1267                                        return "";
1268                                }
1269                        }
1270                }
1271
1272                if( rownum >= queue.getBody().getRowCount() ) {
1273                        status = OVERFLOW;
1274                        return "";
1275                }
1276
1277                if( rownum == queue.getBody().getRowCount() - 1 ) {
1278                        // status = LASTROW;
1279                        status = Math.max( LASTROW, status ); // 4.3.6.2 (2009/04/15) 自身のステータスと比べて大きい方を返す
1280                }
1281
1282                String value = null;
1283                // 5.1.6.0 (2010/05/01) ページNO出力対応
1284                if( ROW_NO.equals( key ) ) {
1285                        value = String.valueOf( rownum + 1 );
1286                }
1287                else {
1288                        value = queue.getBody().getValue( rownum, clmno );
1289                        // 5.0.2.0 (2009/11/01) ローカルリソースフラグを使用しない場合は、リソース変換を行わない。
1290                        if( queue.isFglocal() ) {
1291                                // 4.3.6.0 (2009/04/01)
1292                                value = queue.getBody().getDBColumn( clmno ).getRendererValue( value );
1293                        }
1294                }
1295
1296                // 4.3.6.2 (2009/04/15)
1297                if( currentMaxRow < rownum + 1 ) {
1298                        currentMaxRow = rownum + 1;
1299                }
1300
1301                return value;
1302        }
1303
1304        /**
1305         * 値に'<'や'>','&'が含まれていた場合にメタ文字に変換します。
1306         *
1307         * @og.rev 5.0.2.0 (2009/11/01) 改行Cの変換ロジックを追加
1308         * @og.rev 5.0.2.0 (2009/11/01) リソース変換時のspanタグを除去
1309         *
1310         * @param value
1311         *
1312         * @return 変換後の値
1313         */
1314        private String checkValue( final String value ) {
1315                String rtn = value;
1316
1317                // 5.0.2.0 (2009/11/01)
1318                if( queue.isFglocal() ) {
1319                        int idx = -1;
1320                        if( ( idx = rtn.indexOf( "<span" ) ) >= 0 ) {
1321                                String spanStart = rtn.substring( idx, rtn.indexOf( '>', idx ) + 1 );
1322                                rtn = rtn.replace( spanStart, "" ).replace( "</span>", "" );
1323                        }
1324                }
1325
1326                if( rtn.indexOf( '&' ) >= 0 ) {
1327                        rtn = rtn.replace( "&", "&amp;" );
1328                }
1329                if( rtn.indexOf( '<' ) >= 0 ) {
1330                        rtn = rtn.replace( "<", "&lt;" );
1331                }
1332                if( rtn.indexOf( '>' ) >= 0 ) {
1333                        rtn = rtn.replace( ">", "&gt;" );
1334                }
1335                if( rtn.indexOf( '\n' ) >= 0 ) {
1336                        rtn = rtn.replace( "\r\n", "\n" ).replace( "\n", OOO_CR );
1337                }
1338
1339                return rtn;
1340        }
1341
1342        /**
1343         * 引数の文字列を指定された開始タグ、終了タグで解析し配列として返します。
1344         * 開始タグより前の文字列は0番目に、終了タグより後の文字列は1番目に格納されます。
1345         * 2番目以降に、開始タグ、終了タグの部分が格納されます。
1346         *
1347         * @param str
1348         * @param startTag
1349         * @param endTag
1350         *
1351         * @return 解析結果の配列
1352         */
1353        private static String[] tag2Array( final String str, final String startTag, final String endTag ) {
1354                String header = null;
1355                String footer = null;
1356                List<String> body = new ArrayList<String>();
1357
1358                int preOffset = -1;
1359                int curOffset = 0;
1360
1361                while( true ) {
1362                        curOffset = str.indexOf( startTag, preOffset + 1 );
1363                        if( curOffset < 0 ) {
1364                                curOffset = str.lastIndexOf( endTag ) + endTag.length();
1365                                body.add( str.substring( preOffset, curOffset ) );
1366
1367                                footer = str.substring( curOffset );
1368                                break;
1369                        }
1370                        else if( preOffset == -1 ) {
1371                                header = str.substring( 0, curOffset );
1372                        }
1373                        else {
1374                                body.add( str.substring( preOffset, curOffset ) );
1375                        }
1376                        preOffset = curOffset;
1377                }
1378
1379                String[] arr = new String[body.size()+2];
1380                arr[0] = header;
1381                arr[1] = footer;
1382                for( int i=0; i<body.size(); i++ ) {
1383                        arr[i+2] = body.get(i);
1384                }
1385
1386                return arr;
1387        }
1388
1389        /**
1390         * 帳票処理キューを元に、style.xml(ヘッダー、フッター)を書き換えます。
1391         *
1392         * @og.rev 5.1.8.0 (2010/07/01) パース方法の内部実装変更
1393         */
1394        private void execStyles() {
1395                String fileName = path + "styles.xml";
1396                String content = readOOoXml( fileName );
1397
1398                if( content.indexOf( VAR_START ) < 0 ) { return; }
1399                content = new TagParser() {
1400                        @Override
1401                        public void exec( final String str, final StringBuilder buf, final int offset ) {
1402                                buf.append( getHeaderFooterValue( str ) );
1403                        }
1404                }.doParse( readOOoXml( fileName ), VAR_START, VAR_END, false );
1405
1406                writeOOoXml( fileName, content );
1407        }
1408
1409        /**
1410         * 帳票処理キューを元に、meta.xmlを書き換えます。
1411         *
1412         * @og.rev 5.1.6.0 (2010/05/01) 画面帳票作成機能対応(API経由では出力されないことがある)
1413         */
1414        private void execMeta() {
1415                String fileName = path + "meta.xml";
1416
1417                String meta = readOOoXml( fileName );
1418
1419                // シート数書き換え
1420                // 5.1.6.0 (2010/05/01)
1421                if( meta.indexOf( TABLE_COUNT_START_TAG ) >=0 ){
1422                        String tableCount = TagParser.getValueFromTag( meta, TABLE_COUNT_START_TAG, TABLE_COUNT_END_TAG );
1423                        meta = meta.replace( TABLE_COUNT_START_TAG + tableCount, TABLE_COUNT_START_TAG + pages );
1424                }
1425
1426                // セル数書き換え
1427                // 5.1.6.0 (2010/05/01)
1428                if( meta.indexOf( CELL_COUNT_START_TAG ) >=0 ){
1429                        String cellCount = TagParser.getValueFromTag( meta, CELL_COUNT_START_TAG, CELL_COUNT_END_TAG );
1430                        meta = meta.replace( CELL_COUNT_START_TAG + cellCount, CELL_COUNT_START_TAG + ( Integer.parseInt( cellCount ) * pages ) );
1431                }
1432
1433                // オブジェクト数書き換え
1434                // 5.1.6.0 (2010/05/01)
1435                if( meta.indexOf( OBJECT_COUNT_START_TAG ) >= 0 ){
1436                        String objectCount = TagParser.getValueFromTag( meta, OBJECT_COUNT_START_TAG, OBJECT_COUNT_END_TAG );
1437                        //4.2.4.0 (2008/06/02) 存在しない場合はnullで帰ってくるので無視する
1438                        if( objectCount != null){
1439                                meta = meta.replace( OBJECT_COUNT_START_TAG + objectCount, OBJECT_COUNT_START_TAG + ( Integer.parseInt( objectCount ) * pages ) );
1440                        }
1441                }
1442
1443                writeOOoXml( fileName, meta );
1444        }
1445
1446        /**
1447         * 書き換え対象のスタイルリストを取得します。
1448         *
1449         * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1450         *
1451         * @param header content.xmlのヘッダー
1452         */
1453        private void getRepStyleList( final String header ) {
1454                String[] tags = tag2Array( header, STYLE_START_TAG, STYLE_END_TAG );
1455                Set<String> origNameSet = pageNameMap.keySet();
1456                for( int i=2; i<tags.length; i++ ) {
1457                        for( String origName : origNameSet ) {
1458                                if( tags[i].indexOf( "=\"" + origName + "." ) >= 0 ) {
1459                                        String styleName = TagParser.getValueFromTag( tags[i], STYLE_NAME_START_TAG, STYLE_NAME_END_TAG );
1460                                        repStyleList.add( styleName );
1461                                        break;
1462                                }
1463                        }
1464                }
1465        }
1466
1467        /**
1468         * 帳票処理キューを元に、content.xmlを書き換えます。
1469         * まず、XMLを一旦メモリ上に展開した後、シート単位に分解し、データの埋め込みを行います。
1470         *
1471         * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1472         */
1473        private void execContentHeader() {
1474                String fileName = path + "content.xml";
1475                String content = readOOoXml( fileName );
1476
1477                // ファイルの解析し、シート+行単位に分解
1478                String[] tags = tag2Array( content, STYLE_START_TAG, STYLE_END_TAG );
1479                String header = tags[0];
1480                String footer = tags[1];
1481
1482                BufferedWriter bw = null;
1483                try {
1484                        bw = getWriter( fileName );
1485                        bw.write( xmlHeader );
1486                        bw.write( '\n' );
1487                        bw.write( header );
1488
1489                        // スタイル情報にシート依存の情報がある場合は、ページ分だけコピーする。
1490                        Set<String> origNameSet = pageNameMap.keySet();
1491                        for( int i=2; i<tags.length; i++ ) {
1492                                boolean isReplace = false;
1493                                for( String origName : origNameSet ) {
1494                                        if( tags[i].indexOf( "=\"" + origName + "." ) >= 0 ) {
1495                                                List<String> newNames = pageNameMap.get( origName );
1496                                                for( String newName : newNames ) {
1497                                                        String styleStr = tags[i].replace( "=\"" + origName + "." , "=\"" + newName + "." );
1498                                                        // シート名の書き換え
1499                                                        String styleName = TagParser.getValueFromTag( styleStr, STYLE_NAME_START_TAG, STYLE_NAME_END_TAG );
1500                                                        styleStr = styleStr.replace( STYLE_NAME_START_TAG + styleName, STYLE_NAME_START_TAG + styleName + "_" + newName );
1501                                                        bw.write( styleStr );
1502                                                        isReplace = true;
1503                                                }
1504                                                break;
1505                                        }
1506                                }
1507
1508                                if( !isReplace ) {
1509                                        bw.write( tags[i] );
1510                                }
1511                        }
1512
1513                        bw.write( footer );
1514                        bw.flush();
1515                }
1516                catch ( IOException ex ) {
1517                        queue.addMsg( "[ERROR]PARSE:error occurer while write ReParsed Sheet " + fileName + HybsSystem.CR );
1518                        throw new HybsSystemException( ex );
1519                }
1520                finally {
1521                        Closer.ioClose( bw );
1522                }
1523        }
1524
1525        /**
1526         * content.xmlのヘッダー部分を出力したcontent.xmlに、ヘッダー部分以降を出力した
1527         * content.xml.bakをマージします。
1528         *
1529         * @og.rev 5.2.2.0 (2010/11/01) 条件付書式対応
1530         */
1531        private void execMergeContent() {
1532                FileChannel srcChannel = null;
1533                FileChannel destChannel = null;
1534                try {
1535                        srcChannel = new FileInputStream( path + "content.xml.tmp" ).getChannel();
1536                        destChannel = new FileOutputStream( path + "content.xml", true ).getChannel();
1537                        srcChannel.transferTo(0, srcChannel.size(), destChannel);
1538                }
1539                catch ( IOException ex ) {
1540                        queue.addMsg( "[ERROR]PARSE:error occurer while merge content.xml" + HybsSystem.CR );
1541                        throw new HybsSystemException( ex );
1542                }
1543                finally {
1544                        Closer.ioClose( srcChannel );
1545                        Closer.ioClose( destChannel );
1546                }
1547                FileUtil.deleteFiles( new File( path + "content.xml.tmp" ) );
1548        }
1549
1550        /**
1551         * META-INF/manifest.xmlに、追加したオブジェクト(グラフ、画像)を登録します。
1552         *
1553         * @og.rev 5.3.1.0 (2011/12/01) 新規作成
1554         */
1555        private void execManifest() {
1556                String fileName = path + "META-INF" + File.separator + "manifest.xml";
1557                String[] conArr = TagParser.tag2Array( readOOoXml( fileName ), MANIFEST_START_TAG, MANIFEST_END_TAG );
1558
1559                StringBuilder buf = new StringBuilder();
1560                buf.append( conArr[0] );
1561                for( int i=2; i<conArr.length; i++ ) {
1562                        buf.append( conArr[i] );
1563                }
1564                for( Map.Entry<String,String> entry : addObjs.entrySet() ) {
1565                        if( "graph".equals( entry.getValue() ) ) {
1566                                buf.append( "<manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" + entry.getKey() + "/content.xml\"/>" );
1567                                buf.append( "<manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" + entry.getKey() + "/styles.xml\"/>" );
1568                                buf.append( "<manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"" + entry.getKey() + "/meta.xml\"/>" );
1569                                buf.append( "<manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.chart\" manifest:full-path=\"" + entry.getKey() + "/\"/>" );
1570                        }
1571                        else {
1572                                buf.append( "<manifest:file-entry manifest:media-type=\"image/" + entry.getValue() + "\" manifest:full-path=\"" + entry.getKey() + "\"/>" );
1573                        }
1574                }
1575                buf.append( conArr[1] );
1576
1577                writeOOoXml( fileName, buf.toString() );
1578        }
1579
1580        /**
1581         * XMLファイルを読み取り、結果を返します。
1582         * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、
1583         * ここでは、2行目の内容部分を返します。
1584         *
1585         * @og.rev 4.3.6.0 (2009/04/01) meta.xmlでコンテンツの部分が改行されている場合があるため、ループを回して読込み
1586         *
1587         * @param fileName
1588         *
1589         * @return 読み取った文字列
1590         */
1591        private String readOOoXml( final String fileName ) {
1592                File file = new File ( fileName );
1593
1594                BufferedReader br = null;
1595                String tmp = null;
1596                StringBuilder buf = new StringBuilder();
1597                try {
1598                        br = new BufferedReader( new InputStreamReader( new FileInputStream( file ), "UTF-8" ) );
1599                        xmlHeader = br.readLine();
1600                        while( ( tmp = br.readLine() ) != null ) { // 4.3.6.0 (2009/04/01)
1601                                buf.append( tmp );
1602                        }
1603                }
1604                catch( IOException ex ) {
1605                        queue.addMsg( "[ERROR]PARSE:Failed to read " + fileName + HybsSystem.CR );
1606                        throw new HybsSystemException( ex );
1607                }
1608                finally {
1609                        Closer.ioClose( br );
1610                }
1611
1612                String str = buf.toString();
1613                if( xmlHeader == null || xmlHeader.length() == 0 || str == null || str.length() == 0 ) {
1614                        queue.addMsg( "[ERROR]PARSE:Maybe " + fileName + " is Broken!" + HybsSystem.CR );
1615                        throw new HybsSystemException();
1616                }
1617
1618                return str;
1619        }
1620
1621        /**
1622         * XMLファイルを書き込みます。
1623         * OOoのXMLファイルは全て1行めがxml宣言で、2行目が内容全体という形式であるため、
1624         * ここでは、2行目の内容部分を渡すことで、XMLファイルを作成します。
1625         *
1626         * @param fileName 書き込むXMLファイル名
1627         * @param str 書き込む文字列
1628         */
1629        private void writeOOoXml( final String fileName, final String str ) {
1630                BufferedWriter bw = null;
1631                try {
1632                        bw = getWriter( fileName );
1633                        bw.write( xmlHeader );
1634                        bw.write( '\n' );
1635                        bw.write( str );
1636                        bw.flush();
1637                }
1638                catch( IOException ex  ) {
1639                        queue.addMsg( "[ERROR]PARSE:Failed to write " + fileName + HybsSystem.CR );
1640                        throw new HybsSystemException( ex );
1641                }
1642                finally {
1643                        Closer.ioClose( bw );
1644                }
1645        }
1646
1647        /**
1648         * XMLファイル書き込み用のライターを返します。
1649         *
1650         * @param fileName ファイル名
1651         *
1652         * @return ライター
1653         */
1654        private BufferedWriter getWriter( final String fileName ) {
1655                return getWriter( fileName, false );
1656        }
1657
1658        /**
1659         * XMLファイル書き込み用のライターを返します。
1660         *
1661         * @param fileName ファイル名
1662         * @param append アベンドするか
1663         *
1664         * @return ライター
1665         */
1666        private BufferedWriter getWriter( final String fileName, final boolean append ) {
1667                File file = new File ( fileName );
1668                BufferedWriter bw;
1669                try {
1670                        bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file, append ), "UTF-8" ) );
1671                }
1672                catch ( UnsupportedEncodingException ex ) {
1673                        queue.addMsg( "[ERROR] Input File is written by Unsupported Encoding" );
1674                        throw new HybsSystemException( ex );
1675                }
1676                catch ( FileNotFoundException ex ) {
1677                        queue.addMsg( "[ERROR] File not Found" );
1678                        throw new HybsSystemException( ex );
1679                }
1680                return bw;
1681        }
1682
1683        /**
1684         * ファイル名から拡張子(小文字)を求めます。
1685         *
1686         * @param fileName 拡張子を取得する為のファイル名
1687         *
1688         * @return 拡張子(小文字)
1689         */
1690        public static String getSuffix( final String fileName ) {
1691                String suffix = null;
1692                if( fileName != null ) {
1693                        int sufIdx = fileName.lastIndexOf( '.' );
1694                        if( sufIdx >= 0 ) {
1695                                suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
1696                        }
1697                }
1698                return suffix;
1699        }
1700}
1701