OSDN Git Service

Merge commit '2458eff3aea04f67893bc824b5cf896fbb767332'
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Anchor.java
diff --git a/src/main/java/jp/sourceforge/jindolf/Anchor.java b/src/main/java/jp/sourceforge/jindolf/Anchor.java
new file mode 100644 (file)
index 0000000..21a0d7c
--- /dev/null
@@ -0,0 +1,344 @@
+/*\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