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