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