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