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