    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_****" の場合は、"_" を外す。長さ０判定の前に行う。
    494                                if( sheetName.startsWith( "_" ) ) { sheetName = sheetName.substring( 1 ); }
    495                                // 長さ０の場合（例えば、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         *  ②前に<table:table-cell(セルの始まり)が<draw:(オブジェクトの始まり)より後方にある
    850         *  ③後に</draw:(オブジェクトの終わり)が見つからない
    851         *  ④後に</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