    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                return value;
    235        }
    236
    237        /**
    238         * 引数のキーから不要なキーを取り除く、ユーティリティメソッドです。
    239         *
    240         * @og.rev 5.1.8.0 (2010/07/01) spanタグを削除
    241         *
    242         * @param key   オリジナルのキー
    243         * @param sb    キーの外に含まれるaタグを削除するための、バッファ
    244         *
    245         * @return 削除後のキー
    246         * @og.rtnNotNull
    247         */
    248        public static String checkKey( final String key, final StringBuilder sb ) {
    249                if( key.indexOf( '<' ) < 0 && key.indexOf( '>' ) < 0 ) { return key; }
    250
    251                final StringBuilder rtn = new StringBuilder( key );
    252                final String tagEnd = ">";
    253                int rtnOffset = -1;
    254
    255                // <text:a ...>{@XXX</text:a>の不要タグを削除
    256                final String delTagStart1 = "<text:a ";
    257                final String delTagEnd1 = "</text:a>";
    258                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd1 ) ) >= 0 ) {
    259                        boolean isDel = false;
    260                        // キー自身に含まれるaタグを削除
    261                        int startOffset = rtn.lastIndexOf( delTagStart1, rtnOffset );
    262                        if( startOffset >= 0 ) {
    263                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    264                                if( endOffset >= 0 ) {
    265                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    266                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    267                                        isDel = true;
    268                                }
    269                        }
    270                        else {
    271                                // キーの外に含まれるaタグを削除
    272                                startOffset = sb.lastIndexOf( delTagStart1 );
    273                                if( startOffset >= 0 ) {
    274                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    275                                        if( endOffset >= 0 ) {
    276                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd1.length() );
    277                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    278                                                isDel = true;
    279                                        }
    280                                }
    281                        }
    282                        if( !isDel ) { break; }
    283                }
    284
    285                // 5.1.8.0 (2010/07/01) spanタグを削除
    286                final String delTagStart2 = "<text:span ";
    287                final String delTagEnd2 = "</text:span>";
    288                while( ( rtnOffset = rtn.lastIndexOf( delTagEnd2 ) ) >= 0 ) {
    289                        boolean isDel = false;
    290                        // キー自身に含まれるspanタグを削除
    291                        int startOffset = rtn.lastIndexOf( delTagStart2, rtnOffset );
    292                        if( startOffset >= 0 ) {
    293                                final int endOffset = rtn.indexOf( tagEnd, startOffset );
    294                                if( endOffset >= 0 ) {
    295                                        rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    296                                        rtn.delete( startOffset, endOffset + tagEnd.length() );
    297                                        isDel = true;
    298                                }
    299                        }
    300                        else {
    301                                // キーの外に含まれるspanタグを削除
    302                                startOffset = sb.lastIndexOf( delTagStart2 );
    303                                if( startOffset >= 0 ) {
    304                                        final int endOffset = sb.indexOf( tagEnd, startOffset );
    305                                        if( endOffset >= 0 ) {
    306                                                rtn.delete( rtnOffset, rtnOffset + delTagEnd2.length() );
    307                                                sb.delete( startOffset, endOffset + tagEnd.length() );
    308                                                isDel = true;
    309                                        }
    310                                }
    311                        }
    312                        if( !isDel ) { break; }
    313                }
    314
    315                return rtn.toString();
    316        }
    317}