    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;
    020
    021import org.opengion.hayabusa.common.HybsSystemException;
    022import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    023import static org.opengion.fukurou.system.HybsConst.CR ;                        // 8.0.3.0 (2021/12/17)
    024
    025/**
    026 * Calc帳票ｼｽﾃﾑでﾀｸﾞのﾊﾟｰｽを行うためのｸﾗｽです｡
    027 *
    028 * 主に開始ﾀｸﾞ､終了ﾀｸﾞを指定したﾊﾟｰｽのﾙｰﾌﾟ処理を行うための機能を提供します｡
    029 * 具体的には､{@link #doParse(String, String, String)}により､ﾊﾟｰｽ文字列､開始ﾀｸﾞ､終了ﾀｸﾞを
    030 * 指定し､ﾊﾟｰｽを行います｡
    031 * ﾊﾟｰｽ後の文字列は､{@link #doParse(String, String, String)}の戻り値になります｡
    032 *
    033 * ﾊﾟｰｽ実行中に､発見された開始ﾀｸﾞから終了ﾀｸﾞまでの間の文字列の処理は､{@link #exec(String, StringBuilder, int)}を
    034 * ｵｰﾊﾞｰﾗｲﾄﾞすることにより定義します｡
    035 *
    036 * また､このｸﾗｽでは､ﾊﾟｰｽに必要な各種ﾕｰﾃｨﾘﾃｨﾒｿｯﾄﾞについても同様に定義されています｡
    037 *
    038 * @og.group 帳票ｼｽﾃﾑ
    039 *
    040 * @version  4.0
    041 * @author   Hiroki.Nakamura
    042 * @since    JDK1.6
    043 */
    044class TagParser {
    045        private static final String VAR_START = "{@";           // 8.0.3.0 (2021/12/17) splitSufix で使います。
    046        private static final char   VAR_END   = '}';            // 8.0.3.0 (2021/12/17) splitSufix で使います。
    047        private static final char   VAR_CON   = '_';            // 8.0.3.0 (2021/12/17) splitSufix,SplitKey で使います。
    048
    049        private int preOffset   ;
    050        private int curOffset   ;
    051
    052        /**
    053         * ﾊﾟｰｽ処理を行います｡
    054         *
    055         * ﾊﾟｰｽ中に取り出された開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理は､
    056         * {@link #exec(String, StringBuilder, int)}で定義します｡
    057         *
    058         * また､isAddTagをtrueにした場合､{@link #exec(String, StringBuilder, int)}に渡される
    059         * 文字列に､開始ﾀｸﾞ､終了ﾀｸﾞが含まれます｡
    060         * 逆にfalseにした場合は､開始ﾀｸﾞ､終了ﾀｸﾞを除き､{@link #exec(String, StringBuilder, int)}に渡されます｡
    061         *
    062         * @og.rev 5.2.2.0 (2010/11/01) 読み飛ばしをした場合に､開始ﾀｸﾞが書き込まれないﾊﾞｸﾞを修正
    063         *
    064         * @param content ﾊﾟｰｽ対象文字列
    065         * @param startTag 開始ﾀｸﾞ
    066         * @param endTag 終了ﾀｸﾞ
    067         * @param isAddTag 開始ﾀｸﾞ･終了ﾀｸﾞを含むか
    068         *
    069         * @return ﾊﾟｰｽ後の文字列
    070         * @og.rtnNotNull
    071         * @see #exec(String, StringBuilder, int)
    072         */
    073        public String doParse( final String content, final String startTag, final String endTag, final boolean isAddTag ) {
    074                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    075
    076                while( ( preOffset = content.indexOf( startTag, Math.max( preOffset, curOffset ) ) ) >= 0 ) {
    077                        buf.append( content.substring( curOffset, preOffset ) );
    078                        curOffset = content.indexOf( endTag, preOffset + startTag.length() );
    079
    080                        if( checkIgnore( preOffset, curOffset ) ) {
    081                                if( curOffset < 0 ){
    082                                        final String errMsg = "[ERROR]PARSE:開始ﾀｸﾞを終了ﾀｸﾞの整合性が不正です｡" + CR
    083                                                                                + "[開始ﾀｸﾞ=" + startTag + ":終了ﾀｸﾞ=" + endTag + "]";
    084                                        throw new HybsSystemException( errMsg );
    085                                }
    086                                preOffset += startTag.length();
    087                                curOffset += endTag.length();
    088
    089                                String str = null;
    090                                if( isAddTag ) {
    091                                        str = content.substring( preOffset - startTag.length(), curOffset );
    092                                }
    093                                else {
    094                                        str = content.substring( preOffset, curOffset - endTag.length() );
    095                                }
    096
    097                                exec( str, buf, curOffset );
    098                        }
    099                        else {
    100                                // 5.2.2.0 (2010/11/01) 開始ﾀｸﾞが書き込まれないﾊﾞｸﾞを修正
    101                                buf.append( startTag );
    102                                preOffset += startTag.length();
    103                                curOffset = preOffset;
    104                        }
    105                }
    106                buf.append( content.substring( curOffset, content.length() ) );
    107
    108                return buf.toString();
    109        }
    110
    111        /**
    112         * ﾊﾟｰｽ処理を行います｡
    113         *
    114         * 詳細は､{@link #doParse(String, String, String, boolean)}のJavadocを参照して下さい｡
    115         *
    116         * @param content ﾊﾟｰｽ対象文字列
    117         * @param startTag 開始ﾀｸﾞ
    118         * @param endTag 終了ﾀｸﾞ
    119         *
    120         * @return ﾊﾟｰｽ後の文字列
    121         * @see #doParse(String, String, String, boolean)
    122         */
    123        public String doParse( final String content, final String startTag, final String endTag ) {
    124                return doParse( content, startTag, endTag, true );
    125        }
    126
    127        /**
    128         * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を定義します｡
    129         *
    130         * この実装では､何も処理を行いません｡(切り出した文字列はｱﾍﾟﾝﾄﾞされません)
    131         * ｻﾌﾞｸﾗｽでｵｰﾊﾞｰﾗｲﾄﾞして実際の処理を実装して下さい｡
    132         *
    133         * @param str 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列(開始ﾀｸﾞ･終了ﾀｸﾞを含む)
    134         * @param buf 出力を行う文字列ﾊﾞｯﾌｧ
    135         * @param offset 終了ﾀｸﾞのｵﾌｾｯﾄ
    136         */
    137        protected void exec( final String str, final StringBuilder buf, final int offset ) {
    138                // Document empty method 対策
    139        }
    140
    141        /**
    142         * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を実行するかどうかを定義します｡
    143         *
    144         * falseが返された場合､何も処理されず({@link #exec(String, StringBuilder, int)}が実行されない)､
    145         * 元の文字列がそのまま出力されます｡
    146         *
    147         * @param strOffset 開始ﾀｸﾞのｵﾌｾｯﾄ
    148         * @param endOffset 終了ﾀｸﾞのｵﾌｾｯﾄ
    149         *
    150         * @return 処理を行うかどうか(true:処理を行う false:処理を行わない)
    151         */
    152        protected boolean checkIgnore( final int strOffset, final int endOffset ) {
    153                return true;
    154        }
    155
    156        /**
    157         * ﾊﾟｰｽ実行中のoffset値を外部からｾｯﾄします｡
    158         *
    159         * このﾒｿｯﾄﾞは､{@link #exec(String, StringBuilder, int)}で､処理結果により､offset値を
    160         * 進めておく必要がある場合に利用されます｡(つまり通常は利用する必要はありません)
    161         *
    162         * @param offset ｵﾌｾｯﾄ
    163         * @see #exec(String, StringBuilder, int)
    164         */
    165        public void setOffset( final int offset ) {
    166                curOffset = offset;
    167        }
    168
    169        /**
    170         * 引数の文字列を指定された開始ﾀｸﾞ､終了ﾀｸﾞで解析し配列として返す､ﾕｰﾃｨﾘﾃｨﾒｿｯﾄﾞです｡
    171         *
    172         * 開始ﾀｸﾞより前の文字列は0番目に､終了ﾀｸﾞより後の文字列は1番目に格納されます｡
    173         * 2番目以降に､開始ﾀｸﾞ､終了ﾀｸﾞの部分が格納されます｡
    174         *
    175         * @param str           解析する文字列
    176         * @param startTag      開始ﾀｸﾞ
    177         * @param endTag        終了ﾀｸﾞ
    178         *
    179         * @return 解析結果の配列
    180         */
    181        public static String[] tag2Array( final String str, final String startTag, final String endTag ) {
    182                String header = null;
    183                String footer = null;
    184                final List<String> body = new ArrayList<>();
    185
    186                int preOffset = -1;
    187                int curOffset = 0;
    188
    189                while( true ) {
    190                        curOffset = str.indexOf( startTag, preOffset + 1 );
    191                        if( curOffset < 0 ) {
    192                                curOffset = str.lastIndexOf( endTag ) + endTag.length();
    193                                body.add( str.substring( preOffset, curOffset ) );
    194
    195                                footer = str.substring( curOffset );
    196                                break;
    197                        }
    198                        else if( preOffset == -1 ) {
    199                                header = str.substring( 0, curOffset );
    200                        }
    201                        else {
    202                                body.add( str.substring( preOffset, curOffset ) );
    203                        }
    204                        preOffset = curOffset;
    205                }
    206
    207                String[] arr = new String[body.size()+2];
    208                arr[0] = header;
    209                arr[1] = footer;
    210                for( int i=0; i<body.size(); i++ ) {
    211                        arr[i+2] = body.get(i);
    212                }
    213
    214                return arr;
    215        }
    216
    217        /**
    218         * 引数の文字列の開始文字と終了文字の間の文字列を取り出す､ﾕｰﾃｨﾘﾃｨﾒｿｯﾄﾞです｡
    219         * ※返される文字列に､開始文字､終了文字は含まれません｡
    220         *
    221         * @param str   解析する文字列
    222         * @param start 開始文字列
    223         * @param end   終了文字列
    224         *
    225         * @return 解析結果の文字
    226         */
    227        public static String getValueFromTag( final String str, final String start, final String end ) {
    228                int startOffset = str.indexOf( start );
    229                // 4.2.4.0 (2008/06/02) 存在しない場合はnullで返す
    230                if( startOffset == -1 ) {
    231                        return null;
    232                }
    233                startOffset += start.length();
    234
    235                final int endOffset = str.indexOf( end, startOffset );
    236//              final String value = str.substring( startOffset, endOffset );
    237
    238                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    239                return str.substring( startOffset, endOffset );
    240
    241//              return value;
    242        }
    243
    244        /**
    245         * 引数のｷｰから不要なｷｰを取り除く､ﾕｰﾃｨﾘﾃｨﾒｿｯﾄﾞです｡
    246         *
    247         * @og.rev 5.1.8.0 (2010/07/01) spanﾀｸﾞを削除
    248         *
    249         * @param key   ｵﾘｼﾞﾅﾙのｷｰ
    250         * @param sb    ｷｰの外に含まれるaﾀｸﾞを削除するための､ﾊﾞｯﾌｧ
    251         *
    252         * @return 削除後のｷｰ
    253         * @og.rtnNotNull
    254         */
    255        public static String checkKey( final String key, final StringBuilder sb ) {
    256                if( key.indexOf( '<' ) < 0 && key.indexOf( '>' ) < 0 ) { return key; }
    257
    258                final StringBuilder rtn = new StringBuilder( key );
    259                final String tagEnd = ">";
    260                int rtnOffset = -1;
    261
    262                // <text:a ...>{@XXX</text:a>の不要ﾀｸﾞを削除
    263                final String delTagStart1 = "<text:a ";
    264                final String delTagEnd1 = "</text:a>";
    265                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd1 ) ) >= 0 ) {
    266                        boolean isDel = false;
    267                        // ｷｰ自身に含まれるaﾀｸﾞを削除
    268                        int startOffset = rtn.lastIndexOf( delTagStart1, rtnOffset );
    269                        if( startOffset >= 0 ) {
    270                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    271                                if( endOffset >= 0 ) {
    272                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    273                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    274                                        isDel = true;
    275                                }
    276                        }
    277                        else {
    278                                // ｷｰの外に含まれるaﾀｸﾞを削除
    279                                startOffset = sb.lastIndexOf( delTagStart1 );
    280                                if( startOffset >= 0 ) {
    281                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    282                                        if( endOffset >= 0 ) {
    283                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    284                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    285                                                isDel = true;
    286                                        }
    287                                }
    288                        }
    289                        if( !isDel ) { break; }
    290                }
    291
    292                // 5.1.8.0 (2010/07/01) spanﾀｸﾞを削除
    293                final String delTagStart2 = "<text:span ";
    294                final String delTagEnd2 = "</text:span>";
    295                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd2 ) ) >= 0 ) {
    296                        boolean isDel = false;
    297                        // ｷｰ自身に含まれるspanﾀｸﾞを削除
    298                        int startOffset = rtn.lastIndexOf( delTagStart2, rtnOffset );
    299                        if( startOffset >= 0 ) {
    300                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    301                                if( endOffset >= 0 ) {
    302                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    303                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    304                                        isDel = true;
    305                                }
    306                        }
    307                        else {
    308                                // ｷｰの外に含まれるspanﾀｸﾞを削除
    309                                startOffset = sb.lastIndexOf( delTagStart2 );
    310                                if( startOffset >= 0 ) {
    311                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    312                                        if( endOffset >= 0 ) {
    313                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    314                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    315                                                isDel = true;
    316                                        }
    317                                }
    318                        }
    319                        if( !isDel ) { break; }
    320                }
    321
    322                return rtn.toString();
    323        }
    324
    325        /**
    326         * "{&#064;" + key + '_' と、'}' の間の文字列を返します。
    327         *
    328         * '_' を含まない場合は、ゼロ文字列を返します。
    329         * ここでは、簡易的に処理しているため、タグ等の文字列が含まれる場合は、
    330         * 上手くいかない可能性があります。
    331         *
    332         * "{&#064;" + key + '_' と、'}' の間の文字列を返します。
    333         * '_' が存在しない場合は、空文字列を返します。
    334         * "{&#064;" + key が存在しない場合は、null を返します。
    335         * "{&#064;" + key が存在しており、'}' が存在しない場合は、Exception が throw されます。
    336         *
    337         * @og.rev 8.0.3.0 (2021/12/17) 新規追加
    338         *
    339         * @param row   検索元の文字列
    340         * @param key   検索対象のｷｰ
    341         *
    342         * @return "{&#064;" + key + '_' と、'}' の間の文字列を返します。
    343         */
    344        public static String splitSufix( final String row,final String key ) {
    345                final int st1 = row.indexOf( VAR_START + key );                         // "{@" + key
    346                if( st1 >= 0 ) {
    347                        final int ed1 = row.indexOf( VAR_END,st1 );                             // '}' を探す
    348                        if( ed1 >= 0 ) {
    349                                final int st2 = row.lastIndexOf​( VAR_CON,ed1 );        // '_' を逆順で探す
    350                                if( st2 < 0 ) {         // '_' が無い場合は、空文字列を返す。
    351                                        return "";
    352                                }
    353                                else {
    354                                        return row.substring( st2+1,ed1 );                              // '_'の次の文字から、'}' 手前まで
    355                                }
    356                        }
    357                        else {
    358                                final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です｡" + CR
    359                                                                        + "変数内の特定の文字列に書式設定がされている可能性があります｡ｷｰ=" + key;
    360                                throw new HybsSystemException( errMsg );
    361                        }
    362                }
    363                return null;
    364        }
    365
    366        /**
    367         * ｱﾝﾀﾞｰﾊﾞｰで､ｷｰと行番号の分離を行います｡
    368         *
    369         * @og.rev 8.0.3.0 (2021/12/17) ｱﾝﾀﾞｰﾊﾞｰで､ｷｰと行番号の分離を､ｲﾝﾅｰｸﾗｽ化します｡
    370         */
    371        /* default */ static final class SplitKey {
    372                /** 分割後のｷｰ */
    373                public final String name ;
    374                /** 分割後の行番号 */
    375                public final int        rownum ;
    376
    377                /**
    378                 * ｺﾝｽﾄﾗｸﾀで､分割､設定
    379                 *
    380                 * @param key 分割処理対象のｷｰ
    381                 */
    382                public SplitKey( final String key ) {
    383                        final int idx = key.lastIndexOf( VAR_CON );
    384
    385                        int num = -1;
    386                        if( idx >= 0 ) {
    387                                try {
    388                                        num = Integer.parseInt( key.substring( idx+1 ) );
    389                                }
    390                                // '_'以降の文字が数字でない場合は､'_'以降の文字もｶﾗﾑ名の一部として扱う
    391                                catch( final NumberFormatException ex ) {
    392                                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks
    393                                        final String errMsg = "'_'以降の文字をｶﾗﾑ名の一部として扱います｡" + CR
    394                                                                                        + "ｶﾗﾑ名=[" + key + "]" + CR
    395                                                                                        + ex.getMessage() ;
    396                                        System.err.println( errMsg );
    397                                }
    398                        }
    399                        if( num >= 0 ) {
    400                                name   = key.substring( 0, idx );
    401                                rownum = num ;
    402                        }
    403                        else {
    404                                name   = key;
    405                                rownum = num ;
    406                        }
    407                }
    408
    409                /**
    410                 * XXX_番号の番号部分を引数分追加して返します｡
    411                 * 番号部分が数字でない場合や､_が無い場合はそのまま返します｡
    412                 *
    413                 * @param inc   ｶｳﾝﾀ部
    414                 *
    415                 * @return 変更後ｷｰ
    416                 */
    417                public String incrementKey( final int inc ) {
    418                        return rownum < 0 ? name : name + VAR_CON + (rownum + inc) ;
    419//                      return new StringBuilder().append(name).append(VAR_CON).append( rownum+inc ).toString();
    420                }
    421
    422//              /**
    423//               * rownumが無効(-1)ならcntを､有効なら､rownumを返します｡
    424//               *
    425//               * @param cnt   ﾃﾞﾌｫﾙﾄのｶｳﾝﾄ値
    426//               *
    427//               * @return rownumか､引数のcntを返します｡
    428//               */
    429//              public int count( final int cnt ) {
    430//                      return rownum < 0 ? cnt : rownum;
    431//              }
    432        }
    433}