--- /dev/null
+/*\r
+ * anchor\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: Anchor.java 1015 2010-03-16 11:21:21Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf;\r
+\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+/**\r
+ * 発言アンカー。\r
+ */\r
+public final class Anchor{\r
+\r
+ private static final int EPILOGUEDAY = 99;\r
+ private static final Pattern ANCHOR_PATTERN;\r
+\r
+ static{\r
+ String spchar = "\u0020\u3000\\t";\r
+ String sp = "[" +spchar+ "]";\r
+ String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?";\r
+\r
+ String day = // TODO 「昨日」なども含めるか?\r
+ "("\r
+ +"(?:"\r
+ + "(プロ(?:ローグ)?)"\r
+ +"|"+"(エピ(?:ローグ)?)"\r
+ +"|"+"(?:"\r
+ + "([1-91-9]?[0-90-9])"\r
+ +sp_n+ "(?:[dDdD]|(?:日目?))"\r
+ +")"\r
+ +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n\r
+ +")?";\r
+ String ampm =\r
+ "("\r
+ +"(?:"\r
+ + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))"\r
+ +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))"\r
+ +")" +sp_n\r
+ +")?";\r
+ String hhmm =\r
+ "(?:"+\r
+ "("\r
+ +"([0-20-2]?[0-90-9])"\r
+ +sp_n+ "[:;:;]?" +sp_n\r
+ +"([0-50-5][0-90-9])"\r
+ +")"\r
+ +"|"\r
+ +"("\r
+ +"([0-20-2]?[0-90-9])"\r
+ +sp_n+ "時" +sp_n\r
+ +"([0-50-5]?[0-90-9])"\r
+ +sp_n+ "分"\r
+ +")"\r
+ +")";\r
+\r
+ String talkNum =\r
+ "(?:>>([1-9][0-9]*))";\r
+\r
+ ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum,\r
+ Pattern.DOTALL);\r
+ }\r
+\r
+ /**\r
+ * 与えられた範囲指定文字列からアンカーを抽出する。\r
+ * @param source 検索対象文字列\r
+ * @param regionStart 範囲開始位置\r
+ * @param regionEnd 範囲終了位置\r
+ * @param currentDay 相対日付の基本となる日\r
+ * @return アンカー\r
+ */\r
+ public static Anchor getAnchor(CharSequence source,\r
+ int regionStart,\r
+ int regionEnd,\r
+ int currentDay ){\r
+ Matcher matcher = ANCHOR_PATTERN.matcher(source);\r
+ matcher.region(regionStart, regionEnd);\r
+\r
+ if( ! matcher.find() ) return null;\r
+\r
+ Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);\r
+\r
+ return anchor;\r
+ }\r
+\r
+ /**\r
+ * 与えられた文字列から全アンカーを抽出する。\r
+ * @param source 検索対象文字列\r
+ * @param currentDay 相対日付の基本となる日\r
+ * @return アンカーのリスト(出現順)\r
+ */\r
+ public static List<Anchor> getAnchorList(CharSequence source,\r
+ int currentDay ){\r
+ List<Anchor> result = new LinkedList<Anchor>();\r
+\r
+ Matcher matcher = ANCHOR_PATTERN.matcher(source);\r
+ int regionEnd = source.length();\r
+\r
+ while(matcher.find()){\r
+ Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);\r
+ result.add(anchor);\r
+ int regionStart = matcher.end();\r
+ matcher.region(regionStart, regionEnd);\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 文字列とそのMatcherからアンカーを抽出する。\r
+ * @param source 検索対象文字列\r
+ * @param matcher Matcher\r
+ * @param currentDay 相対日付の基本となる日\r
+ * @return アンカー\r
+ */\r
+ private static Anchor getAnchorFromMatched(CharSequence source,\r
+ Matcher matcher,\r
+ int currentDay){\r
+ int startPos = matcher.start();\r
+ int endPos = matcher.end();\r
+\r
+ /* G国アンカー */\r
+ if(matcher.start(14) < matcher.end(14)){\r
+ int talkNo = StringUtils.parseInt(source, matcher, 14);\r
+ Anchor anchor = new Anchor(source, startPos, endPos, talkNo);\r
+ return anchor;\r
+ }\r
+\r
+ int day = currentDay;\r
+ if(matcher.start(1) < matcher.end(1)){\r
+ if(matcher.start(2) < matcher.end(2)){ // prologue\r
+ day = 0;\r
+ }else if(matcher.start(3) < matcher.end(3)){ // epilogue\r
+ day = EPILOGUEDAY;\r
+ }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d"\r
+ day = StringUtils.parseInt(source, matcher, 4);\r
+ }else{\r
+ assert false;\r
+ return null;\r
+ }\r
+ }\r
+\r
+ boolean isPM = false;\r
+ if(matcher.start(5) < matcher.end(5)){\r
+ if(matcher.start(6) < matcher.end(6)){ // AM\r
+ isPM = false;\r
+ }else if(matcher.start(7) < matcher.end(7)){ // PM\r
+ isPM = true;\r
+ }else{\r
+ assert false;\r
+ return null;\r
+ }\r
+ }\r
+\r
+ int hourGroup;\r
+ int minuteGroup;\r
+ if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm\r
+ hourGroup = 9;\r
+ minuteGroup = 10;\r
+ }else if(matcher.start(11) < matcher.end(11)){ // h時m分\r
+ hourGroup = 12;\r
+ minuteGroup = 13;\r
+ }else{\r
+ assert false;\r
+ return null;\r
+ }\r
+ int hour = StringUtils.parseInt(source, matcher, hourGroup);\r
+ int minute = StringUtils.parseInt(source, matcher, minuteGroup);\r
+\r
+ if(isPM && hour < 12) hour += 12;\r
+ hour %= 24;\r
+ // 午後12:34は午後00:34になる\r
+\r
+ // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか?\r
+ // とりあえず前者\r
+\r
+ Anchor anchor = new Anchor(source, startPos, endPos,\r
+ day, hour, minute);\r
+\r
+ return anchor;\r
+ }\r
+\r
+ private final CharSequence source;\r
+ private final int startPos;\r
+ private final int endPos;\r
+ private final int day;\r
+ private final int hour;\r
+ private final int minute;\r
+ private final int talkNo;\r
+\r
+ /**\r
+ * アンカーのコンストラクタ。\r
+ * @param source アンカーが含まれる文字列\r
+ * @param startPos アンカーの始まる位置\r
+ * @param endPos アンカーの終わる位置\r
+ * @param day 日\r
+ * @param hour 時間(0-23)\r
+ * @param minute 分(0-59)\r
+ */\r
+ private Anchor(CharSequence source, int startPos, int endPos,\r
+ int day, int hour, int minute ){\r
+ super();\r
+\r
+ this.source = source;\r
+ this.startPos = startPos;\r
+ this.endPos = endPos;\r
+ this.day = day;\r
+ this.hour = hour;\r
+ this.minute = minute;\r
+ this.talkNo = -1;\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * アンカーのコンストラクタ。\r
+ * @param source アンカーが含まれる文字列\r
+ * @param startPos アンカーの始まる位置\r
+ * @param endPos アンカーの終わる位置\r
+ * @param talkNo 公開発言番号\r
+ */\r
+ private Anchor(CharSequence source, int startPos, int endPos,\r
+ int talkNo ){\r
+ super();\r
+\r
+ if(talkNo <= 0) throw new IllegalArgumentException();\r
+\r
+ this.source = source;\r
+ this.startPos = startPos;\r
+ this.endPos = endPos;\r
+ this.day = -1;\r
+ this.hour = -1;\r
+ this.minute = -1;\r
+ this.talkNo = talkNo;\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * アンカーの含まれる文字列を返す。\r
+ * @return アンカーの含まれる文字列\r
+ */\r
+ public CharSequence getSource(){\r
+ return this.source;\r
+ }\r
+\r
+ /**\r
+ * アンカーの開始位置を返す。\r
+ * @return アンカー開始位置\r
+ */\r
+ public int getStartPos(){\r
+ return this.startPos;\r
+ }\r
+\r
+ /**\r
+ * アンカーの終了位置を返す。\r
+ * @return アンカー終了位置\r
+ */\r
+ public int getEndPos(){\r
+ return this.endPos;\r
+ }\r
+\r
+ /**\r
+ * アンカーの示す日付を返す。\r
+ * @return 日付\r
+ */\r
+ public int getDay(){\r
+ return this.day;\r
+ }\r
+\r
+ /**\r
+ * アンカーの示す時刻を返す。\r
+ * @return 時刻(0-23)\r
+ */\r
+ public int getHour(){\r
+ return this.hour;\r
+ }\r
+\r
+ /**\r
+ * アンカーの示す分を返す。\r
+ * @return 分(0-59)\r
+ */\r
+ public int getMinute(){\r
+ return this.minute;\r
+ }\r
+\r
+ /**\r
+ * アンカーの示す公開発言番号を返す。\r
+ * @return 公開発言番号。公開発言番号でない場合は0以下の値。\r
+ */\r
+ public int getTalkNo(){\r
+ return this.talkNo;\r
+ }\r
+\r
+ /**\r
+ * このアンカーが公開発言番号による物か判定する。\r
+ * @return 公開発言番号由来であるならtrue\r
+ */\r
+ public boolean hasTalkNo(){\r
+ return 0 < this.talkNo;\r
+ }\r
+\r
+ /**\r
+ * 明示的なエピローグへのアンカーか判定する。\r
+ * @return 明示的なエピローグへのアンカーならtrue\r
+ */\r
+ public boolean isEpilogueDay(){\r
+ if(this.day >= EPILOGUEDAY) return true;\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * アンカーの文字列表記を返す。\r
+ * 出典:まとめサイトの用語集\r
+ * @return アンカーの文字列表記\r
+ */\r
+ @Override\r
+ public String toString(){\r
+ /* G国表記 */\r
+ if(hasTalkNo()){\r
+ return ">>" + this.talkNo;\r
+ }\r
+\r
+ StringBuilder result = new StringBuilder();\r
+\r
+ result.append(getDay()).append('d');\r
+\r
+ int anchorHour = getHour();\r
+ if(anchorHour < 10) result.append('0');\r
+ result.append(anchorHour).append(':');\r
+\r
+ int anchorMinute = getMinute();\r
+ if(anchorMinute < 10) result.append('0');\r
+ result.append(anchorMinute);\r
+\r
+ return result.toString();\r
+ }\r
+\r
+}\r