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