2 * abstract XHTML parser
\r
4 * License : The MIT License
\r
5 * Copyright(c) 2009 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
118 public DecodedContent getContent(){
\r
119 if(this.parent != null){
\r
120 return this.parent.getContent();
\r
123 return this.content;
\r
128 * @return {@inheritDoc}
\r
131 public Matcher getMatcher(){
\r
132 if(this.parent != null){
\r
133 return this.parent.getMatcher();
\r
136 return this.matcher;
\r
140 * 文脈依存のエラーメッセージを設定する。
\r
141 * {@link #buildParseException}で利用される。
\r
143 * @param errorMessage エラーメッセージ。nullも可能。
\r
145 protected void setContextErrorMessage(String errorMessage){
\r
146 this.contextErrorMessage = errorMessage;
\r
151 * 文脈状況に応じたパース例外を生成する。
\r
152 * 例外にはリージョン開始位置が埋め込まれる。
\r
155 protected HtmlParseException buildParseException(){
\r
156 HtmlParseException result;
\r
157 result = new HtmlParseException(this.contextErrorMessage,
\r
163 * パースに使う正規表現パターンを切り替える。
\r
164 * @param pattern 正規表現パターン
\r
166 protected void switchPattern(Pattern pattern){
\r
167 getMatcher().usePattern(pattern);
\r
172 * 最後のマッチに成功した文字領域以前をパース対象から外す。
\r
174 protected void shrinkRegion(){
\r
175 int lastMatchedEnd;
\r
177 lastMatchedEnd = matchEnd();
\r
178 }catch(IllegalStateException e){
\r
182 int regionEnd = regionEnd();
\r
184 getMatcher().region(lastMatchedEnd, regionEnd);
\r
190 * 検査対象の一部が指定パターンにマッチするか判定する。
\r
191 * @param pattern 指定パターン
\r
192 * @return マッチすればtrue
\r
194 protected boolean findProbe(Pattern pattern){
\r
195 switchPattern(pattern);
\r
196 if( getMatcher().find() ) return true;
\r
201 * 検査対象先頭が指定パターンにマッチするか判定する。
\r
202 * @param pattern 指定パターン
\r
203 * @return マッチすればtrue
\r
205 protected boolean lookingAtProbe(Pattern pattern){
\r
206 switchPattern(pattern);
\r
207 if( getMatcher().lookingAt() ) return true;
\r
212 * 検査対象全体が指定パターンにマッチするか判定する。
\r
213 * @param pattern 指定パターン
\r
214 * @return マッチすればtrue
\r
216 protected boolean matchesProbe(Pattern pattern){
\r
217 switchPattern(pattern);
\r
218 if( getMatcher().matches() ) return true;
\r
223 * 残りの検索対象領域からパターンがマッチする部分を探す。
\r
224 * 見つからなければ例外をスローする。
\r
225 * @param pattern 正規表現パターン
\r
226 * @throws HtmlParseException
\r
229 protected void findAffirm(Pattern pattern)
\r
230 throws HtmlParseException{
\r
231 if( ! findProbe(pattern) ){
\r
232 throw buildParseException();
\r
238 * 残りの検索対象領域先頭からパターンがマッチする部分を探す。
\r
239 * 見つからなければ例外をスローする。
\r
240 * @param pattern 正規表現パターン
\r
241 * @throws HtmlParseException
\r
244 protected void lookingAtAffirm(Pattern pattern)
\r
245 throws HtmlParseException{
\r
246 if( ! lookingAtProbe(pattern) ){
\r
247 throw buildParseException();
\r
253 * 残りの検索対象領域全体がパターンにマッチするか調べる。
\r
254 * マッチしなければ例外をスローする。
\r
255 * @param pattern 正規表現パターン
\r
256 * @throws HtmlParseException
\r
259 protected void matchesAffirm(Pattern pattern)
\r
260 throws HtmlParseException{
\r
261 if( ! matchesProbe(pattern) ){
\r
262 throw buildParseException();
\r
268 * 最後のマッチで任意の前方参照グループがヒットしたか判定する。
\r
269 * @param group グループ番号
\r
270 * @return ヒットしていたらtrue
\r
272 protected boolean isGroupMatched(int group){
\r
273 if(matchStart(group) >= 0) return true;
\r
278 * 最後にマッチした前方参照グループを数値化する。
\r
280 * @param group グループ番号
\r
283 protected int parseGroupedInt(int group){
\r
286 CharSequence rawContent = getContent().getRawContent();
\r
287 int start = matchStart(group);
\r
288 int end = matchEnd(group);
\r
289 for(int pos = start; pos < end; pos++){
\r
290 char letter = rawContent.charAt(pos);
\r
291 int digit = Character.digit(letter, 10);
\r
292 result = result * 10 + digit;
\r
299 * 最後にマッチした前方参照グループの開始位置を得る。
\r
300 * @param group 前方参照識別番号
\r
303 protected int matchStart(int group){
\r
304 return getMatcher().start(group);
\r
308 * 最後にマッチした前方参照グループの終了位置を得る。
\r
309 * @param group 前方参照識別番号
\r
312 protected int matchEnd(int group){
\r
313 return getMatcher().end(group);
\r
317 * 最後にマッチした全領域の開始位置を得る。
\r
320 protected int matchStart(){
\r
321 return getMatcher().start();
\r
325 * 最後にマッチした全領域の終了位置を得る。
\r
328 protected int matchEnd(){
\r
329 return getMatcher().end();
\r
336 protected int regionStart(){
\r
337 return getMatcher().regionStart();
\r
344 protected int regionEnd(){
\r
345 return getMatcher().regionEnd();
\r
349 * 0個以上のホワイトスペースを読み飛ばす。
\r
350 * 具体的には検索対象領域の先頭が進むだけ。
\r
352 protected void sweepSpace(){
\r
353 CharSequence rawContent = getContent().getRawContent();
\r
355 boolean hasSpace = false;
\r
356 int regionStart = regionStart();
\r
357 int regionEnd = regionEnd();
\r
359 for( ; regionStart < regionEnd; regionStart++){
\r
360 char letter = rawContent.charAt(regionStart);
\r
377 getMatcher().region(regionStart, regionEnd);
\r
384 * 検索領域の先頭から各種役職名のマッチを試みる。
\r
385 * @return 役職。何もマッチしなければnullを返す。
\r
387 protected GameRole lookingAtRole(){
\r
388 GameRole role = GameRole.lookingAtRole(getMatcher());
\r