OSDN Git Service

from subversion repository
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / AbstractParser.java
1 /*\r
2  * abstract XHTML parser\r
3  *\r
4  * Copyright(c) 2009 olyutorskii\r
5  * $Id: AbstractParser.java 894 2009-11-04 07:26:59Z olyutorskii $\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf.parser;\r
9 \r
10 import java.util.regex.Matcher;\r
11 import java.util.regex.Pattern;\r
12 import jp.sourceforge.jindolf.corelib.GameRole;\r
13 \r
14 /**\r
15  * 人狼BBS生成のXHTML文書を解釈するパーサの抽象基底クラス。\r
16  * {@link DecodedContent}の内容をパースし、\r
17  * 各種ハンドラへ通知する処理の基盤を構成する。\r
18  * 正規表現エンジンを実装基盤とする。\r
19  * 親パーサを指定することにより、検索対象文字列とマッチエンジンを\r
20  * 親パーサと共有することができる。\r
21  * @see Matcher\r
22  */\r
23 public abstract class AbstractParser implements ChainedParser{\r
24 \r
25     /** ホワイトスペース。 */\r
26     protected static final String SPCHAR = "\u0020\\t\\n\\r";\r
27     /** 0回以上連続するホワイトスペースの正規表現。 */\r
28     protected static final String SP_I = "[" +SPCHAR+ "]*";\r
29 \r
30     private static final Pattern DUMMY_PATTERN = compile("\u0000");\r
31 \r
32     /**\r
33      * 正規表現のコンパイルを行う。\r
34      * デフォルトで{@link java.util.regex.Pattern#DOTALL}が\r
35      * オプション指定される。\r
36      * @param regex 正規表現文字列\r
37      * @return マッチエンジン\r
38      */\r
39     protected static Pattern compile(CharSequence regex){\r
40         Pattern result = Pattern.compile(regex.toString(), Pattern.DOTALL);\r
41         return result;\r
42     }\r
43 \r
44     private final ChainedParser parent;\r
45 \r
46     private DecodedContent content;\r
47     private Matcher matcher;\r
48     private String contextErrorMessage;\r
49 \r
50     /**\r
51      * コンストラクタ。\r
52      */\r
53     protected AbstractParser(){\r
54         this(null);\r
55         return;\r
56     }\r
57 \r
58     /**\r
59      * コンストラクタ。\r
60      * @param parent 親パーサ\r
61      */\r
62     protected AbstractParser(ChainedParser parent){\r
63         super();\r
64         this.parent = parent;\r
65         resetImpl();\r
66         return;\r
67     }\r
68 \r
69     /**\r
70      * パーサの状態をコンストラクタ直後の状態にリセットする。\r
71      * ※コンストラクタから呼ばせるためにオーバーライド不可\r
72      */\r
73     private void resetImpl(){\r
74         this.content = null;\r
75         this.matcher = null;\r
76         this.contextErrorMessage = null;\r
77         return;\r
78     }\r
79 \r
80     /**\r
81      * パーサの状態をリセットする。\r
82      */\r
83     public void reset(){\r
84         if(this.parent != null){\r
85             throw new UnsupportedOperationException();\r
86         }\r
87         resetImpl();\r
88         return;\r
89     }\r
90 \r
91     /**\r
92      * {@inheritDoc}\r
93      * @param content {@inheritDoc}\r
94      */\r
95     /**\r
96      * パース対象文字列をセットする。\r
97      * パースが終わるまでこの文字列の内容を変更してはならない。\r
98      * @param content パース対象文字列\r
99      */\r
100     public void setContent(DecodedContent content){\r
101         if(this.parent != null){\r
102             throw new UnsupportedOperationException();\r
103         }\r
104 \r
105         CharSequence rawContent = content.getRawContent();\r
106 \r
107         this.content = content;\r
108         this.matcher = DUMMY_PATTERN.matcher(rawContent);\r
109 \r
110         return;\r
111     }\r
112 \r
113     /**\r
114      * {@inheritDoc}\r
115      * @return {@inheritDoc}\r
116      */\r
117     public DecodedContent getContent(){\r
118         if(this.parent != null){\r
119             return this.parent.getContent();\r
120         }\r
121 \r
122         return this.content;\r
123     }\r
124 \r
125     /**\r
126      * {@inheritDoc}\r
127      * @return {@inheritDoc}\r
128      */\r
129     public Matcher getMatcher(){\r
130         if(this.parent != null){\r
131             return this.parent.getMatcher();\r
132         }\r
133 \r
134         return this.matcher;\r
135     }\r
136 \r
137     /**\r
138      * 文脈依存のエラーメッセージを設定する。\r
139      * {@link #buildParseException}で利用される。\r
140      * 設定内容は親へ委譲されない。\r
141      * @param errorMessage エラーメッセージ。nullも可能。\r
142      */\r
143     protected void setContextErrorMessage(String errorMessage){\r
144         this.contextErrorMessage = errorMessage;\r
145         return;\r
146     }\r
147 \r
148     /**\r
149      * 文脈状況に応じたパース例外を生成する。\r
150      * 例外にはリージョン開始位置が埋め込まれる。\r
151      * @return パース例外\r
152      */\r
153     protected HtmlParseException buildParseException(){\r
154         HtmlParseException result;\r
155         result = new HtmlParseException(this.contextErrorMessage,\r
156                                         regionStart() );\r
157         return result;\r
158     }\r
159 \r
160     /**\r
161      * パースに使う正規表現パターンを切り替える。\r
162      * @param pattern 正規表現パターン\r
163      */\r
164     protected void switchPattern(Pattern pattern){\r
165         getMatcher().usePattern(pattern);\r
166         return;\r
167     }\r
168 \r
169     /**\r
170      * 最後のマッチに成功した文字領域以前をパース対象から外す。\r
171      */\r
172     protected void shrinkRegion(){\r
173         int lastMatchedEnd;\r
174         try{\r
175             lastMatchedEnd = matchEnd();\r
176         }catch(IllegalStateException e){\r
177             return;\r
178         }\r
179 \r
180         int regionEnd   = regionEnd();\r
181 \r
182         getMatcher().region(lastMatchedEnd, regionEnd);\r
183 \r
184         return;\r
185     }\r
186 \r
187     /**\r
188      * 検査対象の一部が指定パターンにマッチするか判定する。\r
189      * @param pattern 指定パターン\r
190      * @return マッチすればtrue\r
191      */\r
192     protected boolean findProbe(Pattern pattern){\r
193         switchPattern(pattern);\r
194         if( getMatcher().find() ) return true;\r
195         return false;\r
196     }\r
197 \r
198     /**\r
199      * 検査対象先頭が指定パターンにマッチするか判定する。\r
200      * @param pattern 指定パターン\r
201      * @return マッチすればtrue\r
202      */\r
203     protected boolean lookingAtProbe(Pattern pattern){\r
204         switchPattern(pattern);\r
205         if( getMatcher().lookingAt() ) return true;\r
206         return false;\r
207     }\r
208 \r
209     /**\r
210      * 検査対象全体が指定パターンにマッチするか判定する。\r
211      * @param pattern 指定パターン\r
212      * @return マッチすればtrue\r
213      */\r
214     protected boolean matchesProbe(Pattern pattern){\r
215         switchPattern(pattern);\r
216         if( getMatcher().matches() ) return true;\r
217         return false;\r
218     }\r
219 \r
220     /**\r
221      * 残りの検索対象領域からパターンがマッチする部分を探す。\r
222      * 見つからなければ例外をスローする。\r
223      * @param pattern 正規表現パターン\r
224      * @throws HtmlParseException\r
225      * マッチしなかった\r
226      */\r
227     protected void findAffirm(Pattern pattern)\r
228             throws HtmlParseException{\r
229         if( ! findProbe(pattern) ){\r
230             throw buildParseException();\r
231         }\r
232         return;\r
233     }\r
234 \r
235     /**\r
236      * 残りの検索対象領域先頭からパターンがマッチする部分を探す。\r
237      * 見つからなければ例外をスローする。\r
238      * @param pattern 正規表現パターン\r
239      * @throws HtmlParseException\r
240      * マッチしなかった\r
241      */\r
242     protected void lookingAtAffirm(Pattern pattern)\r
243             throws HtmlParseException{\r
244         if( ! lookingAtProbe(pattern) ){\r
245             throw buildParseException();\r
246         }\r
247         return;\r
248     }\r
249 \r
250     /**\r
251      * 残りの検索対象領域全体がパターンにマッチするか調べる。\r
252      * マッチしなければ例外をスローする。\r
253      * @param pattern 正規表現パターン\r
254      * @throws HtmlParseException\r
255      * マッチしなかった\r
256      */\r
257     protected void matchesAffirm(Pattern pattern)\r
258             throws HtmlParseException{\r
259         if( ! matchesProbe(pattern) ){\r
260             throw buildParseException();\r
261         }\r
262         return;\r
263     }\r
264 \r
265     /**\r
266      * 最後のマッチで任意の前方参照グループがヒットしたか判定する。\r
267      * @param group グループ番号\r
268      * @return ヒットしていたらtrue\r
269      */\r
270     protected boolean isGroupMatched(int group){\r
271         if(matchStart(group) >= 0) return true;\r
272         return false;\r
273     }\r
274 \r
275     /**\r
276      * 最後にマッチした前方参照グループを数値化する。\r
277      * 0以上の整数のみサポート。\r
278      * @param group グループ番号\r
279      * @return 数値\r
280      */\r
281     protected int parseGroupedInt(int group){\r
282         int result = 0;\r
283 \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
291         }\r
292 \r
293         return result;\r
294     }\r
295 \r
296     /**\r
297      * 最後にマッチした前方参照グループの開始位置を得る。\r
298      * @param group 前方参照識別番号\r
299      * @return 開始位置\r
300      */\r
301     protected int matchStart(int group){\r
302         return getMatcher().start(group);\r
303     }\r
304 \r
305     /**\r
306      * 最後にマッチした前方参照グループの終了位置を得る。\r
307      * @param group 前方参照識別番号\r
308      * @return 終了位置\r
309      */\r
310     protected int matchEnd(int group){\r
311         return getMatcher().end(group);\r
312     }\r
313 \r
314     /**\r
315      * 最後にマッチした全領域の開始位置を得る。\r
316      * @return 開始位置\r
317      */\r
318     protected int matchStart(){\r
319         return getMatcher().start();\r
320     }\r
321 \r
322     /**\r
323      * 最後にマッチした全領域の終了位置を得る。\r
324      * @return 終了位置\r
325      */\r
326     protected int matchEnd(){\r
327         return getMatcher().end();\r
328     }\r
329 \r
330     /**\r
331      * 検索領域の先頭位置を返す。\r
332      * @return 先頭位置\r
333      */\r
334     protected int regionStart(){\r
335         return getMatcher().regionStart();\r
336     }\r
337 \r
338     /**\r
339      * 検索領域の末尾位置を返す。\r
340      * @return 末尾位置\r
341      */\r
342     protected int regionEnd(){\r
343         return getMatcher().regionEnd();\r
344     }\r
345 \r
346     /**\r
347      * 0個以上のホワイトスペースを読み飛ばす。\r
348      * 具体的には検索対象領域の先頭が進むだけ。\r
349      */\r
350     protected void sweepSpace(){\r
351         CharSequence rawContent = getContent().getRawContent();\r
352 \r
353         boolean hasSpace = false;\r
354         int regionStart = regionStart();\r
355         int regionEnd   = regionEnd();\r
356 \r
357         for( ; regionStart < regionEnd; regionStart++){\r
358             char letter = rawContent.charAt(regionStart);\r
359 \r
360             switch(letter){\r
361             case '\u0020':\r
362             case '\t':\r
363             case '\n':\r
364             case '\r':\r
365                 hasSpace = true;\r
366                 continue;\r
367             default:\r
368                 break;\r
369             }\r
370 \r
371             break;\r
372         }\r
373 \r
374         if(hasSpace){\r
375             getMatcher().region(regionStart, regionEnd);\r
376         }\r
377 \r
378         return;\r
379     }\r
380 \r
381     /**\r
382      * 検索領域の先頭から各種役職名のマッチを試みる。\r
383      * @return 役職。何もマッチしなければnullを返す。\r
384      */\r
385     protected GameRole lookingAtRole(){\r
386         GameRole role = GameRole.lookingAtRole(getMatcher());\r
387         return role;\r
388     }\r
389 \r
390 }\r