OSDN Git Service

@Override追加
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / AbstractParser.java
1 /*\r
2  * abstract XHTML parser\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2009 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     @Override\r
118     public DecodedContent getContent(){\r
119         if(this.parent != null){\r
120             return this.parent.getContent();\r
121         }\r
122 \r
123         return this.content;\r
124     }\r
125 \r
126     /**\r
127      * {@inheritDoc}\r
128      * @return {@inheritDoc}\r
129      */\r
130     @Override\r
131     public Matcher getMatcher(){\r
132         if(this.parent != null){\r
133             return this.parent.getMatcher();\r
134         }\r
135 \r
136         return this.matcher;\r
137     }\r
138 \r
139     /**\r
140      * 文脈依存のエラーメッセージを設定する。\r
141      * {@link #buildParseException}で利用される。\r
142      * 設定内容は親へ委譲されない。\r
143      * @param errorMessage エラーメッセージ。nullも可能。\r
144      */\r
145     protected void setContextErrorMessage(String errorMessage){\r
146         this.contextErrorMessage = errorMessage;\r
147         return;\r
148     }\r
149 \r
150     /**\r
151      * 文脈状況に応じたパース例外を生成する。\r
152      * 例外にはリージョン開始位置が埋め込まれる。\r
153      * @return パース例外\r
154      */\r
155     protected HtmlParseException buildParseException(){\r
156         HtmlParseException result;\r
157         result = new HtmlParseException(this.contextErrorMessage,\r
158                                         regionStart() );\r
159         return result;\r
160     }\r
161 \r
162     /**\r
163      * パースに使う正規表現パターンを切り替える。\r
164      * @param pattern 正規表現パターン\r
165      */\r
166     protected void switchPattern(Pattern pattern){\r
167         getMatcher().usePattern(pattern);\r
168         return;\r
169     }\r
170 \r
171     /**\r
172      * 最後のマッチに成功した文字領域以前をパース対象から外す。\r
173      */\r
174     protected void shrinkRegion(){\r
175         int lastMatchedEnd;\r
176         try{\r
177             lastMatchedEnd = matchEnd();\r
178         }catch(IllegalStateException e){\r
179             return;\r
180         }\r
181 \r
182         int regionEnd   = regionEnd();\r
183 \r
184         getMatcher().region(lastMatchedEnd, regionEnd);\r
185 \r
186         return;\r
187     }\r
188 \r
189     /**\r
190      * 検査対象の一部が指定パターンにマッチするか判定する。\r
191      * @param pattern 指定パターン\r
192      * @return マッチすればtrue\r
193      */\r
194     protected boolean findProbe(Pattern pattern){\r
195         switchPattern(pattern);\r
196         if( getMatcher().find() ) return true;\r
197         return false;\r
198     }\r
199 \r
200     /**\r
201      * 検査対象先頭が指定パターンにマッチするか判定する。\r
202      * @param pattern 指定パターン\r
203      * @return マッチすればtrue\r
204      */\r
205     protected boolean lookingAtProbe(Pattern pattern){\r
206         switchPattern(pattern);\r
207         if( getMatcher().lookingAt() ) return true;\r
208         return false;\r
209     }\r
210 \r
211     /**\r
212      * 検査対象全体が指定パターンにマッチするか判定する。\r
213      * @param pattern 指定パターン\r
214      * @return マッチすればtrue\r
215      */\r
216     protected boolean matchesProbe(Pattern pattern){\r
217         switchPattern(pattern);\r
218         if( getMatcher().matches() ) return true;\r
219         return false;\r
220     }\r
221 \r
222     /**\r
223      * 残りの検索対象領域からパターンがマッチする部分を探す。\r
224      * 見つからなければ例外をスローする。\r
225      * @param pattern 正規表現パターン\r
226      * @throws HtmlParseException\r
227      * マッチしなかった\r
228      */\r
229     protected void findAffirm(Pattern pattern)\r
230             throws HtmlParseException{\r
231         if( ! findProbe(pattern) ){\r
232             throw buildParseException();\r
233         }\r
234         return;\r
235     }\r
236 \r
237     /**\r
238      * 残りの検索対象領域先頭からパターンがマッチする部分を探す。\r
239      * 見つからなければ例外をスローする。\r
240      * @param pattern 正規表現パターン\r
241      * @throws HtmlParseException\r
242      * マッチしなかった\r
243      */\r
244     protected void lookingAtAffirm(Pattern pattern)\r
245             throws HtmlParseException{\r
246         if( ! lookingAtProbe(pattern) ){\r
247             throw buildParseException();\r
248         }\r
249         return;\r
250     }\r
251 \r
252     /**\r
253      * 残りの検索対象領域全体がパターンにマッチするか調べる。\r
254      * マッチしなければ例外をスローする。\r
255      * @param pattern 正規表現パターン\r
256      * @throws HtmlParseException\r
257      * マッチしなかった\r
258      */\r
259     protected void matchesAffirm(Pattern pattern)\r
260             throws HtmlParseException{\r
261         if( ! matchesProbe(pattern) ){\r
262             throw buildParseException();\r
263         }\r
264         return;\r
265     }\r
266 \r
267     /**\r
268      * 最後のマッチで任意の前方参照グループがヒットしたか判定する。\r
269      * @param group グループ番号\r
270      * @return ヒットしていたらtrue\r
271      */\r
272     protected boolean isGroupMatched(int group){\r
273         if(matchStart(group) >= 0) return true;\r
274         return false;\r
275     }\r
276 \r
277     /**\r
278      * 最後にマッチした前方参照グループを数値化する。\r
279      * 0以上の整数のみサポート。\r
280      * @param group グループ番号\r
281      * @return 数値\r
282      */\r
283     protected int parseGroupedInt(int group){\r
284         int result = 0;\r
285 \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
293         }\r
294 \r
295         return result;\r
296     }\r
297 \r
298     /**\r
299      * 最後にマッチした前方参照グループの開始位置を得る。\r
300      * @param group 前方参照識別番号\r
301      * @return 開始位置\r
302      */\r
303     protected int matchStart(int group){\r
304         return getMatcher().start(group);\r
305     }\r
306 \r
307     /**\r
308      * 最後にマッチした前方参照グループの終了位置を得る。\r
309      * @param group 前方参照識別番号\r
310      * @return 終了位置\r
311      */\r
312     protected int matchEnd(int group){\r
313         return getMatcher().end(group);\r
314     }\r
315 \r
316     /**\r
317      * 最後にマッチした全領域の開始位置を得る。\r
318      * @return 開始位置\r
319      */\r
320     protected int matchStart(){\r
321         return getMatcher().start();\r
322     }\r
323 \r
324     /**\r
325      * 最後にマッチした全領域の終了位置を得る。\r
326      * @return 終了位置\r
327      */\r
328     protected int matchEnd(){\r
329         return getMatcher().end();\r
330     }\r
331 \r
332     /**\r
333      * 検索領域の先頭位置を返す。\r
334      * @return 先頭位置\r
335      */\r
336     protected int regionStart(){\r
337         return getMatcher().regionStart();\r
338     }\r
339 \r
340     /**\r
341      * 検索領域の末尾位置を返す。\r
342      * @return 末尾位置\r
343      */\r
344     protected int regionEnd(){\r
345         return getMatcher().regionEnd();\r
346     }\r
347 \r
348     /**\r
349      * 0個以上のホワイトスペースを読み飛ばす。\r
350      * 具体的には検索対象領域の先頭が進むだけ。\r
351      */\r
352     protected void sweepSpace(){\r
353         CharSequence rawContent = getContent().getRawContent();\r
354 \r
355         boolean hasSpace = false;\r
356         int regionStart = regionStart();\r
357         int regionEnd   = regionEnd();\r
358 \r
359         for( ; regionStart < regionEnd; regionStart++){\r
360             char letter = rawContent.charAt(regionStart);\r
361 \r
362             switch(letter){\r
363             case '\u0020':\r
364             case '\t':\r
365             case '\n':\r
366             case '\r':\r
367                 hasSpace = true;\r
368                 continue;\r
369             default:\r
370                 break;\r
371             }\r
372 \r
373             break;\r
374         }\r
375 \r
376         if(hasSpace){\r
377             getMatcher().region(regionStart, regionEnd);\r
378         }\r
379 \r
380         return;\r
381     }\r
382 \r
383     /**\r
384      * 検索領域の先頭から各種役職名のマッチを試みる。\r
385      * @return 役職。何もマッチしなければnullを返す。\r
386      */\r
387     protected GameRole lookingAtRole(){\r
388         GameRole role = GameRole.lookingAtRole(getMatcher());\r
389         return role;\r
390     }\r
391 \r
392 }\r