View Javadoc
1   package com.github.mygreen.cellformatter;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.List;
6   
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  import com.github.mygreen.cellformatter.lang.ArgUtils;
11  import com.github.mygreen.cellformatter.lang.Utils;
12  import com.github.mygreen.cellformatter.number.FormattedNumber;
13  import com.github.mygreen.cellformatter.number.NumberFactory;
14  import com.github.mygreen.cellformatter.number.NumberPartType;
15  import com.github.mygreen.cellformatter.term.AsteriskTerm;
16  import com.github.mygreen.cellformatter.term.EscapedCharTerm;
17  import com.github.mygreen.cellformatter.term.LocaelSymbolTerm;
18  import com.github.mygreen.cellformatter.term.NumberTerm;
19  import com.github.mygreen.cellformatter.term.OtherTerm;
20  import com.github.mygreen.cellformatter.term.Term;
21  import com.github.mygreen.cellformatter.term.UnderscoreTerm;
22  import com.github.mygreen.cellformatter.term.WordTerm;
23  import com.github.mygreen.cellformatter.tokenizer.Token;
24  import com.github.mygreen.cellformatter.tokenizer.TokenStore;
25  
26  
27  /**
28   * {@link ConditionNumberFormatter}のインスタンスを作成するクラス。
29   *
30   * @version 0.10
31   * @author T.TSUCHIE
32   *
33   */
34  public class ConditionNumberFormatterFactory extends ConditionFormatterFactory<ConditionNumberFormatter> {
35  
36      private static final Logger logger = LoggerFactory.getLogger(ConditionNumberFormatterFactory.class);
37  
38      private static final String[] FORMAT_CHARS = {
39          "#", "0", "?",
40      };
41  
42      public static final String[] SYMBOL_CHARS = {
43          ".", ",",
44          "%", "/",
45      };
46  
47      public static final String[] OTHER_CHARS = {
48          "E+", "E-",
49          "General",
50      };
51  
52      public static final String[] DIGITS_START_CHARS = {
53          "1", "2", "3", "4", "5", "6", "7", "8", "9",
54      };
55  
56      public static final String[] DIGITS_CHARS = {
57          "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
58      };
59  
60      /**
61       * 数値の書式かどうかを決定するためのキーワード。
62       */
63      private static final String[] NUMBER_DECISTION_CHARS = toArray(
64              FORMAT_CHARS,
65              SYMBOL_CHARS
66      );
67  
68      /**
69       * 数値の項としてのキーワード
70       */
71      private static final String[] NUMBER_TERM_CHARS = toArray(
72              FORMAT_CHARS,
73              SYMBOL_CHARS,
74              OTHER_CHARS,
75              DIGITS_START_CHARS
76      );
77  
78      /**
79       * {@link #NUMBER_TERM_CHARS}を、検索用に並び替えたもの。
80       * ・フォーマットのキーワードを①文字列の長い順、辞書順に並び変え、比較していく。
81       */
82      private static final List<String> SORTED_NUMBER_TERM_CHARS = Utils.reverse(NUMBER_TERM_CHARS);
83  
84      /**
85       * 数値の書式かどうか判定する。
86       * @param store
87       * @return
88       */
89      public boolean isNumberPattern(final TokenStore store) {
90          return store.containsAnyInFactor(NUMBER_DECISTION_CHARS);
91      }
92  
93      @Override
94      public ConditionNumberFormatter create(final TokenStore store) {
95          ArgUtils.notNull(store, "store");
96  
97          final ConditionNumberFormatterormatter.html#ConditionNumberFormatter">ConditionNumberFormatter formatter = new ConditionNumberFormatter(store.getConcatenatedToken());
98  
99          for(Token token : store.getTokens()) {
100 
101             if(token instanceof Token.Condition) {
102                 // 条件の場合
103                 final Token.Condition conditionToken = token.asCondition();
104                 final String condition = conditionToken.getCondition();
105                 formatter.addCondition(condition);
106 
107                 if(isConditionOperator(conditionToken)) {
108                     setupConditionOperator(formatter, conditionToken);
109 
110                 } else if(isConditionLocale(conditionToken)) {
111                     setupConditionLocale(formatter, conditionToken);
112 
113                 } else if(isConditionLocaleSymbol(conditionToken)) {
114                     final LocaleSymbol localeSymbol = setupConditionLocaleSymbol(formatter, conditionToken);
115                     formatter.addTerm(new LocaelSymbolTerm<FormattedNumber>(localeSymbol));
116 
117                 } else if(isConditionDbNum(conditionToken)) {
118                     setupConditionDbNum(formatter, conditionToken);
119 
120                 } else if(isConditionColor(conditionToken)) {
121                     setupConditionColor(formatter, conditionToken);
122 
123                 }
124 
125             } else if(token instanceof Token.Word) {
126                 formatter.addTerm(new WordTerm<FormattedNumber>(token.asWord()));
127 
128             } else if(token instanceof Token.EscapedChar) {
129                 formatter.addTerm(new EscapedCharTerm<FormattedNumber>(token.asEscapedChar()));
130 
131             } else if(token instanceof Token.Underscore) {
132                 formatter.addTerm(new UnderscoreTerm<FormattedNumber>(token.asUnderscore()));
133 
134             } else if(token instanceof Token.Asterisk) {
135                 formatter.addTerm(new AsteriskTerm<FormattedNumber>(token.asAsterisk()));
136 
137             } else if(token instanceof Token.Factor) {
138                 // 因子を数値の書式に分解する
139                 final List<Token> list = convertFactor(token.asFactor());
140                 for(Token item : list) {
141 
142                     if(item instanceof Token.Formatter) {
143                         if(item.getValue().equals("#")) {
144                             formatter.addTerm(NumberTerm.sharp());
145 
146                         } else if(item.getValue().equals("0")) {
147                             formatter.addTerm(NumberTerm.zero());
148 
149                         } else if(item.getValue().equals("?")) {
150                             formatter.addTerm(NumberTerm.question());
151 
152                         } else {
153                             logger.warn("unknown formatter : '{}'", item.getValue());
154                         }
155 
156                     } else if(item instanceof Token.Factor) {
157                         if(Utils.startsWithIgnoreCase(item.getValue(), "E")) {
158                             formatter.addTerm(NumberTerm.exponnet(item));
159 
160                         } else if(item.getValue().equals("General")) {
161                             formatter.addTerm(NumberTerm.general());
162 
163                         } else {
164                             formatter.addTerm(new OtherTerm<FormattedNumber>(item));
165 
166                         }
167 
168                     } else if(item instanceof Token.Symbol) {
169                         if(item == Token.SYMBOL_COLON) {
170                             formatter.addTerm(NumberTerm.separator(item.asSymbol()));
171 
172                         } else {
173                             formatter.addTerm(NumberTerm.symbol(item.asSymbol()));
174                         }
175 
176                     } else if(item instanceof Token.Digits) {
177                         formatter.addTerm(NumberTerm.digits(item.asDigits()));
178 
179                     } else {
180                         formatter.addTerm(new OtherTerm<FormattedNumber>(item));
181                     }
182 
183                 }
184 
185             } else {
186                 formatter.addTerm(new OtherTerm<FormattedNumber>(token));
187             }
188         }
189 
190         // 書式に付加情報を設定する
191         setupFormat(formatter);
192 
193         return formatter;
194     }
195 
196     private List<Token> convertFactor(final Token.Factor factor) {
197 
198         final String item = factor.getValue();
199         final int itemLength = item.length();
200 
201         final List<Token> list = new ArrayList<>();
202 
203         int idx = 0;
204         // フォーマット以外の文字列を積む
205         StringBuilder noTermChar = new StringBuilder();
206 
207         while(idx < itemLength) {
208 
209             String matchChars = null;
210             for(String chars : SORTED_NUMBER_TERM_CHARS) {
211                 if(Utils.startsWithIgnoreCase(item, chars, idx)) {
212                     matchChars = item.substring(idx, idx + chars.length());
213                     break;
214                 }
215             }
216 
217             if(matchChars == null) {
218                 // フォーマット出ない場合は、文字列としてバッファに追加する。
219                 noTermChar.append(item.charAt(idx));
220                 idx++;
221 
222             } else {
223                 // 数値の書式の場合
224 
225                 if(noTermChar.length() > 0) {
226                     // 今まで積んだバッファを、文字列として分割する。
227                     list.add(Token.factor(noTermChar.toString()));
228                     noTermChar = new StringBuilder();
229                 }
230 
231                 if(Utils.equalsAny(matchChars, DIGITS_START_CHARS)) {
232                     // 数字として切り出す。
233                     StringBuilder digits = new StringBuilder();
234                     digits.append(matchChars);
235 
236                     for(int i=idx+1; i < itemLength; i++) {
237                         final String str = String.valueOf(item.charAt(i));
238                         if(Utils.equalsAny(str, DIGITS_CHARS)) {
239                             digits.append(str);
240                         } else {
241                             break;
242                         }
243                     }
244 
245                     list.add(Token.digits(digits.toString()));
246                     idx += digits.length();
247 
248                 } else {
249                     if(Utils.equalsAny(matchChars, FORMAT_CHARS)) {
250                         list.add(Token.formatter(matchChars));
251 
252                     } else if(Utils.equalsAny(matchChars, SYMBOL_CHARS)) {
253                         if(matchChars.equals(".")) {
254                             list.add(Token.SYMBOL_DOT);
255 
256                         } else if(matchChars.equals(",")) {
257                             list.add(Token.SYMBOL_COLON);
258 
259                         } else if(matchChars.equals("%")) {
260                             list.add(Token.SYMBOL_PERCENT);
261 
262                         } else if(matchChars.equals("/")) {
263                             list.add(Token.SYMBOL_SLASH);
264 
265                         } else {
266                             logger.warn("unknown symbol : '{}'", matchChars);
267                         }
268 
269                     } else {
270                         list.add(Token.factor(matchChars));
271                     }
272 
273                     idx += matchChars.length();
274 
275                 }
276 
277             }
278 
279         }
280 
281         if(noTermChar.length() > 0) {
282             list.add(Token.factor(noTermChar.toString()));
283         }
284 
285         return list;
286 
287     }
288 
289     /**
290      * 構築した項に対してインデックス番号などを付与する。
291      * @param formatter
292      */
293     private void setupFormat(final ConditionNumberFormatter formatter) {
294 
295         if(formatter.containsSymbolTerm(Token.SYMBOL_SLASH)) {
296             // 分数として処理する
297             setupFormatAsFraction(formatter);
298         } else {
299             // 数値として処理する
300             setupFormatAsDecimal(formatter);
301         }
302 
303     }
304 
305     private void setupFormatAsFraction(final ConditionNumberFormatter formatter) {
306 
307         final int termSize = formatter.getTerms().size();
308 
309         // 分数の区切り記号の位置
310         int slashIndex = -1;
311         for(int i=0; i < termSize; i++) {
312 
313             final Term<FormattedNumber> term = formatter.getTerms().get(i);
314             if(isSymbolTerm(term, Token.SYMBOL_SLASH)) {
315                 slashIndex = i;
316                 break;
317             }
318 
319         }
320 
321         // 分子、帯分数の情報の設定
322         boolean wholeType = false;
323         if(slashIndex > 0) {
324             NumberPartType partType = NumberPartType.Numerator;
325             int countNumeratorTerm = 0;
326             int countWholeNumberTerm = 0;
327             boolean foundFirst = false;
328 
329             for(int i=slashIndex-1; i >= 0; i--) {
330                 final Term<FormattedNumber> term = formatter.getTerms().get(i);
331 
332                 if(term instanceof NumberTerm.FormattedTerm) {
333                     final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
334                     formattedTerm.setPart(partType);
335 
336                     if(partType.equals(NumberPartType.Numerator)) {
337                         countNumeratorTerm++;
338                         formattedTerm.setIndex(countNumeratorTerm);
339 
340                     } else if(partType.equals(NumberPartType.WholeNumber)) {
341                         countWholeNumberTerm++;
342                         formattedTerm.setIndex(countWholeNumberTerm);
343                     }
344 
345                     if(!foundFirst) {
346                         foundFirst = true;
347                     }
348 
349                 } else {
350                     // 連続する書式の間に他の文字が入る場合は、帯分数として処理する。
351                     if(foundFirst) {
352                         partType = NumberPartType.WholeNumber;
353                     }
354                 }
355 
356             }
357 
358             if(countWholeNumberTerm > 0) {
359                 // 帯分数かどうか
360                 wholeType = true;
361             }
362         }
363 
364         // 分母の処理
365         boolean exactDenom=false;
366         int denominator = -1;
367         if(slashIndex < termSize) {
368             int countDenominatorTerm = 0;
369             int exactDenominator = -1;
370             for(int i=termSize-1; i > slashIndex; i--) {
371 
372                 final Term<FormattedNumber> term = formatter.getTerms().get(i);
373                 if(term instanceof NumberTerm.FormattedTerm) {
374                     final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
375                     formattedTerm.setPart(NumberPartType.Denominator);
376 
377                     countDenominatorTerm++;
378                     formattedTerm.setIndex(countDenominatorTerm);
379 
380                 } else if(term instanceof NumberTerm.DigitsTerm) {
381                     final NumberTerm.DigitsTerm digitsTerm = (NumberTerm.DigitsTerm) term;
382                     exactDenominator = digitsTerm.getToken().intValue();
383 
384                 }
385             }
386 
387             if(exactDenominator > 0) {
388                 denominator = exactDenominator;
389                 exactDenom = true;
390 
391             } else if(countDenominatorTerm > 0) {
392                 if(countDenominatorTerm >= 5) {
393                     // 5桁以上は対応していない
394                     denominator = (int) Math.pow(10, 5);
395                 } else {
396                     denominator = (int) Math.pow(10, countDenominatorTerm);
397                 }
398             } else {
399                 denominator = 10;
400             }
401         }
402 
403         // 最後の項かどうかのフラグを設定する
404         boolean foundFistWholeNumber = false;
405         boolean foundFistNumerator = false;
406         boolean foundFistDenominator = false;
407         for(Term<FormattedNumber> term : formatter.getTerms()) {
408 
409             if(term instanceof NumberTerm.FormattedTerm) {
410                 final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
411 
412                 if(formattedTerm.getPartType().equals(NumberPartType.WholeNumber) && !foundFistWholeNumber) {
413                     formattedTerm.setLastPart(true);
414                     foundFistWholeNumber = true;
415 
416                 } else if(formattedTerm.getPartType().equals(NumberPartType.Numerator) && !foundFistNumerator) {
417                     formattedTerm.setLastPart(true);
418                     foundFistNumerator = true;
419 
420                 } else if(formattedTerm.getPartType().equals(NumberPartType.Denominator) && !foundFistDenominator) {
421                     formattedTerm.setLastPart(true);
422                     foundFistDenominator = true;
423                 }
424 
425             }
426         }
427 
428         formatter.setNumberFactory(NumberFactory.fractionNumber(denominator, exactDenom, wholeType));
429 
430     }
431 
432     private void setupFormatAsDecimal(final ConditionNumberFormatter formatter) {
433 
434         NumberPartType partType = NumberPartType.Integer;
435         boolean foundFirst = false;
436         int countDecimalTerm = 0;   // 小数部分の書式のカウント
437         boolean foundPercentTerm = false;
438 
439         for(Term<FormattedNumber> term : formatter.getTerms()) {
440 
441             if(isSymbolTerm(term, Token.SYMBOL_PERCENT)) {
442                 foundPercentTerm = true;
443 
444             } else if(isSymbolTerm(term, Token.SYMBOL_DOT)) {
445                 partType = NumberPartType.Decimal;
446                 foundFirst = false;
447 
448             } else if(term instanceof NumberTerm.ExponentTerm) {
449                 partType = NumberPartType.Exponent;
450                 foundFirst = false;
451 
452             } else if(term instanceof NumberTerm.FormattedTerm) {
453                 final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
454                 formattedTerm.setPart(partType);
455 
456                 if(partType.equals(NumberPartType.Decimal)) {
457                     // 小数部分の場合のインデックス番号を振る
458                     countDecimalTerm++;
459                     formattedTerm.setIndex(countDecimalTerm);
460 
461                 } else if(!foundFirst) {
462                     // 整数部分、指数部分の先頭の書式、最後の桁となる
463                     formattedTerm.setLastPart(true);
464                     foundFirst = true;
465                 }
466 
467             }
468         }
469 
470         // 最後から探索し、インデックス番号を振る
471         foundFirst = false;
472         int countIntegerTerm = 0;
473         int countExponentTerm = 0;
474 
475         final int termSize = formatter.getTerms().size();
476         for(int i=0; i < termSize; i++) {
477             final Term<FormattedNumber> term = formatter.getTerms().get(termSize-i-1);
478 
479             if(term instanceof NumberTerm.FormattedTerm) {
480 
481                 final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
482 
483                 if(formattedTerm.getPartType().equals(NumberPartType.Decimal)) {
484                     // 小数部分の場合、はじめに見つかったものが、最後の桁
485                     if(!foundFirst) {
486                         formattedTerm.setLastPart(true);
487                         foundFirst = true;
488                     }
489 
490                 } else if(formattedTerm.getPartType().equals(NumberPartType.Integer)) {
491                     countIntegerTerm++;
492                     formattedTerm.setIndex(countIntegerTerm);
493 
494                 } else if(formattedTerm.getPartType().equals(NumberPartType.Exponent)) {
495                     countExponentTerm++;
496                     formattedTerm.setIndex(countExponentTerm);
497                 }
498 
499             }
500 
501         }
502 
503         // 桁区切りの判定処理
504         boolean useSeparator = false;
505         boolean inIntegerPater = false;
506         for(Term<FormattedNumber> term : formatter.getTerms()) {
507             if(term instanceof NumberTerm.FormattedTerm) {
508                 final NumberTerm.FormattedTerm formattedTerm = (NumberTerm.FormattedTerm) term;
509                 // 整数の書式でかつ途中の書式の場合
510                 if(formattedTerm.getPartType().equals(NumberPartType.Integer)) {
511                     if(formattedTerm.isLastPart() && formattedTerm.getIndex() > 1) {
512                         inIntegerPater = true;
513                     } else if(formattedTerm.getIndex() <= 1) {
514                         inIntegerPater = false;
515                     }
516                 }
517             }
518 
519             if(term instanceof NumberTerm.SeparatorTerm) {
520                 if(inIntegerPater) {
521                     useSeparator = true;
522                 }
523 
524             }
525         }
526 
527         // 最後の書式の直後の区切り文字の判定
528         int indexLastFormatterdTerm = -1;
529         for(int i=0; i < termSize; i++) {
530             final Term<FormattedNumber> term = formatter.getTerms().get(termSize-i-1);
531             if(term instanceof NumberTerm.FormattedTerm) {
532                 indexLastFormatterdTerm = termSize-i-1;
533                 break;
534             }
535         }
536 
537         int countLastColon = 0;
538         if(indexLastFormatterdTerm >= 0 && indexLastFormatterdTerm+1 < termSize) {
539             // 最後の書式の直後の連続するカンマの個数をカウントする
540             for(int i=indexLastFormatterdTerm+1; i < termSize; i++) {
541                 final Term<FormattedNumber> term = formatter.getTerms().get(i);
542                 if(term instanceof NumberTerm.SeparatorTerm) {
543                     countLastColon++;
544                 } else {
545                     break;
546                 }
547             }
548         }
549 
550         if(countExponentTerm > 0) {
551             // 指数の場合
552             formatter.setNumberFactory(NumberFactory.exponentNumber(countDecimalTerm, useSeparator));
553 
554         } else if(foundPercentTerm) {
555             // 百分率の場合
556             formatter.setNumberFactory(NumberFactory.percentNumber(countDecimalTerm, useSeparator, countLastColon));
557 
558         } else {
559             // DBNnum指定でかつ、Generalの書式か判定する
560             boolean useDBNum = false;
561             for(String condition : formatter.getConditions()) {
562                 if(condition.startsWith("DBNum")) {
563                     useDBNum = true;
564                     break;
565                 }
566             }
567 
568             boolean generalFormat = false;
569             for(Term<FormattedNumber> term : formatter.getTerms()) {
570                 if(term instanceof NumberTerm.GeneralTerm) {
571                     generalFormat = true;
572                     break;
573                 }
574             }
575 
576             if(useDBNum && generalFormat) {
577                 // 漢数字かつ標準の数値の場合は、Nativeumberとして処理する。
578                 formatter.setNumberFactory(NumberFactory.nativeNumber());
579             } else {
580                 formatter.setNumberFactory(NumberFactory.decimalNumber(countDecimalTerm, useSeparator, countLastColon));
581             }
582 
583         }
584 
585     }
586 
587     private static boolean isSymbolTerm(final Term<FormattedNumber> term, final Token.Symbol symbol) {
588 
589         if(!(term instanceof NumberTerm.SymbolTerm)) {
590             return false;
591         }
592 
593         final NumberTerm.SymbolTerm symbolTerm = (NumberTerm.SymbolTerm) term;
594         return symbolTerm.getToken().equals(symbol);
595 
596     }
597 
598     private static String[] toArray(final String[]... arrays) {
599         List<String> list = new ArrayList<>();
600         for(String[] array : arrays) {
601             list.addAll(Arrays.asList(array));
602         }
603 
604         return list.toArray(new String[list.size()]);
605     }
606 
607 }