OSDN Git Service

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