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}