    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
    023
    024/**
    025 * Calc帳票システムでタグのパースを行うためのクラスです。
    026 *
    027 * 主に開始タグ、終了タグを指定したパースのループ処理を行うための機能を提供します。
    028 * 具体的には、{@link #doParse(String, String, String)}により、パース文字列、開始タグ、終了タグを
    029 * 指定し、パースを行います。
    030 * パース後の文字列は、{@link #doParse(String, String, String)}の戻り値になります。
    031 *
    032 * パース実行中に、発見された開始タグから終了タグまでの間の文字列の処理は、{@link #exec(String, StringBuilder, int)}を
    033 * オーバーライドすることにより定義します。
    034 *
    035 * また、このクラスでは、パースに必要な各種ユーティリティメソッドについても同様に定義されています。
    036 *
    037 * @og.group 帳票システム
    038 *
    039 * @version  4.0
    040 * @author   Hiroki.Nakamura
    041 * @since    JDK1.6
    042 */
    043class TagParser {
    044
    045        private int preOffset   ;
    046        private int curOffset   ;
    047
    048        /**
    049         * パース処理を行います。
    050         *
    051         * パース中に取り出された開始タグから終了タグまでの文字列の処理は、
    052         * {@link #exec(String, StringBuilder, int)}で定義します。
    053         *
    054         * また、isAddTagをtrueにした場合、{@link #exec(String, StringBuilder, int)}に渡される
    055         * 文字列に、開始タグ、終了タグが含まれます。
    056         * 逆にfalseにした場合は、開始タグ、終了タグを除き、{@link #exec(String, StringBuilder, int)}に渡されます。
    057         *
    058         * @og.rev 5.2.2.0 (2010/11/01) 読み飛ばしをした場合に、開始タグが書き込まれないバグを修正
    059         *
    060         * @param content パース対象文字列
    061         * @param startTag 開始タグ
    062         * @param endTag 終了タグ
    063         * @param isAddTag 開始タグ・終了タグを含むか
    064         *
    065         * @return パース後の文字列
    066         * @og.rtnNotNull
    067         * @see #exec(String, StringBuilder, int)
    068         */
    069        public String doParse( final String content, final String startTag, final String endTag, final boolean isAddTag ) {
    070                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    071                String errMsg = null;
    072
    073                while( ( preOffset = content.indexOf( startTag, Math.max( preOffset, curOffset ) ) ) >= 0 ) {
    074                        buf.append( content.substring( curOffset, preOffset ) );
    075                        curOffset = content.indexOf( endTag, preOffset + startTag.length() );
    076
    077                        if( checkIgnore( preOffset, curOffset ) ) {
    078                                if( curOffset < 0 ){
    079                                        errMsg = "[ERROR]PARSE:開始タグを終了タグの整合性が不正です。[開始タグ=" + startTag + ":終了タグ=" + endTag + "]";
    080                                        throw new HybsSystemException( errMsg );
    081                                }
    082                                preOffset += startTag.length();
    083                                curOffset += endTag.length();
    084
    085                                String str = null;
    086                                if( isAddTag ) {
    087                                        str = content.substring( preOffset - startTag.length(), curOffset );
    088                                }
    089                                else {
    090                                        str = content.substring( preOffset, curOffset - endTag.length() );
    091                                }
    092
    093                                exec( str, buf, curOffset );
    094                        }
    095                        else {
    096                                // 5.2.2.0 (2010/11/01) 開始タグが書き込まれないバグを修正
    097                                buf.append( startTag );
    098                                preOffset += startTag.length();
    099                                curOffset = preOffset;
    100                        }
    101                }
    102                buf.append( content.substring( curOffset, content.length() ) );
    103
    104                return buf.toString();
    105        }
    106
    107        /**
    108         * パース処理を行います。
    109         *
    110         * 詳細は、{@link #doParse(String, String, String, boolean)}のJavadocを参照して下さい。
    111         *
    112         * @param content パース対象文字列
    113         * @param startTag 開始タグ
    114         * @param endTag 終了タグ
    115         *
    116         * @return パース後の文字列
    117         * @see #doParse(String, String, String, boolean)
    118         */
    119        public String doParse( final String content, final String startTag, final String endTag ) {
    120                return doParse( content, startTag, endTag, true );
    121        }
    122
    123        /**
    124         * 開始タグから終了タグまでの文字列の処理を定義します。
    125         *
    126         * この実装では、何も処理を行いません。(切り出した文字列はアペンドされません)
    127         * サブクラスでオーバーライドして実際の処理を実装して下さい。
    128         *
    129         * @param str 開始タグから終了タグまでの文字列(開始タグ・終了タグを含む)
    130         * @param buf 出力を行う文字列バッファ
    131         * @param offset 終了タグのオフセット
    132         */
    133        protected void exec( final String str, final StringBuilder buf, final int offset ) {
    134                // Document empty method 対策
    135        }
    136
    137        /**
    138         * 開始タグから終了タグまでの文字列の処理を実行するかどうかを定義します。
    139         *
    140         * falseが返された場合、何も処理されず({@link #exec(String, StringBuilder, int)}が実行されない)、
    141         * 元の文字列がそのまま出力されます。
    142         *
    143         * @param strOffset 開始タグのオフセット
    144         * @param endOffset 終了タグのオフセット
    145         *
    146         * @return 処理を行うかどうか(true:処理を行う false:処理を行わない)
    147         */
    148        protected boolean checkIgnore( final int strOffset, final int endOffset ) {
    149                return true;
    150        }
    151
    152        /**
    153         * パース実行中のoffset値を外部からセットします。
    154         *
    155         * このメソッドは、{@link #exec(String, StringBuilder, int)}で、処理結果により、offset値を
    156         * 進めておく必要がある場合に利用されます。(つまり通常は利用する必要はありません)
    157         *
    158         * @param offset オフセット
    159         * @see #exec(String, StringBuilder, int)
    160         */
    161        public void setOffset( final int offset ) {
    162                curOffset = offset;
    163        }
    164
    165        /**
    166         * 引数の文字列を指定された開始タグ、終了タグで解析し配列として返す、ユーティリティメソッドです。
    167         *
    168         * 開始タグより前の文字列は0番目に、終了タグより後の文字列は1番目に格納されます。
    169         * 2番目以降に、開始タグ、終了タグの部分が格納されます。
    170         *
    171         * @param str           解析する文字列
    172         * @param startTag      開始タグ
    173         * @param endTag        終了タグ
    174         *
    175         * @return 解析結果の配列
    176         */
    177        public static String[] tag2Array( final String str, final String startTag, final String endTag ) {
    178                String header = null;
    179                String footer = null;
    180                final List<String> body = new ArrayList<>();
    181
    182                int preOffset = -1;
    183                int curOffset = 0;
    184
    185                while( true ) {
    186                        curOffset = str.indexOf( startTag, preOffset + 1 );
    187                        if( curOffset < 0 ) {
    188                                curOffset = str.lastIndexOf( endTag ) + endTag.length();
    189                                body.add( str.substring( preOffset, curOffset ) );
    190
    191                                footer = str.substring( curOffset );
    192                                break;
    193                        }
    194                        else if( preOffset == -1 ) {
    195                                header = str.substring( 0, curOffset );
    196                        }
    197                        else {
    198                                body.add( str.substring( preOffset, curOffset ) );
    199                        }
    200                        preOffset = curOffset;
    201                }
    202
    203                String[] arr = new String[body.size()+2];
    204                arr[0] = header;
    205                arr[1] = footer;
    206                for( int i=0; i<body.size(); i++ ) {
    207                        arr[i+2] = body.get(i);
    208                }
    209
    210                return arr;
    211        }
    212
    213        /**
    214         * 引数の文字列の開始文字と終了文字の間の文字列を取り出す、ユーティリティメソッドです。
    215         * ※返される文字列に、開始文字、終了文字は含まれません。
    216         *
    217         * @param str   解析する文字列
    218         * @param start 開始文字
    219         * @param end   終了文字
    220         *
    221         * @return 解析結果の文字
    222         */
    223        public static String getValueFromTag( final String str, final String start, final String end ) {
    224                int startOffset = str.indexOf( start );
    225                // 4.2.4.0 (2008/06/02) 存在しない場合はnullで返す
    226                if( startOffset == -1 ) {
    227                        return null;
    228                }
    229                startOffset += start.length();
    230
    231                final int endOffset = str.indexOf( end, startOffset );
    232//              final String value = str.substring( startOffset, endOffset );
    233
    234                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    235                return str.substring( startOffset, endOffset );
    236
    237//              return value;
    238        }
    239
    240        /**
    241         * 引数のキーから不要なキーを取り除く、ユーティリティメソッドです。
    242         *
    243         * @og.rev 5.1.8.0 (2010/07/01) spanタグを削除
    244         *
    245         * @param key   オリジナルのキー
    246         * @param sb    キーの外に含まれるaタグを削除するための、バッファ
    247         *
    248         * @return 削除後のキー
    249         * @og.rtnNotNull
    250         */
    251        public static String checkKey( final String key, final StringBuilder sb ) {
    252                if( key.indexOf( '<' ) < 0 && key.indexOf( '>' ) < 0 ) { return key; }
    253
    254                final StringBuilder rtn = new StringBuilder( key );
    255                final String tagEnd = ">";
    256                int rtnOffset = -1;
    257
    258                // <text:a ...>{@XXX</text:a>の不要タグを削除
    259                final String delTagStart1 = "<text:a ";
    260                final String delTagEnd1 = "</text:a>";
    261                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd1 ) ) >= 0 ) {
    262                        boolean isDel = false;
    263                        // キー自身に含まれるaタグを削除
    264                        int startOffset = rtn.lastIndexOf( delTagStart1, rtnOffset );
    265                        if( startOffset >= 0 ) {
    266                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    267                                if( endOffset >= 0 ) {
    268                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    269                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    270                                        isDel = true;
    271                                }
    272                        }
    273                        else {
    274                                // キーの外に含まれるaタグを削除
    275                                startOffset = sb.lastIndexOf( delTagStart1 );
    276                                if( startOffset >= 0 ) {
    277                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    278                                        if( endOffset >= 0 ) {
    279                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    280                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    281                                                isDel = true;
    282                                        }
    283                                }
    284                        }
    285                        if( !isDel ) { break; }
    286                }
    287
    288                // 5.1.8.0 (2010/07/01) spanタグを削除
    289                final String delTagStart2 = "<text:span ";
    290                final String delTagEnd2 = "</text:span>";
    291                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd2 ) ) >= 0 ) {
    292                        boolean isDel = false;
    293                        // キー自身に含まれるspanタグを削除
    294                        int startOffset = rtn.lastIndexOf( delTagStart2, rtnOffset );
    295                        if( startOffset >= 0 ) {
    296                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    297                                if( endOffset >= 0 ) {
    298                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    299                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    300                                        isDel = true;
    301                                }
    302                        }
    303                        else {
    304                                // キーの外に含まれるspanタグを削除
    305                                startOffset = sb.lastIndexOf( delTagStart2 );
    306                                if( startOffset >= 0 ) {
    307                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    308                                        if( endOffset >= 0 ) {
    309                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    310                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    311                                                isDel = true;
    312                                        }
    313                                }
    314                        }
    315                        if( !isDel ) { break; }
    316                }
    317
    318                return rtn.toString();
    319        }
    320}