View Javadoc
1   package com.github.mygreen.cellformatter;
2   
3   import java.util.ArrayList;
4   import java.util.Calendar;
5   import java.util.List;
6   import java.util.regex.Pattern;
7   
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  import com.github.mygreen.cellformatter.lang.ArgUtils;
12  import com.github.mygreen.cellformatter.lang.Utils;
13  import com.github.mygreen.cellformatter.term.AsteriskTerm;
14  import com.github.mygreen.cellformatter.term.DateTerm;
15  import com.github.mygreen.cellformatter.term.EscapedCharTerm;
16  import com.github.mygreen.cellformatter.term.LocaelSymbolTerm;
17  import com.github.mygreen.cellformatter.term.OtherTerm;
18  import com.github.mygreen.cellformatter.term.Term;
19  import com.github.mygreen.cellformatter.term.UnderscoreTerm;
20  import com.github.mygreen.cellformatter.term.WordTerm;
21  import com.github.mygreen.cellformatter.tokenizer.Token;
22  import com.github.mygreen.cellformatter.tokenizer.TokenStore;
23  
24  
25  /**
26   * 書式を解析して{@link ConditionDateFormatter}のインスタンスを作成するクラス。
27   * @author T.TSUCHIE
28   *
29   */
30  public class ConditionDateFormatterFactory extends ConditionFormatterFactory<ConditionDateFormatter> {
31      
32      private static Logger logger = LoggerFactory.getLogger(ConditionDateFormatterFactory.class);
33      
34      /**
35       * 日時の書式かどうかを決定するためのキーワード。
36       */
37      private static final String[] DATE_DECISTION_CHARS = {
38          "yy", "yyyy",
39          "m", "mm", "mmm", "mmmm", "mmmmm",
40          "d", "dd", "ddd", "dddd",
41          "g", "gg", "ggg",
42          /*"e",*/ "ee",  // e単体だと指数と区別がつかないので除外する。
43          "aaa", "aaaa",
44          "r", "rr",
45          "h", "hh",
46          "s", "ss",
47          "am/pm", "a/p",
48          "q", "qq",    // OpenOffice用
49          "nn", "nnn",  // OpenOffice用
50          "ww",         // OpenOffice用
51      };
52      
53      /**
54       * 日時で使用するフォーマット用の文字
55       */
56      private static final String[] DATE_TERM_CHARS = {
57          "yy", "yyyy",
58          "m", "mm", "mmm", "mmmm", "mmmmm",
59          "d", "dd", "ddd", "dddd",
60          "g", "gg", "ggg",
61          "e", "ee",
62          "aaa", "aaaa",
63          "r", "rr",
64          "h", "hh",
65          "s", "ss",
66          "am/pm", "a/p",
67          "q", "qq",  // OpenOffice用
68          "nn",       // OpenOffice用
69          "ww",       // OpenOffice用
70      };
71      
72      /**
73       * {@link #DATE_TERM_CHARS}を、検索用に並び替えたもの。
74       * ・フォーマットのキーワードを①文字列の長い順、辞書順に並び変え、比較していく。
75       */
76      private static final List<String> SORTED_DATE_CHARS = Utils.reverse(DATE_TERM_CHARS);
77      
78      /**
79       * 経過時間の時刻のパターン
80       */
81      private static final Pattern PATTERN_ELAPSED_TIME = Pattern.compile("\\[([h]+|[m]+|[s]+)\\]", Pattern.CASE_INSENSITIVE);
82      
83      /**
84       * 日時の書式かどうか判定する。
85       * @param store
86       * @return
87       */
88      public boolean isDatePattern(final TokenStore store) {
89          
90          if(store.containsInFactor("General")) {
91              return false;
92          }
93          
94          if(store.containsAnyInFactorIgnoreCase(DATE_DECISTION_CHARS)) {
95              return true;
96          }
97          
98          // [h][m][s]の形式のチェック
99          for(Token token : store.getTokens()) {
100             if(!(token instanceof Token.Condition)) {
101                 continue;
102             }
103             
104             final Token.Condition condition = token.asCondition();
105             final String value = condition.getValue();
106             if(PATTERN_ELAPSED_TIME.matcher(value).matches()) {
107                 return true;
108             }
109             
110         }
111         
112         return false;
113     }
114     
115     /**
116      * {@link ConditionDateFormatter}インスタンスを作成する。
117      * @param store
118      * @return
119      * @throws IllegalArgumentException store is null.
120      */
121     @Override
122     public ConditionDateFormatter create(final TokenStore store) {
123         ArgUtils.notNull(store, "store");
124         
125         final ConditionDateFormatterormatter.html#ConditionDateFormatter">ConditionDateFormatter formatter = new ConditionDateFormatter(store.getConcatenatedToken());
126         
127         for(Token token : store.getTokens()) {
128             
129             if(token instanceof Token.Condition) {
130                 // 条件の場合
131                 final Token.Condition conditionToken = token.asCondition();
132                 final String condition = conditionToken.getCondition();
133                 
134                 if(PATTERN_ELAPSED_TIME.matcher(token.getValue()).matches()) {
135                     // [h][m][s]などの経過時刻のパターン
136                     if(Utils.startsWithIgnoreCase(condition, "h")) {
137                         formatter.addTerm(DateTerm.elapsedHour(condition));
138                         
139                     } else if(Utils.startsWithIgnoreCase(condition, "m")) {
140                         formatter.addTerm(DateTerm.elapsedMinute(condition));
141                         
142                     } else if(Utils.startsWithIgnoreCase(condition, "s")) {
143                         formatter.addTerm(DateTerm.elapsedSecond(condition));
144                         
145                     }
146                     continue;
147                 }
148                 
149                 formatter.addCondition(condition);
150                 
151                 if(isConditionOperator(conditionToken)) {
152                     setupConditionOperator(formatter, conditionToken);
153                     
154                 } else if(isConditionLocale(conditionToken)) {
155                     setupConditionLocale(formatter, conditionToken);
156                     
157                 } else if(isConditionLocaleSymbol(conditionToken)) {
158                     final LocaleSymbol localeSymbol = setupConditionLocaleSymbol(formatter, conditionToken);
159                     formatter.addTerm(new LocaelSymbolTerm<Calendar>(localeSymbol));
160                     
161                 } else if(isConditionDbNum(conditionToken)) {
162                     setupConditionDbNum(formatter, conditionToken);
163                     
164                 } else if(isConditionColor(conditionToken)) {
165                     setupConditionColor(formatter, conditionToken);
166                     
167                 }
168                 
169             } else if(token instanceof Token.Word) {
170                 formatter.addTerm(new WordTerm<Calendar>(token.asWord()));
171                 
172             } else if(token instanceof Token.EscapedChar) {
173                 formatter.addTerm(new EscapedCharTerm<Calendar>(token.asEscapedChar()));
174                 
175             } else if(token instanceof Token.Underscore) {
176                 formatter.addTerm(new UnderscoreTerm<Calendar>(token.asUnderscore()));
177                 
178             } else if(token instanceof Token.Asterisk) {
179                 formatter.addTerm(new AsteriskTerm<Calendar>(token.asAsterisk()));
180                 
181             } else if(token instanceof Token.Factor) {
182                 // 因子を日時用の書式に分解する
183                 final List<Token> list = convertFactor(token.asFactor());
184                 for(Token item : list) {
185                     
186                     if(item instanceof Token.Formatter) {
187                         final String formatterItem = item.asFormatter().getValue();
188                         
189                         if(Utils.equalsAnyIgnoreCase(formatterItem, new String[]{"am/pm", "a/p"})) {
190                             formatter.addTerm(DateTerm.amPm(formatterItem));
191                             
192                         } else if(Utils.startsWithIgnoreCase(formatterItem, "w")) {
193                             formatter.addTerm(DateTerm.weekNumber(formatterItem));
194                             
195                         } else if(Utils.startsWithIgnoreCase(formatterItem, "y")) {
196                             formatter.addTerm(DateTerm.year(formatterItem));
197                             
198                         } else if(Utils.startsWithIgnoreCase(formatterItem, "g")) {
199                             formatter.addTerm(DateTerm.eraName(formatterItem));
200                             
201                         } else if(Utils.startsWithIgnoreCase(formatterItem, "e")) {
202                             formatter.addTerm(DateTerm.eraYear(formatterItem));
203                             
204                         } else if(Utils.startsWithIgnoreCase(formatterItem, "r")) {
205                             formatter.addTerm(DateTerm.eraNameYear(formatterItem));
206                             
207                         } else if(Utils.startsWithIgnoreCase(formatterItem, "m")) {
208                             // 月か分かの判定は、全ての書式を組み立て後に行う。
209                             formatter.addTerm(DateTerm.month(formatterItem));
210                             
211                         } else if(Utils.startsWithIgnoreCase(formatterItem, "d")) {
212                             formatter.addTerm(DateTerm.day(formatterItem));
213                             
214                         } else if(Utils.startsWithIgnoreCase(formatterItem, "a")) {
215                             formatter.addTerm(DateTerm.weekName(formatterItem));
216                             
217                         } else if(Utils.startsWithIgnoreCase(formatterItem, "n")) {
218                             formatter.addTerm(DateTerm.weekNameForOO(formatterItem));
219                             
220                         } else if(Utils.startsWithIgnoreCase(formatterItem, "h")) {
221                             final boolean halfHour = store.containsAnyInFactorIgnoreCase(new String[]{"am/pm", "a/p"});
222                             formatter.addTerm(DateTerm.hour(formatterItem, halfHour));
223                             
224                         } else if(Utils.startsWithIgnoreCase(formatterItem, "s")) {
225                             formatter.addTerm(DateTerm.second(formatterItem));
226                             
227                         } else if(Utils.startsWithIgnoreCase(formatterItem, "q")) {
228                             formatter.addTerm(DateTerm.quater(formatterItem));
229                             
230                         } else {
231                             // ここには到達しない
232                             if(logger.isWarnEnabled()) {
233                                 logger.warn("unknown date format terms '{}'.", formatterItem);
234                             }
235                             formatter.addTerm(new OtherTerm<Calendar>(item));
236                         }
237                         
238                     } else {
239                         formatter.addTerm(new OtherTerm<Calendar>(item));
240                     }
241                     
242                 }
243             } else {
244                 formatter.addTerm(new OtherTerm<Calendar>(token));
245             }
246         }
247         
248         // 書式'm'の項を分に変換する処理を行う
249         convertMinuteTerm(formatter);
250         
251         return formatter;
252         
253     }
254     
255     /**
256      * 書式の因子を日時用とそれ以外に変換する。
257      * @param factor
258      * @return
259      */
260     private List<Token> convertFactor(final Token.Factor factor) {
261         
262         final String item = factor.getValue();
263         final int itemLength = item.length();
264         
265         final List<Token> list = new ArrayList<>();
266         
267         int idx = 0;
268         StringBuilder noTermChar = new StringBuilder(); // フォーマット以外の文字列を積む。
269         while(idx < itemLength) {
270             
271             String matchChars = null;
272             for(String chars : SORTED_DATE_CHARS) {
273                 if(Utils.startsWithIgnoreCase(item, chars, idx)) {
274                     matchChars = item.substring(idx, idx + chars.length());
275                     break;
276                 }
277             }
278             
279             if(matchChars == null) {
280                 // フォーマットでない場合は、文字列としてバッファに追加する。
281                 noTermChar.append(item.charAt(idx));
282                 idx++;
283             } else {
284                 if(noTermChar.length() > 0) {
285                     // 今まで積んだバッファを、文字列として分割する。
286                     list.add(Token.factor(noTermChar.toString()));
287                     noTermChar = new StringBuilder();
288                 }
289                 
290                 list.add(Token.formatter(matchChars));
291                 idx += matchChars.length();
292                 
293             }
294         }
295         
296         if(noTermChar.length() > 0) {
297             list.add(Token.factor(noTermChar.toString()));
298         }
299         
300         return list;
301     }
302     
303     /**
304      * 組み立てた項の中で、月の項を分に変換する。
305      * 
306      * @param formatter
307      */
308     private void convertMinuteTerm(final ConditionDateFormatter formatter) {
309         
310         final int termSize = formatter.getTerms().size();
311         for(int i=0; i < termSize; i++) {
312             final Term<Calendar> term = formatter.getTerms().get(i);
313             if(!(term instanceof DateTerm.MonthTerm)) {
314                 continue;
315             }
316             
317             if(isMinuteTerm(i, formatter.getTerms())) {
318                 // '分'の項に入れ替える
319                 final DateTerm.MonthTerm monthTerm = (DateTerm.MonthTerm) term;
320                 formatter.getTerms().set(i, DateTerm.minute(monthTerm.getFormat()));
321             }
322             
323         }
324         
325     }
326     
327     /**
328      * 現在の項が'分'が判定する。
329      * ・現在の直前の項が、'h'(時間)を示すフォーマットがあるかどうか。
330      * ・現在の直後の項が、's'(秒)を示すフォーマットがあるかどうか。
331      * 
332      * @param currentTermIdx
333      * @param terms
334      * @return
335      */
336     private boolean isMinuteTerm(final int currentTermIdx, final List<Term<Calendar>> terms) {
337         
338         final int termSize = terms.size();
339         
340         // 直前の項のチェック
341         if(currentTermIdx -1 > 0) {
342             DateTerm beforeTerm = null;
343             for(int i=currentTermIdx-1; i >= 0; i--) {
344                 final Term<Calendar> term = terms.get(i);
345                 if(term instanceof DateTerm) {
346                     beforeTerm = (DateTerm) term;
347                     break;
348                 }
349                 
350             }
351             
352             if(beforeTerm != null) {
353                 if(beforeTerm instanceof DateTerm.HourTerm || beforeTerm instanceof DateTerm.ElapsedHourTerm) {
354                     return true;
355                 }
356             }
357             
358         }
359         
360         // 直後の項のチェック
361         if(currentTermIdx +1 < termSize) {
362             DateTerm afterTerm = null;
363             for(int i=currentTermIdx+1; i < termSize; i++) {
364                 final Term<Calendar> term = terms.get(i);
365                 if(term instanceof DateTerm) {
366                     afterTerm = (DateTerm) term;
367                     break;
368                 }
369             }
370             
371             if(afterTerm != null) {
372                 if(afterTerm instanceof DateTerm.SecondTerm || afterTerm instanceof DateTerm.ElapsedSecondTerm) {
373                     return true;
374                 }
375             }
376         }
377         
378         return false;
379     }
380     
381 }