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;
18 public final class Anchor{
20 private static final int EPILOGUEDAY = 99;
21 private static final Pattern ANCHOR_PATTERN;
24 String spchar = "\u0020\u3000\\t";
25 String sp = "[" +spchar+ "]";
26 String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?";
28 String day = // TODO 「昨日」なども含めるか?
34 + "([1-91-9]?[0-90-9])"
35 +sp_n+ "(?:[dDdD]|(?:日目?))"
37 +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n
42 + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))"
43 +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))"
49 +"([0-20-2]?[0-90-9])"
50 +sp_n+ "[:;:;]?" +sp_n
55 +"([0-20-2]?[0-90-9])"
57 +"([0-50-5]?[0-90-9])"
63 "(?:>>([1-9][0-9]{0,8}))";
65 ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum,
70 private final CharSequence source;
71 private final int startPos;
72 private final int endPos;
73 private final int day;
74 private final int hour;
75 private final int minute;
76 private final int talkNo;
81 * @param source アンカーが含まれる文字列
82 * @param startPos アンカーの始まる位置
83 * @param endPos アンカーの終わる位置
84 * @param talkNo 公開発言番号
86 private Anchor(CharSequence source, int startPos, int endPos,
90 if(talkNo <= 0) throw new IllegalArgumentException();
93 this.startPos = startPos;
105 * @param source アンカーが含まれる文字列
106 * @param startPos アンカーの始まる位置
107 * @param endPos アンカーの終わる位置
109 * @param hour 時間(0-23)
110 * @param minute 分(0-59)
112 private Anchor(CharSequence source, int startPos, int endPos,
113 int day, int hour, int minute ){
116 this.source = source;
117 this.startPos = startPos;
118 this.endPos = endPos;
121 this.minute = minute;
129 * 与えられた範囲指定文字列からアンカーを抽出する。
130 * @param source 検索対象文字列
131 * @param regionStart 範囲開始位置
132 * @param regionEnd 範囲終了位置
133 * @param currentDay 相対日付の基本となる日
136 public static Anchor getAnchor(CharSequence source,
140 Matcher matcher = ANCHOR_PATTERN.matcher(source);
141 matcher.region(regionStart, regionEnd);
143 if( ! matcher.find() ) return null;
145 Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);
151 * 与えられた文字列から全アンカーを抽出する。
152 * @param source 検索対象文字列
153 * @param currentDay 相対日付の基本となる日
154 * @return アンカーのリスト(出現順)
156 public static List<Anchor> getAnchorList(CharSequence source,
158 List<Anchor> result = new LinkedList<>();
160 Matcher matcher = ANCHOR_PATTERN.matcher(source);
161 int regionEnd = source.length();
163 while(matcher.find()){
164 Anchor anchor = getAnchorFromMatched(source, matcher, currentDay);
166 int regionStart = matcher.end();
167 matcher.region(regionStart, regionEnd);
174 * 文字列とそのMatcherからアンカーを抽出する。
175 * @param source 検索対象文字列
176 * @param matcher Matcher
177 * @param currentDay 相対日付の基本となる日
180 private static Anchor getAnchorFromMatched(CharSequence source,
183 int startPos = matcher.start();
184 int endPos = matcher.end();
187 if(matcher.start(14) < matcher.end(14)){
188 int talkNo = parseInt(source, matcher, 14);
189 Anchor anchor = new Anchor(source, startPos, endPos, talkNo);
193 int day = currentDay;
194 if(matcher.start(1) < matcher.end(1)){
195 if(matcher.start(2) < matcher.end(2)){ // prologue
197 }else if(matcher.start(3) < matcher.end(3)){ // epilogue
199 }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d"
200 day = parseInt(source, matcher, 4);
207 boolean isPM = false;
208 if(matcher.start(5) < matcher.end(5)){
209 if(matcher.start(6) < matcher.end(6)){ // AM
211 }else if(matcher.start(7) < matcher.end(7)){ // PM
221 if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm
224 }else if(matcher.start(11) < matcher.end(11)){ // h時m分
231 int hour = parseInt(source, matcher, hourGroup);
232 int minute = parseInt(source, matcher, minuteGroup);
234 if(isPM && hour < 12) hour += 12;
236 // 午後12:34は午後00:34になる
238 // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか?
241 Anchor anchor = new Anchor(source, startPos, endPos,
248 * 正規表現にマッチした領域を数値化する。
251 * @param matcher Matcher
252 * @param groupIndex 前方指定グループ番号
254 * @throws IndexOutOfBoundsException 不正なグループ番号
256 static int parseInt(CharSequence seq,
259 throws IndexOutOfBoundsException {
260 int startPos = matcher.start(groupIndex);
261 int endPos = matcher.end(groupIndex);
262 return parseInt(seq, startPos, endPos);
269 * @param startPos 範囲開始位置
270 * @param endPos 範囲終了位置
272 * @throws IndexOutOfBoundsException 不正な位置指定
274 static int parseInt(CharSequence seq, int startPos, int endPos)
275 throws IndexOutOfBoundsException{
278 for(int pos = startPos; pos < endPos; pos++){
279 char ch = seq.charAt(pos);
280 int digit = Character.digit(ch, 10);
292 * @return アンカーの含まれる文字列
294 public CharSequence getSource(){
302 public int getStartPos(){
303 return this.startPos;
310 public int getEndPos(){
326 public int getHour(){
334 public int getMinute(){
340 * @return 公開発言番号。公開発言番号でない場合は0以下の値。
342 public int getTalkNo(){
347 * このアンカーが公開発言番号による物か判定する。
348 * @return 公開発言番号由来であるならtrue
350 public boolean hasTalkNo(){
351 return 0 < this.talkNo;
355 * 明示的なエピローグへのアンカーか判定する。
356 * @return 明示的なエピローグへのアンカーならtrue
358 public boolean isEpilogueDay(){
359 if(this.day >= EPILOGUEDAY) return true;
369 public String toString(){
372 return ">>" + this.talkNo;
375 StringBuilder result = new StringBuilder();
377 result.append(getDay()).append('d');
379 int anchorHour = getHour();
380 if(anchorHour < 10) result.append('0');
381 result.append(anchorHour).append(':');
383 int anchorMinute = getMinute();
384 if(anchorMinute < 10) result.append('0');
385 result.append(anchorMinute);
387 return result.toString();