4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sfjp.jindolf.data;
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;
19 public final class Anchor{
21 private static final int EPILOGUEDAY = 99;
22 private static final Pattern ANCHOR_PATTERN;
25 String spchar = "\u0020\u3000\\t";
26 String sp = "[" +spchar+ "]";
27 String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?";
29 String day = // TODO 「昨日」なども含めるか?
35 + "([1-91-9]?[0-90-9])"
36 +sp_n+ "(?:[dDdD]|(?:日目?))"
38 +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n
43 + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))"
44 +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))"
50 +"([0-20-2]?[0-90-9])"
51 +sp_n+ "[:;:;]?" +sp_n
56 +"([0-20-2]?[0-90-9])"
58 +"([0-50-5]?[0-90-9])"
64 "(?:>>([1-9][0-9]{0,8}))";
66 ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum,
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;
82 * @param source アンカーが含まれる文字列
83 * @param startPos アンカーの始まる位置
84 * @param endPos アンカーの終わる位置
85 * @param talkNo 公開発言番号
87 private Anchor(CharSequence source, int startPos, int endPos,
91 if(talkNo <= 0) throw new IllegalArgumentException();
94 this.startPos = startPos;
106 * @param source アンカーが含まれる文字列
107 * @param startPos アンカーの始まる位置
108 * @param endPos アンカーの終わる位置
110 * @param hour 時間(0-23)
111 * @param minute 分(0-59)
113 private Anchor(CharSequence source, int startPos, int endPos,
114 int day, int hour, int minute ){
117 this.source = source;
118 this.startPos = startPos;
119 this.endPos = endPos;
122 this.minute = minute;
130 * 与えられた範囲指定文字列からアンカーを抽出する。
131 * @param source 検索対象文字列
132 * @param regionStart 範囲開始位置
133 * @param regionEnd 範囲終了位置
134 * @param currentDay 相対日付の基本となる日
137 public static Anchor getAnchor(CharSequence source,
141 Matcher matcher = ANCHOR_PATTERN.matcher(source);
142 matcher.region(regionStart, regionEnd);
144 if( ! matcher.find() ) return null;
146 Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);
152 * 与えられた文字列から全アンカーを抽出する。
153 * @param source 検索対象文字列
154 * @param currentDay 相対日付の基本となる日
155 * @return アンカーのリスト(出現順)
157 public static List<Anchor> getAnchorList(CharSequence source,
159 List<Anchor> result = new LinkedList<>();
161 Matcher matcher = ANCHOR_PATTERN.matcher(source);
162 int regionEnd = source.length();
164 while(matcher.find()){
165 Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);
167 int regionStart = matcher.end();
168 matcher.region(regionStart, regionEnd);
175 * 文字列とそのMatcherからアンカーを抽出する。
176 * @param source 検索対象文字列
177 * @param matcher Matcher
178 * @param currentDay 相対日付の基本となる日
181 private static Anchor getAnchorFromMatched(CharSequence source,
184 int startPos = matcher.start();
185 int endPos = matcher.end();
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);
194 int day = currentDay;
195 if(matcher.start(1) < matcher.end(1)){
196 if(matcher.start(2) < matcher.end(2)){ // prologue
198 }else if(matcher.start(3) < matcher.end(3)){ // epilogue
200 }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d"
201 day = StringUtils.parseInt(source, matcher, 4);
208 boolean isPM = false;
209 if(matcher.start(5) < matcher.end(5)){
210 if(matcher.start(6) < matcher.end(6)){ // AM
212 }else if(matcher.start(7) < matcher.end(7)){ // PM
222 if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm
225 }else if(matcher.start(11) < matcher.end(11)){ // h時m分
232 int hour = StringUtils.parseInt(source, matcher, hourGroup);
233 int minute = StringUtils.parseInt(source, matcher, minuteGroup);
235 if(isPM && hour < 12) hour += 12;
237 // 午後12:34は午後00:34になる
239 // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか?
242 Anchor anchor = new Anchor(source, startPos, endPos,
250 * @return アンカーの含まれる文字列
252 public CharSequence getSource(){
260 public int getStartPos(){
261 return this.startPos;
268 public int getEndPos(){
284 public int getHour(){
292 public int getMinute(){
298 * @return 公開発言番号。公開発言番号でない場合は0以下の値。
300 public int getTalkNo(){
305 * このアンカーが公開発言番号による物か判定する。
306 * @return 公開発言番号由来であるならtrue
308 public boolean hasTalkNo(){
309 return 0 < this.talkNo;
313 * 明示的なエピローグへのアンカーか判定する。
314 * @return 明示的なエピローグへのアンカーならtrue
316 public boolean isEpilogueDay(){
317 if(this.day >= EPILOGUEDAY) return true;
327 public String toString(){
330 return ">>" + this.talkNo;
333 StringBuilder result = new StringBuilder();
335 result.append(getDay()).append('d');
337 int anchorHour = getHour();
338 if(anchorHour < 10) result.append('0');
339 result.append(anchorHour).append(':');
341 int anchorMinute = getMinute();
342 if(anchorMinute < 10) result.append('0');
343 result.append(anchorMinute);
345 return result.toString();