4 * License : The MIT License
\r
5 * Copyright(c) 2008 olyutorskii
\r
8 package jp.sourceforge.jindolf;
\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
18 public final class Anchor{
\r
20 private static final int EPILOGUEDAY = 99;
\r
21 private static final Pattern ANCHOR_PATTERN;
\r
24 String spchar = "\u0020\u3000\\t";
\r
25 String sp = "[" +spchar+ "]";
\r
26 String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?";
\r
28 String day = // TODO 「昨日」なども含めるか?
\r
34 + "([1-91-9]?[0-90-9])"
\r
35 +sp_n+ "(?:[dDdD]|(?:日目?))"
\r
37 +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n
\r
42 + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))"
\r
43 +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))"
\r
49 +"([0-20-2]?[0-90-9])"
\r
50 +sp_n+ "[:;:;]?" +sp_n
\r
51 +"([0-50-5][0-90-9])"
\r
55 +"([0-20-2]?[0-90-9])"
\r
57 +"([0-50-5]?[0-90-9])"
\r
63 "(?:>>([1-9][0-9]*))";
\r
65 ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum,
\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
81 * @param source アンカーが含まれる文字列
\r
82 * @param startPos アンカーの始まる位置
\r
83 * @param endPos アンカーの終わる位置
\r
84 * @param talkNo 公開発言番号
\r
86 private Anchor(CharSequence source, int startPos, int endPos,
\r
90 if(talkNo <= 0) throw new IllegalArgumentException();
\r
92 this.source = source;
\r
93 this.startPos = startPos;
\r
94 this.endPos = endPos;
\r
98 this.talkNo = talkNo;
\r
105 * @param source アンカーが含まれる文字列
\r
106 * @param startPos アンカーの始まる位置
\r
107 * @param endPos アンカーの終わる位置
\r
109 * @param hour 時間(0-23)
\r
110 * @param minute 分(0-59)
\r
112 private Anchor(CharSequence source, int startPos, int endPos,
\r
113 int day, int hour, int minute ){
\r
116 this.source = source;
\r
117 this.startPos = startPos;
\r
118 this.endPos = endPos;
\r
121 this.minute = minute;
\r
129 * 与えられた範囲指定文字列からアンカーを抽出する。
\r
130 * @param source 検索対象文字列
\r
131 * @param regionStart 範囲開始位置
\r
132 * @param regionEnd 範囲終了位置
\r
133 * @param currentDay 相対日付の基本となる日
\r
136 public static Anchor getAnchor(CharSequence source,
\r
140 Matcher matcher = ANCHOR_PATTERN.matcher(source);
\r
141 matcher.region(regionStart, regionEnd);
\r
143 if( ! matcher.find() ) return null;
\r
145 Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);
\r
151 * 与えられた文字列から全アンカーを抽出する。
\r
152 * @param source 検索対象文字列
\r
153 * @param currentDay 相対日付の基本となる日
\r
154 * @return アンカーのリスト(出現順)
\r
156 public static List<Anchor> getAnchorList(CharSequence source,
\r
158 List<Anchor> result = new LinkedList<Anchor>();
\r
160 Matcher matcher = ANCHOR_PATTERN.matcher(source);
\r
161 int regionEnd = source.length();
\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
174 * 文字列とそのMatcherからアンカーを抽出する。
\r
175 * @param source 検索対象文字列
\r
176 * @param matcher Matcher
\r
177 * @param currentDay 相対日付の基本となる日
\r
180 private static Anchor getAnchorFromMatched(CharSequence source,
\r
183 int startPos = matcher.start();
\r
184 int endPos = matcher.end();
\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
193 int day = currentDay;
\r
194 if(matcher.start(1) < matcher.end(1)){
\r
195 if(matcher.start(2) < matcher.end(2)){ // prologue
\r
197 }else if(matcher.start(3) < matcher.end(3)){ // epilogue
\r
199 }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d"
\r
200 day = StringUtils.parseInt(source, matcher, 4);
\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
211 }else if(matcher.start(7) < matcher.end(7)){ // PM
\r
221 if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm
\r
224 }else if(matcher.start(11) < matcher.end(11)){ // h時m分
\r
231 int hour = StringUtils.parseInt(source, matcher, hourGroup);
\r
232 int minute = StringUtils.parseInt(source, matcher, minuteGroup);
\r
234 if(isPM && hour < 12) hour += 12;
\r
236 // 午後12:34は午後00:34になる
\r
238 // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか?
\r
241 Anchor anchor = new Anchor(source, startPos, endPos,
\r
242 day, hour, minute);
\r
249 * @return アンカーの含まれる文字列
\r
251 public CharSequence getSource(){
\r
252 return this.source;
\r
259 public int getStartPos(){
\r
260 return this.startPos;
\r
267 public int getEndPos(){
\r
268 return this.endPos;
\r
275 public int getDay(){
\r
283 public int getHour(){
\r
291 public int getMinute(){
\r
292 return this.minute;
\r
296 * アンカーの示す公開発言番号を返す。
\r
297 * @return 公開発言番号。公開発言番号でない場合は0以下の値。
\r
299 public int getTalkNo(){
\r
300 return this.talkNo;
\r
304 * このアンカーが公開発言番号による物か判定する。
\r
305 * @return 公開発言番号由来であるならtrue
\r
307 public boolean hasTalkNo(){
\r
308 return 0 < this.talkNo;
\r
312 * 明示的なエピローグへのアンカーか判定する。
\r
313 * @return 明示的なエピローグへのアンカーならtrue
\r
315 public boolean isEpilogueDay(){
\r
316 if(this.day >= EPILOGUEDAY) return true;
\r
323 * @return アンカーの文字列表記
\r
326 public String toString(){
\r
329 return ">>" + this.talkNo;
\r
332 StringBuilder result = new StringBuilder();
\r
334 result.append(getDay()).append('d');
\r
336 int anchorHour = getHour();
\r
337 if(anchorHour < 10) result.append('0');
\r
338 result.append(anchorHour).append(':');
\r
340 int anchorMinute = getMinute();
\r
341 if(anchorMinute < 10) result.append('0');
\r
342 result.append(anchorMinute);
\r
344 return result.toString();
\r