-/*\r
- * abstract XHTML parser\r
- *\r
- * Copyright(c) 2009 olyutorskii\r
- * $Id: AbstractParser.java 894 2009-11-04 07:26:59Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf.parser;\r
-\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-import jp.sourceforge.jindolf.corelib.GameRole;\r
-\r
-/**\r
- * 人狼BBS生成のXHTML文書を解釈するパーサの抽象基底クラス。\r
- * {@link DecodedContent}の内容をパースし、\r
- * 各種ハンドラへ通知する処理の基盤を構成する。\r
- * 正規表現エンジンを実装基盤とする。\r
- * 親パーサを指定することにより、検索対象文字列とマッチエンジンを\r
- * 親パーサと共有することができる。\r
- * @see Matcher\r
- */\r
-public abstract class AbstractParser implements ChainedParser{\r
-\r
- /** ホワイトスペース。 */\r
- protected static final String SPCHAR = "\u0020\\t\\n\\r";\r
- /** 0回以上連続するホワイトスペースの正規表現。 */\r
- protected static final String SP_I = "[" +SPCHAR+ "]*";\r
-\r
- private static final Pattern DUMMY_PATTERN = compile("\u0000");\r
-\r
- /**\r
- * 正規表現のコンパイルを行う。\r
- * デフォルトで{@link java.util.regex.Pattern#DOTALL}が\r
- * オプション指定される。\r
- * @param regex 正規表現文字列\r
- * @return マッチエンジン\r
- */\r
- protected static Pattern compile(CharSequence regex){\r
- Pattern result = Pattern.compile(regex.toString(), Pattern.DOTALL);\r
- return result;\r
- }\r
-\r
- private final ChainedParser parent;\r
-\r
- private DecodedContent content;\r
- private Matcher matcher;\r
- private String contextErrorMessage;\r
-\r
- /**\r
- * コンストラクタ。\r
- */\r
- protected AbstractParser(){\r
- this(null);\r
- return;\r
- }\r
-\r
- /**\r
- * コンストラクタ。\r
- * @param parent 親パーサ\r
- */\r
- protected AbstractParser(ChainedParser parent){\r
- super();\r
- this.parent = parent;\r
- resetImpl();\r
- return;\r
- }\r
-\r
- /**\r
- * パーサの状態をコンストラクタ直後の状態にリセットする。\r
- * ※コンストラクタから呼ばせるためにオーバーライド不可\r
- */\r
- private void resetImpl(){\r
- this.content = null;\r
- this.matcher = null;\r
- this.contextErrorMessage = null;\r
- return;\r
- }\r
-\r
- /**\r
- * パーサの状態をリセットする。\r
- */\r
- public void reset(){\r
- if(this.parent != null){\r
- throw new UnsupportedOperationException();\r
- }\r
- resetImpl();\r
- return;\r
- }\r
-\r
- /**\r
- * {@inheritDoc}\r
- * @param content {@inheritDoc}\r
- */\r
- /**\r
- * パース対象文字列をセットする。\r
- * パースが終わるまでこの文字列の内容を変更してはならない。\r
- * @param content パース対象文字列\r
- */\r
- public void setContent(DecodedContent content){\r
- if(this.parent != null){\r
- throw new UnsupportedOperationException();\r
- }\r
-\r
- CharSequence rawContent = content.getRawContent();\r
-\r
- this.content = content;\r
- this.matcher = DUMMY_PATTERN.matcher(rawContent);\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * {@inheritDoc}\r
- * @return {@inheritDoc}\r
- */\r
- public DecodedContent getContent(){\r
- if(this.parent != null){\r
- return this.parent.getContent();\r
- }\r
-\r
- return this.content;\r
- }\r
-\r
- /**\r
- * {@inheritDoc}\r
- * @return {@inheritDoc}\r
- */\r
- public Matcher getMatcher(){\r
- if(this.parent != null){\r
- return this.parent.getMatcher();\r
- }\r
-\r
- return this.matcher;\r
- }\r
-\r
- /**\r
- * 文脈依存のエラーメッセージを設定する。\r
- * {@link #buildParseException}で利用される。\r
- * 設定内容は親へ委譲されない。\r
- * @param errorMessage エラーメッセージ。nullも可能。\r
- */\r
- protected void setContextErrorMessage(String errorMessage){\r
- this.contextErrorMessage = errorMessage;\r
- return;\r
- }\r
-\r
- /**\r
- * 文脈状況に応じたパース例外を生成する。\r
- * 例外にはリージョン開始位置が埋め込まれる。\r
- * @return パース例外\r
- */\r
- protected HtmlParseException buildParseException(){\r
- HtmlParseException result;\r
- result = new HtmlParseException(this.contextErrorMessage,\r
- regionStart() );\r
- return result;\r
- }\r
-\r
- /**\r
- * パースに使う正規表現パターンを切り替える。\r
- * @param pattern 正規表現パターン\r
- */\r
- protected void switchPattern(Pattern pattern){\r
- getMatcher().usePattern(pattern);\r
- return;\r
- }\r
-\r
- /**\r
- * 最後のマッチに成功した文字領域以前をパース対象から外す。\r
- */\r
- protected void shrinkRegion(){\r
- int lastMatchedEnd;\r
- try{\r
- lastMatchedEnd = matchEnd();\r
- }catch(IllegalStateException e){\r
- return;\r
- }\r
-\r
- int regionEnd = regionEnd();\r
-\r
- getMatcher().region(lastMatchedEnd, regionEnd);\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 検査対象の一部が指定パターンにマッチするか判定する。\r
- * @param pattern 指定パターン\r
- * @return マッチすればtrue\r
- */\r
- protected boolean findProbe(Pattern pattern){\r
- switchPattern(pattern);\r
- if( getMatcher().find() ) return true;\r
- return false;\r
- }\r
-\r
- /**\r
- * 検査対象先頭が指定パターンにマッチするか判定する。\r
- * @param pattern 指定パターン\r
- * @return マッチすればtrue\r
- */\r
- protected boolean lookingAtProbe(Pattern pattern){\r
- switchPattern(pattern);\r
- if( getMatcher().lookingAt() ) return true;\r
- return false;\r
- }\r
-\r
- /**\r
- * 検査対象全体が指定パターンにマッチするか判定する。\r
- * @param pattern 指定パターン\r
- * @return マッチすればtrue\r
- */\r
- protected boolean matchesProbe(Pattern pattern){\r
- switchPattern(pattern);\r
- if( getMatcher().matches() ) return true;\r
- return false;\r
- }\r
-\r
- /**\r
- * 残りの検索対象領域からパターンがマッチする部分を探す。\r
- * 見つからなければ例外をスローする。\r
- * @param pattern 正規表現パターン\r
- * @throws HtmlParseException\r
- * マッチしなかった\r
- */\r
- protected void findAffirm(Pattern pattern)\r
- throws HtmlParseException{\r
- if( ! findProbe(pattern) ){\r
- throw buildParseException();\r
- }\r
- return;\r
- }\r
-\r
- /**\r
- * 残りの検索対象領域先頭からパターンがマッチする部分を探す。\r
- * 見つからなければ例外をスローする。\r
- * @param pattern 正規表現パターン\r
- * @throws HtmlParseException\r
- * マッチしなかった\r
- */\r
- protected void lookingAtAffirm(Pattern pattern)\r
- throws HtmlParseException{\r
- if( ! lookingAtProbe(pattern) ){\r
- throw buildParseException();\r
- }\r
- return;\r
- }\r
-\r
- /**\r
- * 残りの検索対象領域全体がパターンにマッチするか調べる。\r
- * マッチしなければ例外をスローする。\r
- * @param pattern 正規表現パターン\r
- * @throws HtmlParseException\r
- * マッチしなかった\r
- */\r
- protected void matchesAffirm(Pattern pattern)\r
- throws HtmlParseException{\r
- if( ! matchesProbe(pattern) ){\r
- throw buildParseException();\r
- }\r
- return;\r
- }\r
-\r
- /**\r
- * 最後のマッチで任意の前方参照グループがヒットしたか判定する。\r
- * @param group グループ番号\r
- * @return ヒットしていたらtrue\r
- */\r
- protected boolean isGroupMatched(int group){\r
- if(matchStart(group) >= 0) return true;\r
- return false;\r
- }\r
-\r
- /**\r
- * 最後にマッチした前方参照グループを数値化する。\r
- * 0以上の整数のみサポート。\r
- * @param group グループ番号\r
- * @return 数値\r
- */\r
- protected int parseGroupedInt(int group){\r
- int result = 0;\r
-\r
- CharSequence rawContent = getContent().getRawContent();\r
- int start = matchStart(group);\r
- int end = matchEnd(group);\r
- for(int pos = start; pos < end; pos++){\r
- char letter = rawContent.charAt(pos);\r
- int digit = Character.digit(letter, 10);\r
- result = result * 10 + digit;\r
- }\r
-\r
- return result;\r
- }\r
-\r
- /**\r
- * 最後にマッチした前方参照グループの開始位置を得る。\r
- * @param group 前方参照識別番号\r
- * @return 開始位置\r
- */\r
- protected int matchStart(int group){\r
- return getMatcher().start(group);\r
- }\r
-\r
- /**\r
- * 最後にマッチした前方参照グループの終了位置を得る。\r
- * @param group 前方参照識別番号\r
- * @return 終了位置\r
- */\r
- protected int matchEnd(int group){\r
- return getMatcher().end(group);\r
- }\r
-\r
- /**\r
- * 最後にマッチした全領域の開始位置を得る。\r
- * @return 開始位置\r
- */\r
- protected int matchStart(){\r
- return getMatcher().start();\r
- }\r
-\r
- /**\r
- * 最後にマッチした全領域の終了位置を得る。\r
- * @return 終了位置\r
- */\r
- protected int matchEnd(){\r
- return getMatcher().end();\r
- }\r
-\r
- /**\r
- * 検索領域の先頭位置を返す。\r
- * @return 先頭位置\r
- */\r
- protected int regionStart(){\r
- return getMatcher().regionStart();\r
- }\r
-\r
- /**\r
- * 検索領域の末尾位置を返す。\r
- * @return 末尾位置\r
- */\r
- protected int regionEnd(){\r
- return getMatcher().regionEnd();\r
- }\r
-\r
- /**\r
- * 0個以上のホワイトスペースを読み飛ばす。\r
- * 具体的には検索対象領域の先頭が進むだけ。\r
- */\r
- protected void sweepSpace(){\r
- CharSequence rawContent = getContent().getRawContent();\r
-\r
- boolean hasSpace = false;\r
- int regionStart = regionStart();\r
- int regionEnd = regionEnd();\r
-\r
- for( ; regionStart < regionEnd; regionStart++){\r
- char letter = rawContent.charAt(regionStart);\r
-\r
- switch(letter){\r
- case '\u0020':\r
- case '\t':\r
- case '\n':\r
- case '\r':\r
- hasSpace = true;\r
- continue;\r
- default:\r
- break;\r
- }\r
-\r
- break;\r
- }\r
-\r
- if(hasSpace){\r
- getMatcher().region(regionStart, regionEnd);\r
- }\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 検索領域の先頭から各種役職名のマッチを試みる。\r
- * @return 役職。何もマッチしなければnullを返す。\r
- */\r
- protected GameRole lookingAtRole(){\r
- GameRole role = GameRole.lookingAtRole(getMatcher());\r
- return role;\r
- }\r
-\r
-}\r
+/*
+ * abstract XHTML parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jindolf.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import jp.sourceforge.jindolf.corelib.GameRole;
+
+/**
+ * 人狼BBS生成のXHTML文書を解釈するパーサの抽象基底クラス。
+ * {@link DecodedContent}の内容をパースし、
+ * 各種ハンドラへ通知する処理の基盤を構成する。
+ * 正規表現エンジンを実装基盤とする。
+ * 親パーサを指定することにより、検索対象文字列とマッチエンジンを
+ * 親パーサと共有することができる。
+ * @see Matcher
+ */
+public abstract class AbstractParser implements ChainedParser{
+
+ /** ホワイトスペース。 */
+ protected static final String SPCHAR = "\u0020\\t\\n\\r";
+ /** 0回以上連続するホワイトスペースの正規表現。 */
+ protected static final String SP_I = "[" +SPCHAR+ "]*";
+
+ private static final Pattern DUMMY_PATTERN = compile("\u0000");
+
+
+ private final ChainedParser parent;
+
+ private DecodedContent content;
+ private Matcher matcher;
+ private String contextErrorMessage;
+
+
+ /**
+ * コンストラクタ。
+ */
+ protected AbstractParser(){
+ this(null);
+ return;
+ }
+
+ /**
+ * コンストラクタ。
+ * @param parent 親パーサ
+ */
+ protected AbstractParser(ChainedParser parent){
+ super();
+ this.parent = parent;
+ resetImpl();
+ return;
+ }
+
+
+ /**
+ * 正規表現のコンパイルを行う。
+ * デフォルトで{@link java.util.regex.Pattern#DOTALL}が
+ * オプション指定される。
+ * @param regex 正規表現文字列
+ * @return マッチエンジン
+ */
+ protected static Pattern compile(CharSequence regex){
+ Pattern result = Pattern.compile(regex.toString(), Pattern.DOTALL);
+ return result;
+ }
+
+ /**
+ * パーサの状態をコンストラクタ直後の状態にリセットする。
+ * ※コンストラクタから呼ばせるためにオーバーライド不可
+ */
+ private void resetImpl(){
+ this.content = null;
+ this.matcher = null;
+ this.contextErrorMessage = null;
+ return;
+ }
+
+ /**
+ * パーサの状態をリセットする。
+ */
+ public void reset(){
+ if(this.parent != null){
+ throw new UnsupportedOperationException();
+ }
+ resetImpl();
+ return;
+ }
+
+ /**
+ * パース対象文字列をセットする。
+ * パースが終わるまでこの文字列の内容を変更してはならない。
+ * @param content パース対象文字列
+ */
+ public void setContent(DecodedContent content){
+ if(this.parent != null){
+ throw new UnsupportedOperationException();
+ }
+
+ CharSequence rawContent = content.getRawContent();
+
+ this.content = content;
+ this.matcher = DUMMY_PATTERN.matcher(rawContent);
+
+ return;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ @Override
+ public DecodedContent getContent(){
+ if(this.parent != null){
+ return this.parent.getContent();
+ }
+
+ return this.content;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ @Override
+ public Matcher getMatcher(){
+ if(this.parent != null){
+ return this.parent.getMatcher();
+ }
+
+ return this.matcher;
+ }
+
+ /**
+ * 文脈依存のエラーメッセージを設定する。
+ * {@link #buildParseException}で利用される。
+ * 設定内容は親へ委譲されない。
+ * @param errorMessage エラーメッセージ。nullも可能。
+ */
+ protected void setContextErrorMessage(String errorMessage){
+ this.contextErrorMessage = errorMessage;
+ return;
+ }
+
+ /**
+ * 文脈状況に応じたパース例外を生成する。
+ * 例外にはリージョン開始位置が埋め込まれる。
+ * @return パース例外
+ */
+ protected HtmlParseException buildParseException(){
+ HtmlParseException result;
+ result = new HtmlParseException(this.contextErrorMessage,
+ regionStart() );
+ return result;
+ }
+
+ /**
+ * パースに使う正規表現パターンを切り替える。
+ * @param pattern 正規表現パターン
+ */
+ protected void switchPattern(Pattern pattern){
+ getMatcher().usePattern(pattern);
+ return;
+ }
+
+ /**
+ * 最後のマッチに成功した文字領域以前をパース対象から外す。
+ */
+ protected void shrinkRegion(){
+ int lastMatchedEnd;
+ try{
+ lastMatchedEnd = matchEnd();
+ }catch(IllegalStateException e){
+ return;
+ }
+
+ int regionEnd = regionEnd();
+
+ getMatcher().region(lastMatchedEnd, regionEnd);
+
+ return;
+ }
+
+ /**
+ * 検査対象の一部が指定パターンにマッチするか判定する。
+ * @param pattern 指定パターン
+ * @return マッチすればtrue
+ */
+ protected boolean findProbe(Pattern pattern){
+ switchPattern(pattern);
+ if( getMatcher().find() ) return true;
+ return false;
+ }
+
+ /**
+ * 検査対象先頭が指定パターンにマッチするか判定する。
+ * @param pattern 指定パターン
+ * @return マッチすればtrue
+ */
+ protected boolean lookingAtProbe(Pattern pattern){
+ switchPattern(pattern);
+ if( getMatcher().lookingAt() ) return true;
+ return false;
+ }
+
+ /**
+ * 検査対象全体が指定パターンにマッチするか判定する。
+ * @param pattern 指定パターン
+ * @return マッチすればtrue
+ */
+ protected boolean matchesProbe(Pattern pattern){
+ switchPattern(pattern);
+ if( getMatcher().matches() ) return true;
+ return false;
+ }
+
+ /**
+ * 残りの検索対象領域からパターンがマッチする部分を探す。
+ * 見つからなければ例外をスローする。
+ * @param pattern 正規表現パターン
+ * @throws HtmlParseException マッチしなかった
+ */
+ protected void findAffirm(Pattern pattern)
+ throws HtmlParseException{
+ if( ! findProbe(pattern) ){
+ throw buildParseException();
+ }
+ return;
+ }
+
+ /**
+ * 残りの検索対象領域先頭からパターンがマッチする部分を探す。
+ * 見つからなければ例外をスローする。
+ * @param pattern 正規表現パターン
+ * @throws HtmlParseException マッチしなかった
+ */
+ protected void lookingAtAffirm(Pattern pattern)
+ throws HtmlParseException{
+ if( ! lookingAtProbe(pattern) ){
+ throw buildParseException();
+ }
+ return;
+ }
+
+ /**
+ * 残りの検索対象領域全体がパターンにマッチするか調べる。
+ * マッチしなければ例外をスローする。
+ * @param pattern 正規表現パターン
+ * @throws HtmlParseException マッチしなかった
+ */
+ protected void matchesAffirm(Pattern pattern)
+ throws HtmlParseException{
+ if( ! matchesProbe(pattern) ){
+ throw buildParseException();
+ }
+ return;
+ }
+
+ /**
+ * 最後のマッチで任意の前方参照グループがヒットしたか判定する。
+ * @param group グループ番号
+ * @return ヒットしていたらtrue
+ */
+ protected boolean isGroupMatched(int group){
+ if(matchStart(group) >= 0) return true;
+ return false;
+ }
+
+ /**
+ * 最後にマッチした前方参照グループを数値化する。
+ * 0以上の整数のみサポート。
+ * @param group グループ番号
+ * @return 数値
+ */
+ protected int parseGroupedInt(int group){
+ int result = 0;
+
+ CharSequence rawContent = getContent().getRawContent();
+ int start = matchStart(group);
+ int end = matchEnd(group);
+ for(int pos = start; pos < end; pos++){
+ char letter = rawContent.charAt(pos);
+ int digit = Character.digit(letter, 10);
+ result = result * 10 + digit;
+ }
+
+ return result;
+ }
+
+ /**
+ * 最後にマッチした前方参照グループの開始位置を得る。
+ * @param group 前方参照識別番号
+ * @return 開始位置
+ */
+ protected int matchStart(int group){
+ return getMatcher().start(group);
+ }
+
+ /**
+ * 最後にマッチした全領域の開始位置を得る。
+ * @return 開始位置
+ */
+ protected int matchStart(){
+ return getMatcher().start();
+ }
+
+ /**
+ * 最後にマッチした前方参照グループの終了位置を得る。
+ * @param group 前方参照識別番号
+ * @return 終了位置
+ */
+ protected int matchEnd(int group){
+ return getMatcher().end(group);
+ }
+
+ /**
+ * 最後にマッチした全領域の終了位置を得る。
+ * @return 終了位置
+ */
+ protected int matchEnd(){
+ return getMatcher().end();
+ }
+
+ /**
+ * 検索領域の先頭位置を返す。
+ * @return 先頭位置
+ */
+ protected int regionStart(){
+ return getMatcher().regionStart();
+ }
+
+ /**
+ * 検索領域の末尾位置を返す。
+ * @return 末尾位置
+ */
+ protected int regionEnd(){
+ return getMatcher().regionEnd();
+ }
+
+ /**
+ * 0個以上のホワイトスペースを読み飛ばす。
+ * 具体的には検索対象領域の先頭が進むだけ。
+ */
+ protected void sweepSpace(){
+ CharSequence rawContent = getContent().getRawContent();
+
+ boolean hasSpace = false;
+ int regionStart = regionStart();
+ int regionEnd = regionEnd();
+
+ for( ; regionStart < regionEnd; regionStart++){
+ char letter = rawContent.charAt(regionStart);
+
+ switch(letter){
+ case '\u0020':
+ case '\t':
+ case '\n':
+ case '\r':
+ hasSpace = true;
+ continue;
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ if(hasSpace){
+ getMatcher().region(regionStart, regionEnd);
+ }
+
+ return;
+ }
+
+ /**
+ * 検索領域の先頭から各種役職名のマッチを試みる。
+ * @return 役職。何もマッチしなければnullを返す。
+ */
+ protected GameRole lookingAtRole(){
+ GameRole role = GameRole.lookingAtRole(getMatcher());
+ return role;
+ }
+
+}