2 * abstract XHTML parser
\r
4 * Copyright(c) 2009 olyutorskii
\r
5 * $Id: AbstractParser.java 894 2009-11-04 07:26:59Z olyutorskii $
\r
8 package jp.sourceforge.jindolf.parser;
\r
10 import java.util.regex.Matcher;
\r
11 import java.util.regex.Pattern;
\r
12 import jp.sourceforge.jindolf.corelib.GameRole;
\r
15 * 人狼BBS生成のXHTML文書を解釈するパーサの抽象基底クラス。
\r
16 * {@link DecodedContent}の内容をパースし、
\r
17 * 各種ハンドラへ通知する処理の基盤を構成する。
\r
19 * 親パーサを指定することにより、検索対象文字列とマッチエンジンを
\r
23 public abstract class AbstractParser implements ChainedParser{
\r
26 protected static final String SPCHAR = "\u0020\\t\\n\\r";
\r
27 /** 0回以上連続するホワイトスペースの正規表現。 */
\r
28 protected static final String SP_I = "[" +SPCHAR+ "]*";
\r
30 private static final Pattern DUMMY_PATTERN = compile("\u0000");
\r
34 * デフォルトで{@link java.util.regex.Pattern#DOTALL}が
\r
36 * @param regex 正規表現文字列
\r
39 protected static Pattern compile(CharSequence regex){
\r
40 Pattern result = Pattern.compile(regex.toString(), Pattern.DOTALL);
\r
44 private final ChainedParser parent;
\r
46 private DecodedContent content;
\r
47 private Matcher matcher;
\r
48 private String contextErrorMessage;
\r
53 protected AbstractParser(){
\r
60 * @param parent 親パーサ
\r
62 protected AbstractParser(ChainedParser parent){
\r
64 this.parent = parent;
\r
70 * パーサの状態をコンストラクタ直後の状態にリセットする。
\r
71 * ※コンストラクタから呼ばせるためにオーバーライド不可
\r
73 private void resetImpl(){
\r
74 this.content = null;
\r
75 this.matcher = null;
\r
76 this.contextErrorMessage = null;
\r
83 public void reset(){
\r
84 if(this.parent != null){
\r
85 throw new UnsupportedOperationException();
\r
93 * @param content {@inheritDoc}
\r
97 * パースが終わるまでこの文字列の内容を変更してはならない。
\r
98 * @param content パース対象文字列
\r
100 public void setContent(DecodedContent content){
\r
101 if(this.parent != null){
\r
102 throw new UnsupportedOperationException();
\r
105 CharSequence rawContent = content.getRawContent();
\r
107 this.content = content;
\r
108 this.matcher = DUMMY_PATTERN.matcher(rawContent);
\r
115 * @return {@inheritDoc}
\r
117 public DecodedContent getContent(){
\r
118 if(this.parent != null){
\r
119 return this.parent.getContent();
\r
122 return this.content;
\r
127 * @return {@inheritDoc}
\r
129 public Matcher getMatcher(){
\r
130 if(this.parent != null){
\r
131 return this.parent.getMatcher();
\r
134 return this.matcher;
\r
138 * 文脈依存のエラーメッセージを設定する。
\r
139 * {@link #buildParseException}で利用される。
\r
141 * @param errorMessage エラーメッセージ。nullも可能。
\r
143 protected void setContextErrorMessage(String errorMessage){
\r
144 this.contextErrorMessage = errorMessage;
\r
149 * 文脈状況に応じたパース例外を生成する。
\r
150 * 例外にはリージョン開始位置が埋め込まれる。
\r
153 protected HtmlParseException buildParseException(){
\r
154 HtmlParseException result;
\r
155 result = new HtmlParseException(this.contextErrorMessage,
\r
161 * パースに使う正規表現パターンを切り替える。
\r
162 * @param pattern 正規表現パターン
\r
164 protected void switchPattern(Pattern pattern){
\r
165 getMatcher().usePattern(pattern);
\r
170 * 最後のマッチに成功した文字領域以前をパース対象から外す。
\r
172 protected void shrinkRegion(){
\r
173 int lastMatchedEnd;
\r
175 lastMatchedEnd = matchEnd();
\r
176 }catch(IllegalStateException e){
\r
180 int regionEnd = regionEnd();
\r
182 getMatcher().region(lastMatchedEnd, regionEnd);
\r
188 * 検査対象の一部が指定パターンにマッチするか判定する。
\r
189 * @param pattern 指定パターン
\r
190 * @return マッチすればtrue
\r
192 protected boolean findProbe(Pattern pattern){
\r
193 switchPattern(pattern);
\r
194 if( getMatcher().find() ) return true;
\r
199 * 検査対象先頭が指定パターンにマッチするか判定する。
\r
200 * @param pattern 指定パターン
\r
201 * @return マッチすればtrue
\r
203 protected boolean lookingAtProbe(Pattern pattern){
\r
204 switchPattern(pattern);
\r
205 if( getMatcher().lookingAt() ) return true;
\r
210 * 検査対象全体が指定パターンにマッチするか判定する。
\r
211 * @param pattern 指定パターン
\r
212 * @return マッチすればtrue
\r
214 protected boolean matchesProbe(Pattern pattern){
\r
215 switchPattern(pattern);
\r
216 if( getMatcher().matches() ) return true;
\r
221 * 残りの検索対象領域からパターンがマッチする部分を探す。
\r
222 * 見つからなければ例外をスローする。
\r
223 * @param pattern 正規表現パターン
\r
224 * @throws HtmlParseException
\r
227 protected void findAffirm(Pattern pattern)
\r
228 throws HtmlParseException{
\r
229 if( ! findProbe(pattern) ){
\r
230 throw buildParseException();
\r
236 * 残りの検索対象領域先頭からパターンがマッチする部分を探す。
\r
237 * 見つからなければ例外をスローする。
\r
238 * @param pattern 正規表現パターン
\r
239 * @throws HtmlParseException
\r
242 protected void lookingAtAffirm(Pattern pattern)
\r
243 throws HtmlParseException{
\r
244 if( ! lookingAtProbe(pattern) ){
\r
245 throw buildParseException();
\r
251 * 残りの検索対象領域全体がパターンにマッチするか調べる。
\r
252 * マッチしなければ例外をスローする。
\r
253 * @param pattern 正規表現パターン
\r
254 * @throws HtmlParseException
\r
257 protected void matchesAffirm(Pattern pattern)
\r
258 throws HtmlParseException{
\r
259 if( ! matchesProbe(pattern) ){
\r
260 throw buildParseException();
\r
266 * 最後のマッチで任意の前方参照グループがヒットしたか判定する。
\r
267 * @param group グループ番号
\r
268 * @return ヒットしていたらtrue
\r
270 protected boolean isGroupMatched(int group){
\r
271 if(matchStart(group) >= 0) return true;
\r
276 * 最後にマッチした前方参照グループを数値化する。
\r
278 * @param group グループ番号
\r
281 protected int parseGroupedInt(int group){
\r
284 CharSequence rawContent = getContent().getRawContent();
\r
285 int start = matchStart(group);
\r
286 int end = matchEnd(group);
\r
287 for(int pos = start; pos < end; pos++){
\r
288 char letter = rawContent.charAt(pos);
\r
289 int digit = Character.digit(letter, 10);
\r
290 result = result * 10 + digit;
\r
297 * 最後にマッチした前方参照グループの開始位置を得る。
\r
298 * @param group 前方参照識別番号
\r
301 protected int matchStart(int group){
\r
302 return getMatcher().start(group);
\r
306 * 最後にマッチした前方参照グループの終了位置を得る。
\r
307 * @param group 前方参照識別番号
\r
310 protected int matchEnd(int group){
\r
311 return getMatcher().end(group);
\r
315 * 最後にマッチした全領域の開始位置を得る。
\r
318 protected int matchStart(){
\r
319 return getMatcher().start();
\r
323 * 最後にマッチした全領域の終了位置を得る。
\r
326 protected int matchEnd(){
\r
327 return getMatcher().end();
\r
334 protected int regionStart(){
\r
335 return getMatcher().regionStart();
\r
342 protected int regionEnd(){
\r
343 return getMatcher().regionEnd();
\r
347 * 0個以上のホワイトスペースを読み飛ばす。
\r
348 * 具体的には検索対象領域の先頭が進むだけ。
\r
350 protected void sweepSpace(){
\r
351 CharSequence rawContent = getContent().getRawContent();
\r
353 boolean hasSpace = false;
\r
354 int regionStart = regionStart();
\r
355 int regionEnd = regionEnd();
\r
357 for( ; regionStart < regionEnd; regionStart++){
\r
358 char letter = rawContent.charAt(regionStart);
\r
375 getMatcher().region(regionStart, regionEnd);
\r
382 * 検索領域の先頭から各種役職名のマッチを試みる。
\r
383 * @return 役職。何もマッチしなければnullを返す。
\r
385 protected GameRole lookingAtRole(){
\r
386 GameRole role = GameRole.lookingAtRole(getMatcher());
\r