2 * abstract XHTML parser
4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.osdn.jindolf.parser;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12 import jp.osdn.jindolf.parser.content.DecodedContent;
13 import jp.sourceforge.jindolf.corelib.GameRole;
16 * 人狼BBS生成のXHTML文書を解釈するパーサの抽象基底クラス。
17 * {@link DecodedContent}の内容をパースし、
18 * 各種ハンドラへ通知する処理の基盤を構成する。
20 * 親パーサを指定することにより、検索対象文字列とマッチエンジンを
24 public abstract class AbstractParser implements ChainedParser{
27 protected static final String SPCHAR = "\u0020\\t\\n\\r";
28 /** 0回以上連続するホワイトスペースの正規表現。 */
29 protected static final String SP_I = "[" +SPCHAR+ "]*";
31 private static final Pattern DUMMY_PATTERN = compile("\u0000");
34 private final ChainedParser parent;
36 private DecodedContent content;
37 private Matcher matcher;
38 private String contextErrorMessage;
44 protected AbstractParser(){
53 protected AbstractParser(ChainedParser parent){
63 * デフォルトで{@link java.util.regex.Pattern#DOTALL}が
65 * @param regex 正規表現文字列
68 protected static Pattern compile(CharSequence regex){
69 Pattern result = Pattern.compile(regex.toString(), Pattern.DOTALL);
74 * パーサの状態をコンストラクタ直後の状態にリセットする。
75 * ※コンストラクタから呼ばせるためにオーバーライド不可
77 private void resetImpl(){
80 this.contextErrorMessage = null;
88 if(this.parent != null){
89 throw new UnsupportedOperationException();
97 * パースが終わるまでこの文字列の内容を変更してはならない。
98 * @param content パース対象文字列
100 public void setContent(DecodedContent content){
101 if(this.parent != null){
102 throw new UnsupportedOperationException();
105 CharSequence rawContent = content.getRawContent();
107 this.content = content;
108 this.matcher = DUMMY_PATTERN.matcher(rawContent);
115 * @return {@inheritDoc}
118 public DecodedContent getContent(){
119 if(this.parent != null){
120 return this.parent.getContent();
128 * @return {@inheritDoc}
131 public Matcher getMatcher(){
132 if(this.parent != null){
133 return this.parent.getMatcher();
140 * 文脈依存のエラーメッセージを設定する。
141 * {@link #buildParseException}で利用される。
143 * @param errorMessage エラーメッセージ。nullも可能。
145 protected void setContextErrorMessage(String errorMessage){
146 this.contextErrorMessage = errorMessage;
151 * 文脈状況に応じたパース例外を生成する。
152 * 例外にはリージョン開始位置が埋め込まれる。
155 protected HtmlParseException buildParseException(){
156 HtmlParseException result;
157 result = new HtmlParseException(this.contextErrorMessage,
163 * パースに使う正規表現パターンを切り替える。
164 * @param pattern 正規表現パターン
166 protected void switchPattern(Pattern pattern){
167 getMatcher().usePattern(pattern);
172 * 最後のマッチに成功した文字領域以前をパース対象から外す。
174 protected void shrinkRegion(){
177 lastMatchedEnd = matchEnd();
178 }catch(IllegalStateException e){
182 int regionEnd = regionEnd();
184 getMatcher().region(lastMatchedEnd, regionEnd);
190 * 検査対象の一部が指定パターンにマッチするか判定する。
191 * @param pattern 指定パターン
194 protected boolean findProbe(Pattern pattern){
195 switchPattern(pattern);
196 boolean result = getMatcher().find();
201 * 検査対象先頭が指定パターンにマッチするか判定する。
202 * @param pattern 指定パターン
205 protected boolean lookingAtProbe(Pattern pattern){
206 switchPattern(pattern);
207 boolean result = getMatcher().lookingAt();
212 * 検査対象全体が指定パターンにマッチするか判定する。
213 * @param pattern 指定パターン
216 protected boolean matchesProbe(Pattern pattern){
217 switchPattern(pattern);
218 boolean result = getMatcher().matches();
223 * 残りの検索対象領域からパターンがマッチする部分を探す。
225 * @param pattern 正規表現パターン
226 * @throws HtmlParseException マッチしなかった
228 protected void findAffirm(Pattern pattern)
229 throws HtmlParseException{
230 if( ! findProbe(pattern) ){
231 throw buildParseException();
237 * 残りの検索対象領域先頭からパターンがマッチする部分を探す。
239 * @param pattern 正規表現パターン
240 * @throws HtmlParseException マッチしなかった
242 protected void lookingAtAffirm(Pattern pattern)
243 throws HtmlParseException{
244 if( ! lookingAtProbe(pattern) ){
245 throw buildParseException();
251 * 残りの検索対象領域全体がパターンにマッチするか調べる。
253 * @param pattern 正規表現パターン
254 * @throws HtmlParseException マッチしなかった
256 protected void matchesAffirm(Pattern pattern)
257 throws HtmlParseException{
258 if( ! matchesProbe(pattern) ){
259 throw buildParseException();
265 * 最後のマッチで任意の前方参照グループがヒットしたか判定する。
266 * @param group グループ番号
267 * @return ヒットしていたらtrue
269 protected boolean isGroupMatched(int group){
270 boolean result = matchStart(group) >= 0;
275 * 最後にマッチした前方参照グループを数値化する。
277 * @param group グループ番号
280 protected int parseGroupedInt(int group){
283 CharSequence rawContent = getContent().getRawContent();
284 int start = matchStart(group);
285 int end = matchEnd(group);
286 for(int pos = start; pos < end; pos++){
287 char letter = rawContent.charAt(pos);
288 int digit = Character.digit(letter, 10);
289 result = result * 10 + digit;
296 * 最後にマッチした前方参照グループの開始位置を得る。
297 * @param group 前方参照識別番号
300 protected int matchStart(int group){
301 return getMatcher().start(group);
305 * 最後にマッチした全領域の開始位置を得る。
308 protected int matchStart(){
309 return getMatcher().start();
313 * 最後にマッチした前方参照グループの終了位置を得る。
314 * @param group 前方参照識別番号
317 protected int matchEnd(int group){
318 return getMatcher().end(group);
322 * 最後にマッチした全領域の終了位置を得る。
325 protected int matchEnd(){
326 return getMatcher().end();
333 protected int regionStart(){
334 return getMatcher().regionStart();
341 protected int regionEnd(){
342 return getMatcher().regionEnd();
346 * 0個以上のホワイトスペースを読み飛ばす。
347 * 具体的には検索対象領域の先頭が進むだけ。
349 protected void sweepSpace(){
350 CharSequence rawContent = getContent().getRawContent();
352 boolean hasSpace = false;
353 int regionStart = regionStart();
354 int regionEnd = regionEnd();
356 for( ; regionStart < regionEnd; regionStart++){
357 char letter = rawContent.charAt(regionStart);
380 getMatcher().region(regionStart, regionEnd);
387 * 検索領域の先頭から各種役職名のマッチを試みる。
388 * @return 役職。何もマッチしなければnullを返す。
390 protected GameRole lookingAtRole(){
391 GameRole role = GameRole.lookingAtRole(getMatcher());