    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.util.ArrayList;
    019import java.util.List;
    020import java.util.Map;                                                                                           // 8.0.3.0 (2021/12/17)
    021import java.util.HashMap;                                                                                       // 8.0.3.0 (2021/12/17)
    022
    023import org.opengion.hayabusa.common.HybsSystemException;
    024import static org.opengion.fukurou.system.HybsConst.CR ;                        // 8.0.3.0 (2021/12/17)
    025// import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;   // 8.0.3.0 (2021/12/17)
    026
    027import org.opengion.hayabusa.report2.TagParser.SplitKey;
    028
    029/**
    030 * ｼｰﾄ単位のcontent.xmlを管理するためのｸﾗｽです｡
    031 * ｼｰﾄのﾍｯﾀﾞｰ､行の配列､ﾌｯﾀｰ及びｼｰﾄ名を管理します｡
    032 *
    033 * 7.0.1.5 (2018/12/10)
    034 *   FORMAT_LINEでは､同一行をｺﾋﾟｰするため､ｾﾙの選択行(A5とか$C7とか)までｺﾋﾟｰされ
    035 *   行ﾚﾍﾞﾙの計算が出来ません｡その場合は､INDIRECT(ADDRESS(ROW();列番号))関数を
    036 *   使用することでｾﾙのｱﾄﾞﾚｽが指定可能です｡
    037 *   列番号は､A=1 です｡
    038 *   ※ OpenOffice系は､区切り文字が『;』 EXCELの場合は､『,』 要注意
    039 *
    040 * ※ 繰り返しを使用する場合で､ﾍｯﾀﾞｰ部分の印刷領域を繰り返したい場合は､
    041 *    1.｢書式(O)｣-｢印刷範囲(N)｣-｢編集(E)｣で｢印刷範囲の編集｣ﾀﾞｲｱﾛｸﾞを表示
    042 *    2.｢繰り返す行｣の右の方にある矢印のついたﾎﾞﾀﾝをｸﾘｯｸ
    043 *    3.ﾍﾟｰｼﾞごとに表示したい行をﾄﾞﾗｯｸして指定する
    044 *        (ﾃｷｽﾄﾎﾞｯｸｽに$1:$2などと直接入力しても良い->$1:$2の場合であれば1-2行目が繰り返し印刷される)
    045 *
    046 * 8.0.3.0 (2021/12/17)
    047 *    {&#064;FORMATLINE} を指定した行は､BODY(GE51)行のﾌｫｰﾏｯﾄを指定できます｡
    048 *    {&#064;FORMATLINE_1}で､GE51のKBTEXT=B1 で指定した行をひな形にします｡
    049 *    {&#064;FORMATLINE_2}で､GE51のKBTEXT=B2 です｡
    050 *    引数の数字を指定しない場合は､KBTEXT=B です｡
    051 *    {&#064;DUMMYLINE} は､先のﾌｫｰﾏｯﾄ行をその行と交換して出力します｡
    052 *    ただし､ﾃﾞｰﾀが存在しない場合は､このDUMMYLINEそのものが使用されます｡
    053 *    {&#064;COPYLINE} は､先のﾌｫｰﾏｯﾄ行をﾃﾞｰﾀの数だけ繰り返しその場にｺﾋﾟｰします｡
    054 *    ｲﾒｰｼﾞ的には､DUMMYLINE は､1ﾍﾟｰｼﾞ分のﾌｫｰﾏｯﾄを指定する場合､COPYLINE は
    055 *    無制限の連続帳票を想定しています｡
    056 *
    057 * @og.group 帳票ｼｽﾃﾑ
    058 *
    059 * @version  4.0
    060 * @author   Hiroki.Nakamura
    061 * @since    JDK1.6
    062 */
    063class OdsSheet {
    064
    065        //======== content.xmlのﾊﾟｰｽで使用 ========================================
    066
    067        /* 行の開始終了ﾀｸﾞ */
    068        private static final String ROW_START_TAG = "<table:table-row ";
    069        private static final String ROW_END_TAG = "</table:table-row>";
    070
    071        /* ｼｰﾄ名を取得するための開始終了文字 */
    072        private static final String SHEET_NAME_START = "table:name=\"";
    073//      private static final String SHEET_NAME_END = "\"";
    074        private static final String END_KEY = "\"";                             // 8.0.3.0 (2021/12/17)
    075
    076        /* 変数定義の開始終了文字及び区切り文字 */
    077        private static final String VAR_START = "{@";
    078        private static final String VAR_END = "}";
    079//      private static final String VAR_CON = "_";
    080
    081        /* ﾌｫｰﾏｯﾄﾗｲﾝ文字列 5.0.0.2 (2009/09/15) */
    082        private static final String FORMAT_LINE = "FORMATLINE"; // 8.0.3.0 (2021/12/17)
    083
    084        /* ﾀﾞﾐｰﾗｲﾝ文字列 8.0.3.0 (2021/12/17) */
    085        private static final String DUMMY_LINE = "DUMMYLINE";   // 8.0.3.0 (2021/12/17)
    086
    087        /* ｺﾋﾟｰﾗｲﾝ文字列 8.0.3.0 (2021/12/17) */
    088        private static final String COPY_LINE = "COPYLINE";             // 8.0.3.0 (2021/12/17)
    089
    090        private final List<String>                      sheetRows       = new ArrayList<>();
    091        private final Map<String,String>        rowsMap         = new HashMap<>();
    092        private int                     offsetCnt = -1;         // 8.0.3.0 (2021/12/17) FORMAT_LINE が最初に現れた番号
    093        private String[]        bodyTypes ;                     // 8.0.3.0 (2021/12/17) 行番号に対応した､ﾎﾞﾃﾞｨﾀｲﾌﾟ(KBTEXT)配列
    094
    095        private String          sheetHeader;
    096        private String          sheetFooter;
    097        private String          sheetName;
    098        private String          origSheetName;
    099        private String          confSheetName;
    100
    101        /**
    102         * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
    103         *
    104         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    105         */
    106        public OdsSheet() { super(); }          // これも､自動的に呼ばれるが､空のﾒｿｯﾄﾞを作成すると警告されるので､明示的にしておきます｡
    107
    108        /**
    109         * ｼｰﾄを行単位に分解します｡
    110         *
    111         * @og.rev 5.0.0.2 (2009/09/15) ﾎﾞﾃﾞｨ部のｶｳﾝﾄを引数に追加し､LINECOPY機能実装｡
    112         * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
    113         * @og.rev 8.0.3.0 (2021/12/17) COPY_LINE機能の追加
    114         *
    115         * @param sheet ｼｰﾄ名
    116         * @param bodyTypes 行番号に対応した､ﾎﾞﾃﾞｨﾀｲﾌﾟ(KBTEXT)配列
    117         */
    118//      public void analyze( final String sheet, final int bodyRowCount ) {
    119        public void analyze( final String sheet, final String[] bodyTypes ) {
    120                this.bodyTypes = bodyTypes ;
    121
    122                final String[] tags = TagParser.tag2Array( sheet, ROW_START_TAG, ROW_END_TAG );
    123                sheetHeader = tags[0];
    124                sheetFooter = tags[1];
    125                for( int i=2; i<tags.length; i++ ) {
    126//                      sheetRows.add( tags[i] );
    127//                      lineCopy( tags[i], bodyRowCount );      // 5.0.0.2 (2009/09/15)
    128                        lineCopy( tags[i] );                            // 8.0.3.0 (2021/12/17)
    129                }
    130
    131//              sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, SHEET_NAME_END );
    132                sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, END_KEY );        // 8.0.3.0 (2021/12/17)
    133                origSheetName = sheetName;
    134
    135                confSheetName = null;
    136                if( sheetName != null ) {
    137                        final int cnfIdx = sheetName.indexOf( "__" );
    138                        if( cnfIdx > 0 && !sheetName.endsWith( "__" ) ) {
    139                                confSheetName = sheetName.substring( cnfIdx + 2 );
    140                                sheetName = sheetName.substring( 0, cnfIdx );
    141                        }
    142                }
    143        }
    144
    145        /**
    146         * ﾗｲﾝｺﾋﾟｰに関する処理を行います｡
    147         *
    148         * {&#064;LINECOPY}が存在した場合に､ﾃｰﾌﾞﾙﾓﾃﾞﾙ分だけ
    149         * 行をｺﾋﾟｰします｡
    150         * その際､{&#064;XXX_番号}の番号をｶｳﾝﾄｱｯﾌﾟしてｺﾋﾟｰします｡
    151         *
    152         * 整合性等のｴﾗｰﾊﾝﾄﾞﾘﾝｸﾞはこのﾒｿｯﾄﾞでは行わず､
    153         * 実際のﾊﾟｰｽ処理中で行います｡
    154         *
    155         * 7.0.1.5 (2018/12/10)
    156         *   LINECOPYでは､同一行をｺﾋﾟｰするため､ｾﾙの選択行(A5とか$C7とか)までｺﾋﾟｰされ
    157         *   行ﾚﾍﾞﾙの計算が出来ません｡その場合は､INDIRECT(ADDRESS(ROW(),列番号))関数を
    158         *   使用することでｾﾙのｱﾄﾞﾚｽが指定可能です｡
    159         *   列番号は､A=1 です｡
    160         *
    161         * @og.rev 5.0.0.2 (2009/09/15) 追加
    162         * @og.rev 5.1.8.0 (2010/07/01) ﾊﾟｰｽ処理の内部実装を変更
    163         * @og.rev 7.0.1.5 (2018/12/10) LINECOPYでの注意(JavaDocのみ追記)
    164         * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します｡
    165         * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
    166         *
    167         * @param rowStr        行ﾃﾞｰﾀ
    168         * @param rowCount      ｶｳﾝﾀ
    169         */
    170//      private void lineCopy( final String rowStr, final int rowCount ) {
    171        private void lineCopy( final String rowStr ) {
    172                // FORMAT_LINE を見つけて､引数をｷｰにﾏｯﾌﾟに登録します｡
    173                final String cpLin = TagParser.splitSufix( rowStr,FORMAT_LINE );
    174                if( cpLin != null ) {
    175                        if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }   // 初めてあらわれた位置
    176                        final String tmp = rowsMap.get( "B" + cpLin );
    177                        if( tmp == null ) {
    178                                rowsMap.put( "B" + cpLin , rowStr );            // ﾌｫｰﾏｯﾄのｷｰは､"B" + ｻﾌｨｯｸｽ
    179        //                      sheetRows.add( rowStr );                                        // 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
    180                        }
    181                        else {
    182                                // ｾﾙ結合時に､複数行を1行に再設定する｡
    183                                rowsMap.put( "B" + cpLin , tmp + rowStr );      // ﾌｫｰﾏｯﾄのｷｰは､"B" + ｻﾌｨｯｸｽ
    184                        }
    185                        return;
    186                }
    187
    188                // DUMMY_LINE を見つける｡
    189                final int st1 = rowStr.indexOf( VAR_START + DUMMY_LINE );
    190                if( st1 >= 0 ) {                                                // ｷｰが見つかった場合
    191                        if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }   // 初めてあらわれた位置
    192                        sheetRows.add( rowStr );                        // DUMMY_LINE を登録
    193                        return ;
    194                }
    195
    196                // COPY_LINE を見つける｡
    197                final int st2 = rowStr.indexOf( VAR_START + COPY_LINE );
    198                if( st2 >= 0 ) {                                                // ｷｰが見つかった場合
    199                        if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }   // 初めてあらわれた位置
    200
    201                        // COPY_LINEは､その場に全件ｺﾋﾟｰします(行数を確保するため)
    202                        for( int row=0; row<bodyTypes.length; row++ ) {
    203                                sheetRows.add( rowStr );                // COPY_LINE を登録
    204                        }
    205                        return ;
    206                }
    207
    208                sheetRows.add( rowStr );                                // rowStr を登録(通常行)
    209
    210//              // この段階で存在しなければ即終了
    211//              final int lcStr = row.indexOf( VAR_START + LINE_COPY );
    212////            if( lcStrOffset < 0 ) { return; }
    213//              if( lcStr < 0 ) { sheetRows.add( row ); return 1; }
    214//              final int lcEnd = row.indexOf( VAR_END, lcStr );
    215////            if( lcEndOffset < 0 ) { return; }
    216//              if( lcEnd < 0 ) { sheetRows.add( row ); return 1; }
    217//
    218//              final StringBuilder lcStrBuf = new StringBuilder( row );
    219//              final String lcKey = TagParser.checkKey( row.substring( lcStr + VAR_START.length(), lcEnd ), lcStrBuf );
    220////            if( lcKey == null || !LINE_COPY.equals( lcKey ) ) { return; }
    221//              final SplitKey cpKey = new SplitKey( lcKey );           // 8.0.3.0 (2021/12/17)
    222//              final int copyCnt = cpKey.count( rowCount );
    223
    224//              // 存在すればﾃｰﾌﾞﾙﾓﾃﾞﾙ行数-1回ﾙｰﾌﾟ(自身を除くため)
    225//              for( int i=1; i<rowCount; i++ ) {
    226//              // 存在すればﾃｰﾌﾞﾙﾓﾃﾞﾙ行数回ﾙｰﾌﾟ(自身も含める必要がある)
    227//              for( int i=0; i<copyCnt; i++ ) {                                        // {@LINECOPY_回数} で､繰り返し回数指定
    228//                      final int cRow = i;                                                             // final 宣言しないと無名ｸﾗｽに設定できない｡
    229//                      final String rowStr = new TagParser() {
    230//                              /**
    231//                               * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を定義します｡
    232//                               *
    233//                               * @param str 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列(開始ﾀｸﾞ･終了ﾀｸﾞを含む)
    234//                               * @param buf 出力を行う文字列ﾊﾞｯﾌｧ
    235//                               * @param offset 終了ﾀｸﾞのｵﾌｾｯﾄ(ここでは使っていません)
    236//                               */
    237//                              @Override
    238//                              protected void exec( final String str, final StringBuilder buf, final int offset ) {
    239//                                      String key = TagParser.checkKey( str, buf );
    240//                                      if( key.indexOf( '<' ) >= 0 ){
    241//                                              final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です｡" + CR
    242//                                                                                      + "変数内の特定の文字列に書式設定がされている可能性があります｡ｷｰ=" + key;
    243//                                              throw new HybsSystemException( errMsg );
    244//                                      }
    245//                                      final SplitKey spKey = new SplitKey( key );             // 8.0.3.0 (2021/12/17)
    246////                                    buf.append( VAR_START ).append( incrementKey( key, cRow ) ).append( VAR_END );
    247//                                      buf.append( VAR_START ).append( spKey.incrementKey( cRow ) ).append( VAR_END );
    248//                              }
    249//                      }.doParse( lcStrBuf.toString(), VAR_START, VAR_END, false );
    250//
    251//                      sheetRows.add( rowStr );
    252//              }
    253//              return copyCnt;
    254        }
    255
    256//      /**
    257//       * XXX_番号の番号部分を引数分追加して返します｡
    258//       * 番号部分が数字でない場合や､_が無い場合はそのまま返します｡
    259//       *
    260//       * @og.rev 5.0.0.2 (2009/09/15) LINE_COPYで利用するために追加
    261//       * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します｡
    262//       *
    263//       * @param key   ｷｰ文字列
    264//       * @param inc   ｶｳﾝﾀ部
    265//       *
    266//       * @return 変更後ｷｰ
    267//       */
    268//      private String incrementKey( final String key, final int inc ) {
    269//              final int conOffset = key.lastIndexOf( VAR_CON );
    270//              if( conOffset < 0 ) { return key; }
    271//
    272//              final String name = key.substring( 0, conOffset );
    273//              int rownum = -1;
    274//              try {
    275//                      rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) );               // 6.0.2.4 (2014/10/17) ﾒｿｯﾄﾞ間違い
    276//              }
    277//              // ｴﾗｰが起きてもなにもしない｡
    278//              catch( final NumberFormatException ex ) {
    279//                      // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks
    280//                      final String errMsg = "Not incrementKey. KEY=[" + key + "] " + ex.getMessage() ;
    281//                      System.err.println( errMsg );
    282//              }
    283//
    284//              // ｱﾝﾀﾞｰｽｺｱ後が数字に変換できない場合はﾍｯﾀﾞﾌｯﾀとして認識
    285//              if( rownum < 0 ){ return key; }
    286//              else                    { return name + VAR_CON + (rownum + inc) ; }
    287//      }
    288
    289        /**
    290         * ｼｰﾄのﾍｯﾀﾞｰ部分を返します｡
    291         *
    292         * @return ﾍｯﾀﾞｰ
    293         */
    294        public String getHeader() {
    295                return sheetHeader;
    296        }
    297
    298        /**
    299         * ｼｰﾄのﾌｯﾀｰ部分を返します｡
    300         *
    301         * @return ﾌｯﾀｰ
    302         */
    303        public String getFooter() {
    304                return sheetFooter;
    305        }
    306
    307        /**
    308         * ｼｰﾄ名称を返します｡
    309         *
    310         * @return ｼｰﾄ名称
    311         */
    312        public String getSheetName() {
    313                return sheetName;
    314        }
    315
    316        /**
    317         * 定義済ｼｰﾄ名称を返します｡
    318         *
    319         * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
    320         *
    321         * @return 定義済ｼｰﾄ名称
    322         */
    323        public String getConfSheetName() {
    324                return confSheetName;
    325        }
    326
    327        /**
    328         * 定義名変換前のｼｰﾄ名称を返します｡
    329         *
    330         * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
    331         *
    332         * @return 定義済ｼｰﾄ名称
    333         */
    334        public String getOrigSheetName() {
    335                return origSheetName;
    336        }
    337
    338//      /**
    339//       * ｼｰﾄの各行を配列で返します｡
    340//       *
    341//       * @og.rev 4.3.1.1 (2008/08/23) あらかじめ､必要な配列の長さを確保しておきます｡
    342//       * @og.rev 8.0.3.0 (2021/12/17) 廃止
    343//       *
    344//       * @return ｼｰﾄの各行の配列
    345//       * @og.rtnNotNull
    346//       */
    347//      public String[] getRows() {
    348//              return sheetRows.toArray( new String[sheetRows.size()] );
    349//      }
    350
    351        /**
    352         * ｼｰﾄの行を返します｡
    353         *
    354         * @og.rev 8.0.3.0 (2021/12/17) 新規追加
    355         * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
    356         * @og.rev 8.4.0.0 (2022/11/11) DUMMYLINE改善(複数のﾌｫｰﾏｯﾄについて複数行に跨って縦線がﾏﾁﾏﾁあるﾊﾟﾀｰﾝ)(問合・ﾄﾗﾌﾞﾙ 43100-221109-01)
    357         *
    358         * @param idx           ｼｰﾄ内での行番号
    359         * @param baseRow       TableModelのﾍﾞｰｽ行番号
    360         *
    361         * @return ｼｰﾄの行
    362         * @og.rtnNotNull
    363         */
    364        public String getRow( final int idx, final int baseRow ) {
    365                final String rowStr = sheetRows.get( idx );
    366
    367//              8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
    368//              final boolean useFmt = rowStr.contains( VAR_START + FORMAT_LINE )
    369//                                                      || rowStr.contains( VAR_START + DUMMY_LINE )
    370//                                                      || rowStr.contains( VAR_START + COPY_LINE ) ;
    371
    372                final boolean useFmtD = rowStr.contains( VAR_START + DUMMY_LINE );
    373                final boolean useFmtC = rowStr.contains( VAR_START + COPY_LINE ) ;
    374
    375                if( useFmtD || useFmtC ) {                                                                                              // ｷｰが見つかった場合
    376                        final int row = idx-offsetCnt+baseRow;
    377
    378//                      final String dummy = row < bodyTypes.length                                                     // 配列overﾁｪｯｸ
    379//                                                      ? rowsMap.getOrDefault( bodyTypes[row],rowStr )         // 存在しなかった場合の処置
    380//                                                      : rowStr ;
    381                        // 8.4.0.0 (2022/11/11) DUMMYLINE改善
    382                        final String dummy;
    383                        // FORMATLINEが1種類のDUMMYLINE使用
    384                        if( useFmtD && rowsMap.size() == 1 ) {
    385                                dummy = rowsMap.get( "B" ) ;
    386                        }
    387                        // FORMATLINEが複数のDUMMYLINE使用 or COPYLINE使用
    388                        else {
    389                                dummy = row < bodyTypes.length                                                                  // 配列overﾁｪｯｸ
    390                                                ? rowsMap.getOrDefault( bodyTypes[row],rowStr )                 // 存在しなかった場合の処置
    391                                                : rowStr ;
    392                        }
    393
    394                        return new TagParser() {
    395                                /**
    396                                 * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を定義します｡
    397                                 *
    398                                 * @param str 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列(開始ﾀｸﾞ･終了ﾀｸﾞを含む)
    399                                 * @param buf 出力を行う文字列ﾊﾞｯﾌｧ
    400                                 * @param offset 終了ﾀｸﾞのｵﾌｾｯﾄ(ここでは使っていません)
    401                                 */
    402                                @Override
    403                                protected void exec( final String str, final StringBuilder buf, final int offset ) {
    404                                        final String key = TagParser.checkKey( str, buf );
    405                                        if( key.indexOf( '<' ) >= 0 ){
    406                                                final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です｡" + CR
    407                                                                                        + "変数内の特定の文字列に書式設定がされている可能性があります｡ｷｰ=" + key;
    408                                                throw new HybsSystemException( errMsg );
    409                                        }
    410                                        final SplitKey spKey = new SplitKey( key );             // 8.0.3.0 (2021/12/17)
    411                                        buf.append( VAR_START ).append( spKey.incrementKey( idx-offsetCnt ) ).append( VAR_END );
    412                                }
    413                        }.doParse( dummy, VAR_START, VAR_END, false );
    414                }
    415                return rowStr;
    416        }
    417
    418        /**
    419         * ｼｰﾄに含まれている行数を返します｡
    420         *
    421         * @og.rev 8.0.3.0 (2021/12/17) 新規追加
    422         *
    423         * @return ｼｰﾄに含まれている行数
    424         */
    425        public int getRowCnt() {
    426                return sheetRows.size();
    427        }
    428}