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