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}