OSDN Git Service

e2ccbd1d88ed1668d59530a4bf30e22946925683
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Anchor.java
1 /*\r
2  * anchor\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2008 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.util.LinkedList;\r
11 import java.util.List;\r
12 import java.util.regex.Matcher;\r
13 import java.util.regex.Pattern;\r
14 \r
15 /**\r
16  * 発言アンカー。\r
17  */\r
18 public final class Anchor{\r
19 \r
20     private static final int EPILOGUEDAY = 99;\r
21     private static final Pattern ANCHOR_PATTERN;\r
22 \r
23     static{\r
24         String spchar = "\u0020\u3000\\t";\r
25         String sp = "[" +spchar+ "]";\r
26         String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?";\r
27 \r
28         String day =   // TODO 「昨日」なども含めるか?\r
29                 "("\r
30                     +"(?:"\r
31                         +    "(プロ(?:ローグ)?)"\r
32                         +"|"+"(エピ(?:ローグ)?)"\r
33                         +"|"+"(?:"\r
34                                 + "([1-91-9]?[0-90-9])"\r
35                                 +sp_n+ "(?:[dDdD]|(?:日目?))"\r
36                             +")"\r
37                     +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n\r
38                 +")?";\r
39         String ampm =\r
40                 "("\r
41                     +"(?:"\r
42                         +    "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))"\r
43                         +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))"\r
44                     +")" +sp_n\r
45                 +")?";\r
46         String hhmm =\r
47                 "(?:"+\r
48                     "("\r
49                         +"([0-20-2]?[0-90-9])"\r
50                             +sp_n+ "[:;:;]?" +sp_n\r
51                         +"([0-50-5][0-90-9])"\r
52                     +")"\r
53                         +"|"\r
54                     +"("\r
55                         +"([0-20-2]?[0-90-9])"\r
56                             +sp_n+ "時" +sp_n\r
57                         +"([0-50-5]?[0-90-9])"\r
58                             +sp_n+ "分"\r
59                     +")"\r
60                 +")";\r
61 \r
62         String talkNum =\r
63                 "(?:>>([1-9][0-9]*))";\r
64 \r
65         ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum,\r
66                                          Pattern.DOTALL);\r
67     }\r
68 \r
69 \r
70     private final CharSequence source;\r
71     private final int startPos;\r
72     private final int endPos;\r
73     private final int day;\r
74     private final int hour;\r
75     private final int minute;\r
76     private final int talkNo;\r
77 \r
78 \r
79     /**\r
80      * アンカーのコンストラクタ。\r
81      * @param source アンカーが含まれる文字列\r
82      * @param startPos アンカーの始まる位置\r
83      * @param endPos アンカーの終わる位置\r
84      * @param talkNo 公開発言番号\r
85      */\r
86     private Anchor(CharSequence source, int startPos, int endPos,\r
87                     int talkNo ){\r
88         super();\r
89 \r
90         if(talkNo <= 0) throw new IllegalArgumentException();\r
91 \r
92         this.source = source;\r
93         this.startPos = startPos;\r
94         this.endPos = endPos;\r
95         this.day = -1;\r
96         this.hour = -1;\r
97         this.minute = -1;\r
98         this.talkNo = talkNo;\r
99 \r
100         return;\r
101     }\r
102 \r
103     /**\r
104      * アンカーのコンストラクタ。\r
105      * @param source アンカーが含まれる文字列\r
106      * @param startPos アンカーの始まる位置\r
107      * @param endPos アンカーの終わる位置\r
108      * @param day 日\r
109      * @param hour 時間(0-23)\r
110      * @param minute 分(0-59)\r
111      */\r
112     private Anchor(CharSequence source, int startPos, int endPos,\r
113                     int day, int hour, int minute                 ){\r
114         super();\r
115 \r
116         this.source = source;\r
117         this.startPos = startPos;\r
118         this.endPos = endPos;\r
119         this.day = day;\r
120         this.hour = hour;\r
121         this.minute = minute;\r
122         this.talkNo = -1;\r
123 \r
124         return;\r
125     }\r
126 \r
127 \r
128     /**\r
129      * 与えられた範囲指定文字列からアンカーを抽出する。\r
130      * @param source 検索対象文字列\r
131      * @param regionStart 範囲開始位置\r
132      * @param regionEnd 範囲終了位置\r
133      * @param currentDay 相対日付の基本となる日\r
134      * @return アンカー\r
135      */\r
136     public static Anchor getAnchor(CharSequence source,\r
137                                     int regionStart,\r
138                                     int regionEnd,\r
139                                     int currentDay      ){\r
140         Matcher matcher = ANCHOR_PATTERN.matcher(source);\r
141         matcher.region(regionStart, regionEnd);\r
142 \r
143         if( ! matcher.find() ) return null;\r
144 \r
145         Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);\r
146 \r
147         return anchor;\r
148     }\r
149 \r
150     /**\r
151      * 与えられた文字列から全アンカーを抽出する。\r
152      * @param source 検索対象文字列\r
153      * @param currentDay 相対日付の基本となる日\r
154      * @return アンカーのリスト(出現順)\r
155      */\r
156     public static List<Anchor> getAnchorList(CharSequence source,\r
157                                                int currentDay      ){\r
158         List<Anchor> result = new LinkedList<Anchor>();\r
159 \r
160         Matcher matcher = ANCHOR_PATTERN.matcher(source);\r
161         int regionEnd = source.length();\r
162 \r
163         while(matcher.find()){\r
164             Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);\r
165             result.add(anchor);\r
166             int regionStart = matcher.end();\r
167             matcher.region(regionStart, regionEnd);\r
168         }\r
169 \r
170         return result;\r
171     }\r
172 \r
173     /**\r
174      * 文字列とそのMatcherからアンカーを抽出する。\r
175      * @param source 検索対象文字列\r
176      * @param matcher Matcher\r
177      * @param currentDay 相対日付の基本となる日\r
178      * @return アンカー\r
179      */\r
180     private static Anchor getAnchorFromMatched(CharSequence source,\r
181                                                   Matcher matcher,\r
182                                                   int currentDay){\r
183         int startPos = matcher.start();\r
184         int endPos   = matcher.end();\r
185 \r
186         /* G国アンカー */\r
187         if(matcher.start(14) < matcher.end(14)){\r
188             int talkNo = StringUtils.parseInt(source, matcher, 14);\r
189             Anchor anchor = new Anchor(source, startPos, endPos, talkNo);\r
190             return anchor;\r
191         }\r
192 \r
193         int day = currentDay;\r
194         if(matcher.start(1) < matcher.end(1)){\r
195             if(matcher.start(2) < matcher.end(2)){ // prologue\r
196                 day = 0;\r
197             }else if(matcher.start(3) < matcher.end(3)){ // epilogue\r
198                 day = EPILOGUEDAY;\r
199             }else if(matcher.start(4) < matcher.end(4)){  // etc) "6d"\r
200                 day = StringUtils.parseInt(source, matcher, 4);\r
201             }else{\r
202                 assert false;\r
203                 return null;\r
204             }\r
205         }\r
206 \r
207         boolean isPM = false;\r
208         if(matcher.start(5) < matcher.end(5)){\r
209             if(matcher.start(6) < matcher.end(6)){        // AM\r
210                 isPM = false;\r
211             }else if(matcher.start(7) < matcher.end(7)){  // PM\r
212                 isPM = true;\r
213             }else{\r
214                 assert false;\r
215                 return null;\r
216             }\r
217         }\r
218 \r
219         int hourGroup;\r
220         int minuteGroup;\r
221         if(matcher.start(8) < matcher.end(8)){   // hhmm hmm hh:mm\r
222             hourGroup = 9;\r
223             minuteGroup = 10;\r
224         }else if(matcher.start(11) < matcher.end(11)){   // h時m分\r
225             hourGroup = 12;\r
226             minuteGroup = 13;\r
227         }else{\r
228             assert false;\r
229             return null;\r
230         }\r
231         int hour   = StringUtils.parseInt(source, matcher, hourGroup);\r
232         int minute = StringUtils.parseInt(source, matcher, minuteGroup);\r
233 \r
234         if(isPM && hour < 12) hour += 12;\r
235         hour %= 24;\r
236         // 午後12:34は午後00:34になる\r
237 \r
238         // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか?\r
239         // とりあえず前者\r
240 \r
241         Anchor anchor = new Anchor(source, startPos, endPos,\r
242                                    day, hour, minute);\r
243 \r
244         return anchor;\r
245     }\r
246 \r
247     /**\r
248      * アンカーの含まれる文字列を返す。\r
249      * @return アンカーの含まれる文字列\r
250      */\r
251     public CharSequence getSource(){\r
252         return this.source;\r
253     }\r
254 \r
255     /**\r
256      * アンカーの開始位置を返す。\r
257      * @return アンカー開始位置\r
258      */\r
259     public int getStartPos(){\r
260         return this.startPos;\r
261     }\r
262 \r
263     /**\r
264      * アンカーの終了位置を返す。\r
265      * @return アンカー終了位置\r
266      */\r
267     public int getEndPos(){\r
268         return this.endPos;\r
269     }\r
270 \r
271     /**\r
272      * アンカーの示す日付を返す。\r
273      * @return 日付\r
274      */\r
275     public int getDay(){\r
276         return this.day;\r
277     }\r
278 \r
279     /**\r
280      * アンカーの示す時刻を返す。\r
281      * @return 時刻(0-23)\r
282      */\r
283     public int getHour(){\r
284         return this.hour;\r
285     }\r
286 \r
287     /**\r
288      * アンカーの示す分を返す。\r
289      * @return 分(0-59)\r
290      */\r
291     public int getMinute(){\r
292         return this.minute;\r
293     }\r
294 \r
295     /**\r
296      * アンカーの示す公開発言番号を返す。\r
297      * @return 公開発言番号。公開発言番号でない場合は0以下の値。\r
298      */\r
299     public int getTalkNo(){\r
300         return this.talkNo;\r
301     }\r
302 \r
303     /**\r
304      * このアンカーが公開発言番号による物か判定する。\r
305      * @return 公開発言番号由来であるならtrue\r
306      */\r
307     public boolean hasTalkNo(){\r
308         return 0 < this.talkNo;\r
309     }\r
310 \r
311     /**\r
312      * 明示的なエピローグへのアンカーか判定する。\r
313      * @return 明示的なエピローグへのアンカーならtrue\r
314      */\r
315     public boolean isEpilogueDay(){\r
316         if(this.day >= EPILOGUEDAY) return true;\r
317         return false;\r
318     }\r
319 \r
320     /**\r
321      * アンカーの文字列表記を返す。\r
322      * 出典:まとめサイトの用語集\r
323      * @return アンカーの文字列表記\r
324      */\r
325     @Override\r
326     public String toString(){\r
327         /* G国表記 */\r
328         if(hasTalkNo()){\r
329             return ">>" + this.talkNo;\r
330         }\r
331 \r
332         StringBuilder result = new StringBuilder();\r
333 \r
334         result.append(getDay()).append('d');\r
335 \r
336         int anchorHour = getHour();\r
337         if(anchorHour < 10) result.append('0');\r
338         result.append(anchorHour).append(':');\r
339 \r
340         int anchorMinute = getMinute();\r
341         if(anchorMinute < 10) result.append('0');\r
342         result.append(anchorMinute);\r
343 \r
344         return result.toString();\r
345     }\r
346 \r
347 }\r