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