OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library number_format; | 5 part of intl; |
6 | 6 /** |
7 import 'dart:math'; | 7 * Provides the ability to format a number in a locale-specific way. The |
8 | 8 * format is specified as a pattern using a subset of the ICU formatting |
9 import "intl.dart"; | 9 * patterns. |
10 import "number_symbols.dart"; | 10 * |
11 import "number_symbols_data.dart"; | 11 * 0 - A single digit |
12 | 12 * # - A single digit, omitted if the value is zero |
13 * . - Decimal separator | |
14 * - - Minus sign | |
15 * , - Grouping separator | |
16 * E - Separates mantissa and expontent | |
17 * + - Before an exponent, indicates it should be prefixed with a plus sign. | |
18 * % - In prefix or suffix, multiply by 100 and show as percentage | |
19 * \u2030 - In prefix or suffix, multiply by 1000 and show as per mille | |
20 * \u00A4 - Currency sign, replaced by currency name | |
21 * ' - Used to quote special characters | |
22 * ; - Used to separate the positive and negative patterns if both are present | |
23 * | |
24 * For example, | |
25 * var f = new NumberFormat("###.0#", "en_US"); | |
26 * print(f.format(12.345)); | |
27 * ==> 12.34 | |
28 * If the locale is not specified, it will default to the current locale. If | |
29 * the format is not specified it will print in a basic format with at least | |
30 * one integer digit and three fraction digits. | |
31 * | |
32 * There are also standard patterns available via the special constructors. e.g. | |
33 * var symbols = new NumberFormat.PERCENT_FORMAT("ar"); | |
34 * There are four such constructors: DECIMAL_FORMAT, PERCENT_FORMAT, | |
35 * SCIENTIFIC_FORMAT and CURRENCY_FORMAT. However, at the moment, | |
36 * SCIENTIFIC_FORMAT prints only as equivalent to "#E0" and does not take | |
37 * into account significant digits. CURRENCY_FORMAT will always use the name | |
38 * of the currency rather than the symbol. | |
39 */ | |
13 class NumberFormat { | 40 class NumberFormat { |
14 /** Variables to determine how number printing behaves. */ | 41 /** Variables to determine how number printing behaves. */ |
15 // TODO(alanknight): If these remain as variables and are set based on the | 42 // TODO(alanknight): If these remain as variables and are set based on the |
16 // pattern, can we make them final? | 43 // pattern, can we make them final? |
17 String _negativePrefix = '-'; | 44 String _negativePrefix = '-'; |
18 String _positivePrefix = ''; | 45 String _positivePrefix = ''; |
19 String _negativeSuffix = ''; | 46 String _negativeSuffix = ''; |
20 String _positiveSuffix = ''; | 47 String _positiveSuffix = ''; |
21 /** How many numbers in a group when using punctuation to group digits in | 48 /** |
49 * How many numbers in a group when using punctuation to group digits in | |
22 * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits | 50 * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits |
23 * between commas. | 51 * between commas. |
24 */ | 52 */ |
25 int _groupingSize = 3; | 53 int _groupingSize = 3; |
26 bool _decimalSeparatorAlwaysShown = false; | 54 bool _decimalSeparatorAlwaysShown = false; |
55 bool _useSignForPositiveExponent = false; | |
27 bool _useExponentialNotation = false; | 56 bool _useExponentialNotation = false; |
57 | |
28 int _maximumIntegerDigits = 40; | 58 int _maximumIntegerDigits = 40; |
29 int _minimumIntegerDigits = 1; | 59 int _minimumIntegerDigits = 1; |
30 int _maximumFractionDigits = 3; // invariant, >= minFractionDigits | 60 int _maximumFractionDigits = 3; |
31 int _minimumFractionDigits = 0; | 61 int _minimumFractionDigits = 0; |
32 int _minimumExponentDigits = 0; | 62 int _minimumExponentDigits = 0; |
33 bool _useSignForPositiveExponent = false; | 63 |
64 int _multiplier = 1; | |
65 | |
66 /** | |
67 * Stores the pattern used to create this format. This isn't used, but | |
68 * is helpful in debugging. | |
69 */ | |
70 String _pattern; | |
71 /** | |
72 * Set the maximum digits printed to the left of the decimal point. | |
73 * Normally this is computed from the pattern, but it's exposed here for | |
74 * testing purposes and for rare cases where you want to force it explicitly. | |
75 */ | |
76 void setMaximumIntegerDigits(int max) { | |
77 _maximumIntegerDigits = max; | |
78 } | |
79 | |
80 /** | |
81 * Set the minimum number of digits printed to the left of the decimal point. | |
82 * Normally this is computed from the pattern, but it's exposed here for | |
83 * testing purposes and for rare cases where you want to force it explicitly. | |
84 */ | |
85 void setMinimumIntegerDigits(int min) { | |
86 _minimumIntegerDigits = min; | |
87 } | |
88 | |
89 /** | |
90 * Set the maximum number of digits printed to the right of the decimal point. | |
91 * Normally this is computed from the pattern, but it's exposed here for | |
92 * testing purposes and for rare cases where you want to force it explicitly. | |
93 */ | |
94 void setMaximumFractionDigits(int max) { | |
95 _maximumFractionDigits = max; | |
96 } | |
97 | |
98 /** | |
99 * Set the minimum digits printed to the left of the decimal point. | |
100 * Normally this is computed from the pattern, but it's exposed here for | |
101 * testing purposes and for rare cases where you want to force it explicitly. | |
102 */ | |
103 void setMinimumFractionDigits(int max) { | |
104 _minimumFractionDigits = max; | |
105 } | |
34 | 106 |
35 /** The locale in which we print numbers. */ | 107 /** The locale in which we print numbers. */ |
36 final String _locale; | 108 final String _locale; |
37 | 109 |
38 /** Caches the symbols used for our locale. */ | 110 /** Caches the symbols used for our locale. */ |
39 NumberSymbols _symbols; | 111 NumberSymbols _symbols; |
40 | 112 |
41 /** | 113 /** |
42 * Transient internal state in which to build up the result of the format | 114 * Transient internal state in which to build up the result of the format |
43 * operation. We can have this be just an instance variable because Dart is | 115 * operation. We can have this be just an instance variable because Dart is |
44 * single-threaded and unless we do an asynchronous operation in the process | 116 * single-threaded and unless we do an asynchronous operation in the process |
45 * of formatting then there will only ever be one number being formatted | 117 * of formatting then there will only ever be one number being formatted |
46 * at a time. In languages with threads we'd need to pass this on the stack. | 118 * at a time. In languages with threads we'd need to pass this on the stack. |
47 */ | 119 */ |
48 StringBuffer _buffer; | 120 StringBuffer _buffer; |
49 | 121 |
50 /** | 122 /** |
51 * Create a number format that prints in [newPattern] as it applies in | 123 * Create a number format that prints using [newPattern] as it applies in |
52 * [locale]. | 124 * [locale]. |
53 */ | 125 */ |
54 NumberFormat([String newPattern, String locale]): | 126 factory NumberFormat([String newPattern, String locale]) { |
55 _locale = Intl.verifiedLocale(locale, localeExists) { | 127 return new NumberFormat._forPattern(locale, (x) => newPattern); |
56 // TODO(alanknight): There will need to be some kind of async setup | 128 } |
57 // operations so as not to bring along every locale in every program. | 129 |
130 /** Create a number format that prints as DECIMAL_PATTERN. */ | |
131 NumberFormat.DECIMAL_PATTERN([String locale]) : | |
Emily Fortuna
2013/04/09 19:39:15
personal preference: I'd prefer the name of these
| |
132 this._forPattern(locale, (x) => x.DECIMAL_PATTERN); | |
133 | |
134 /** Create a number format that prints as PERCENT_PATTERN. */ | |
135 NumberFormat.PERCENT_PATTERN([String locale]) : | |
136 this._forPattern(locale, (x) => x.PERCENT_PATTERN); | |
137 | |
138 /** Create a number format that prints as SCIENTIFIC_PATTERN. */ | |
139 NumberFormat.SCIENTIFIC_PATTERN([String locale]) : | |
140 this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); | |
141 | |
142 /** Create a number format that prints as CURRENCY_PATTERN. */ | |
143 NumberFormat.CURRENCY_PATTERN([String locale]) : | |
144 this._forPattern(locale, (x) => x.CURRENCY_PATTERN); | |
145 | |
146 /** | |
147 * Create a number format that prints in a pattern we get from | |
148 * the [getPattern] function using the locale [locale]. | |
149 */ | |
150 NumberFormat._forPattern(String locale, Function getPattern) : | |
151 _locale = Intl.verifiedLocale(locale, localeExists) { | |
58 _symbols = numberFormatSymbols[_locale]; | 152 _symbols = numberFormatSymbols[_locale]; |
59 _setPattern(newPattern); | 153 _setPattern(getPattern(_symbols)); |
60 } | 154 } |
61 | 155 |
62 /** | 156 /** |
63 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. | 157 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. |
64 */ | 158 */ |
65 String get locale => _locale; | 159 String get locale => _locale; |
66 | 160 |
67 /** | 161 /** |
68 * Return true if the locale exists, or if it is null. The null case | 162 * Return true if the locale exists, or if it is null. The null case |
69 * is interpreted to mean that we use the default locale. | 163 * is interpreted to mean that we use the default locale. |
70 */ | 164 */ |
71 static bool localeExists(localeName) { | 165 static bool localeExists(localeName) { |
72 if (localeName == null) return false; | 166 if (localeName == null) return false; |
73 return numberFormatSymbols.containsKey(localeName); | 167 return numberFormatSymbols.containsKey(localeName); |
74 } | 168 } |
75 | 169 |
76 /** | 170 /** |
77 * Return the symbols which are used in our locale. Cache them to avoid | 171 * Return the symbols which are used in our locale. Cache them to avoid |
78 * repeated lookup. | 172 * repeated lookup. |
79 */ | 173 */ |
80 NumberSymbols get symbols { | 174 NumberSymbols get symbols { |
81 return _symbols; | 175 return _symbols; |
82 } | 176 } |
83 | 177 |
84 // TODO(alanknight): Actually use the pattern and locale. | |
85 _setPattern(String x) {} | |
86 | |
87 /** | 178 /** |
88 * Format [number] according to our pattern and return the formatted string. | 179 * Format [number] according to our pattern and return the formatted string. |
89 */ | 180 */ |
90 String format(num number) { | 181 String format(num number) { |
91 // TODO(alanknight): Do we have to do anything for printing numbers bidi? | 182 // TODO(alanknight): Do we have to do anything for printing numbers bidi? |
92 // Or are they always printed left to right? | 183 // Or are they always printed left to right? |
93 if (number.isNaN) return symbols.NAN; | 184 if (number.isNaN) return symbols.NAN; |
94 if (number.isInfinite) return "${_signPrefix(number)}${symbols.INFINITY}"; | 185 if (number.isInfinite) return "${_signPrefix(number)}${symbols.INFINITY}"; |
95 | 186 |
96 _newBuffer(); | 187 _newBuffer(); |
97 _add(_signPrefix(number)); | 188 _add(_signPrefix(number)); |
98 _formatNumber(number.abs()); | 189 _formatNumber(number.abs() * _multiplier); |
99 _add(_signSuffix(number)); | 190 _add(_signSuffix(number)); |
100 | 191 |
101 var result = _buffer.toString(); | 192 var result = _buffer.toString(); |
102 _buffer = null; | 193 _buffer = null; |
103 return result; | 194 return result; |
104 } | 195 } |
105 | 196 |
106 /** | 197 /** |
107 * Format the main part of the number in the form dictated by the pattern. | 198 * Format the main part of the number in the form dictated by the pattern. |
108 */ | 199 */ |
109 void _formatNumber(num number) { | 200 void _formatNumber(num number) { |
110 if (_useExponentialNotation) { | 201 if (_useExponentialNotation) { |
111 _formatExponential(number); | 202 _formatExponential(number); |
112 } else { | 203 } else { |
113 _formatFixed(number); | 204 _formatFixed(number); |
114 } | 205 } |
115 } | 206 } |
116 | 207 |
117 /** Format the number in exponential notation. */ | 208 /** Format the number in exponential notation. */ |
118 _formatExponential(num number) { | 209 void _formatExponential(num number) { |
119 if (number == 0.0) { | 210 if (number == 0.0) { |
120 _formatFixed(number); | 211 _formatFixed(number); |
121 _formatExponent(0); | 212 _formatExponent(0); |
122 return; | 213 return; |
123 } | 214 } |
124 | 215 |
125 var exponent = (log(number) / log(10)).floor(); | 216 var exponent = (log(number) / log(10)).floor(); |
126 var mantissa = number / pow(10, exponent); | 217 var mantissa = number / pow(10.0, exponent); |
127 | 218 |
128 if (_minimumIntegerDigits < 1) { | 219 var minIntDigits = _minimumIntegerDigits; |
129 exponent++; | 220 if (_maximumIntegerDigits > 1 && |
130 mantissa /= 10; | 221 _maximumIntegerDigits > _minimumIntegerDigits) { |
222 // A repeating range is defined; adjust to it as follows. | |
223 // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; | |
224 // -3,-4,-5=>-6, etc. This takes into account that the | |
225 // exponent we have here is off by one from what we expect; | |
226 // it is for the format 0.MMMMMx10^n. | |
227 while ((exponent % _maximumIntegerDigits) != 0) { | |
228 mantissa *= 10; | |
229 exponent--; | |
230 } | |
231 minIntDigits = 1; | |
131 } else { | 232 } else { |
132 exponent -= _minimumIntegerDigits - 1; | 233 // No repeating range is defined, use minimum integer digits. |
133 mantissa *= pow(10, _minimumIntegerDigits - 1); | 234 if (_minimumIntegerDigits < 1) { |
235 exponent++; | |
236 mantissa /= 10; | |
237 } else { | |
238 exponent -= _minimumIntegerDigits - 1; | |
239 mantissa *= pow(10, _minimumIntegerDigits - 1); | |
240 } | |
134 } | 241 } |
135 _formatFixed(number); | 242 _formatFixed(mantissa); |
136 _formatExponent(exponent); | 243 _formatExponent(exponent); |
137 } | 244 } |
138 | 245 |
139 /** | 246 /** |
140 * Format the exponent portion, e.g. in "1.3e-5" the "e-5". | 247 * Format the exponent portion, e.g. in "1.3e-5" the "e-5". |
141 */ | 248 */ |
142 void _formatExponent(num exponent) { | 249 void _formatExponent(num exponent) { |
143 _add(symbols.EXP_SYMBOL); | 250 _add(symbols.EXP_SYMBOL); |
144 if (exponent < 0) { | 251 if (exponent < 0) { |
145 exponent = -exponent; | 252 exponent = -exponent; |
146 _add(symbols.MINUS_SIGN); | 253 _add(symbols.MINUS_SIGN); |
147 } else if (_useSignForPositiveExponent) { | 254 } else if (_useSignForPositiveExponent) { |
148 _add(symbols.PLUS_SIGN); | 255 _add(symbols.PLUS_SIGN); |
149 } | 256 } |
150 _pad(_minimumExponentDigits, exponent.toString()); | 257 _pad(_minimumExponentDigits, exponent.toString()); |
151 } | 258 } |
152 | 259 |
260 /** Used to test if we have exceeded Javascript integer limits. */ | |
261 final _maxInt = pow(2, 52); | |
262 | |
153 /** | 263 /** |
154 * Format the basic number portion, inluding the fractional digits. | 264 * Format the basic number portion, inluding the fractional digits. |
155 */ | 265 */ |
156 void _formatFixed(num number) { | 266 void _formatFixed(num number) { |
157 // Round the number. | 267 // Very fussy math to get integer and fractional parts. |
158 var power = pow(10, _maximumFractionDigits); | 268 var power = pow(10, _maximumFractionDigits); |
159 var intValue = number.truncate(); | 269 var shiftedNumber = (number * power); |
160 var multiplied = (number * power).round(); | 270 // We must not roundToDouble() an int or it will lose precision. We must not |
161 var fracValue = (multiplied - intValue * power).floor(); | 271 // round() a large double or it will take its loss of precision and |
272 // preserve it in an int, which we will then print to the right | |
273 // of the decimal place. Therefore, only roundToDouble if we are already | |
274 // a double. | |
275 if (shiftedNumber is double) { | |
276 shiftedNumber = shiftedNumber.roundToDouble(); | |
277 } | |
278 var intValue, fracValue; | |
279 if (shiftedNumber.isInfinite) { | |
280 intValue = number.toInt(); | |
281 fracValue = 0; | |
282 } else { | |
283 intValue = shiftedNumber.round() ~/ power; | |
284 fracValue = (shiftedNumber - intValue * power).floor(); | |
285 } | |
162 var fractionPresent = _minimumFractionDigits > 0 || fracValue > 0; | 286 var fractionPresent = _minimumFractionDigits > 0 || fracValue > 0; |
163 | 287 |
164 // On dartj2s the integer part may be large enough to be a floating | 288 // If the int part is larger than 2^52 and we're on Javascript (so it's |
165 // point value, in which case we reduce it until it is small enough | 289 // really a float) it will lose precision, so pad out the rest of it |
166 // to be printed as an integer and pad the remainder with zeros. | 290 // with zeros. Check for Javascript by seeing if an integer is double. |
167 var paddingDigits = new StringBuffer(); | 291 var paddingDigits = new StringBuffer(); |
168 while ((intValue & 0x7fffffff) != intValue) { | 292 if (1 is double && intValue > _maxInt) { |
169 paddingDigits.write(symbols.ZERO_DIGIT); | 293 var howManyDigitsTooBig = (log(intValue) / LN10).ceil() - 16; |
170 intValue = intValue ~/ 10; | 294 var divisor = pow(10, howManyDigitsTooBig).round(); |
295 for (var each in new List(howManyDigitsTooBig.toInt())) { | |
296 paddingDigits.write(symbols.ZERO_DIGIT); | |
297 } | |
298 intValue = (intValue / divisor).truncate(); | |
171 } | 299 } |
172 var integerDigits = "${intValue}${paddingDigits}".codeUnits; | 300 var integerDigits = "${intValue}${paddingDigits}".codeUnits; |
173 var digitLength = integerDigits.length; | 301 var digitLength = integerDigits.length; |
174 | 302 |
175 if (_hasPrintableIntegerPart(intValue)) { | 303 if (_hasPrintableIntegerPart(intValue)) { |
176 _pad(_minimumIntegerDigits - digitLength); | 304 _pad(_minimumIntegerDigits - digitLength); |
177 for (var i = 0; i < digitLength; i++) { | 305 for (var i = 0; i < digitLength; i++) { |
178 _addDigit(integerDigits[i]); | 306 _addDigit(integerDigits[i]); |
179 _group(digitLength, i); | 307 _group(digitLength, i); |
180 } | 308 } |
181 } else if (!fractionPresent) { | 309 } else if (!fractionPresent) { |
182 // If neither fraction nor integer part exists, just print zero. | 310 // If neither fraction nor integer part exists, just print zero. |
183 _addZero(); | 311 _addZero(); |
184 } | 312 } |
185 | 313 |
186 _decimalSeparator(fractionPresent); | 314 _decimalSeparator(fractionPresent); |
187 _formatFractionPart((fracValue + power).toString()); | 315 _formatFractionPart((fracValue + power).toString()); |
188 } | 316 } |
189 | 317 |
190 /** | 318 /** |
191 * Format the part after the decimal place in a fixed point number. | 319 * Format the part after the decimal place in a fixed point number. |
192 */ | 320 */ |
193 void _formatFractionPart(String fractionPart) { | 321 void _formatFractionPart(String fractionPart) { |
194 var fractionCodes = fractionPart.codeUnits; | 322 var fractionCodes = fractionPart.codeUnits; |
195 var fractionLength = fractionPart.length; | 323 var fractionLength = fractionPart.length; |
196 while (fractionPart[fractionLength - 1] == '0' && | 324 while(fractionCodes[fractionLength - 1] == _zero && |
197 fractionLength > _minimumFractionDigits + 1) { | 325 fractionLength > _minimumFractionDigits + 1) { |
198 fractionLength--; | 326 fractionLength--; |
199 } | 327 } |
200 for (var i = 1; i < fractionLength; i++) { | 328 for (var i = 1; i < fractionLength; i++) { |
201 _addDigit(fractionCodes[i]); | 329 _addDigit(fractionCodes[i]); |
202 } | 330 } |
203 } | 331 } |
204 | 332 |
205 /** Print the decimal separator if appropriate. */ | 333 /** Print the decimal separator if appropriate. */ |
206 void _decimalSeparator(bool fractionPresent) { | 334 void _decimalSeparator(bool fractionPresent) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
251 */ | 379 */ |
252 void _group(int totalLength, int position) { | 380 void _group(int totalLength, int position) { |
253 var distanceFromEnd = totalLength - position; | 381 var distanceFromEnd = totalLength - position; |
254 if (distanceFromEnd <= 1 || _groupingSize <= 0) return; | 382 if (distanceFromEnd <= 1 || _groupingSize <= 0) return; |
255 if (distanceFromEnd % _groupingSize == 1) { | 383 if (distanceFromEnd % _groupingSize == 1) { |
256 _add(symbols.GROUP_SEP); | 384 _add(symbols.GROUP_SEP); |
257 } | 385 } |
258 } | 386 } |
259 | 387 |
260 /** Returns the code point for the character '0'. */ | 388 /** Returns the code point for the character '0'. */ |
261 int get _zero => '0'.codeUnits.first; | 389 final _zero = '0'.codeUnits.first; |
262 | 390 |
263 /** Returns the code point for the locale's zero digit. */ | 391 /** Returns the code point for the locale's zero digit. */ |
264 // Note that there is a slight risk of a locale's zero digit not fitting | 392 // Note that there is a slight risk of a locale's zero digit not fitting |
265 // into a single code unit, but it seems very unlikely, and if it did, | 393 // into a single code unit, but it seems very unlikely, and if it did, |
266 // there's a pretty good chance that our assumptions about being able to do | 394 // there's a pretty good chance that our assumptions about being able to do |
267 // arithmetic on it would also be invalid. | 395 // arithmetic on it would also be invalid. |
268 int get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; | 396 get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; |
269 | 397 |
270 /** | 398 /** |
271 * Returns the prefix for [x] based on whether it's positive or negative. | 399 * Returns the prefix for [x] based on whether it's positive or negative. |
272 * In en_US this would be '' and '-' respectively. | 400 * In en_US this would be '' and '-' respectively. |
273 */ | 401 */ |
274 String _signPrefix(num x) { | 402 String _signPrefix(num x) { |
275 return x.isNegative ? _negativePrefix : _positivePrefix; | 403 return x.isNegative ? _negativePrefix : _positivePrefix; |
276 } | 404 } |
277 | 405 |
278 /** | 406 /** |
279 * Returns the suffix for [x] based on wether it's positive or negative. | 407 * Returns the suffix for [x] based on wether it's positive or negative. |
280 * In en_US there are no suffixes for positive or negative. | 408 * In en_US there are no suffixes for positive or negative. |
281 */ | 409 */ |
282 String _signSuffix(num x) { | 410 String _signSuffix(num x) { |
283 return x.isNegative ? _negativeSuffix : _positiveSuffix; | 411 return x.isNegative ? _negativeSuffix : _positiveSuffix; |
284 } | 412 } |
413 | |
414 void _setPattern(String newPattern) { | |
415 if (newPattern == null) return; | |
416 // Make spaces non-breaking | |
417 _pattern = newPattern.replaceAll(' ', '\u00a0'); | |
418 var parser = new _NumberFormatParser(this, newPattern); | |
419 parser.parse(); | |
420 } | |
421 | |
422 String toString() => "NumberFormat($_locale, $_pattern)"; | |
285 } | 423 } |
424 | |
425 /** | |
426 * Private class that parses the numeric formatting pattern and sets the | |
427 * variables in [format] to appropriate values. Instances of this are | |
428 * transient and store parsing state in instance variables, so can only be used | |
429 * to parse a single pattern. | |
430 */ | |
431 class _NumberFormatParser { | |
432 | |
433 /** | |
434 * The special characters in the pattern language. All others are treated | |
435 * as literals. | |
436 */ | |
437 static const _PATTERN_SEPARATOR = ';'; | |
438 static const _QUOTE = "'"; | |
439 static const _PATTERN_DIGIT = '#'; | |
440 static const _PATTERN_ZERO_DIGIT = '0'; | |
441 static const _PATTERN_GROUPING_SEPARATOR = ','; | |
442 static const _PATTERN_DECIMAL_SEPARATOR = '.'; | |
443 static const _PATTERN_CURRENCY_SIGN = '\u00A4'; | |
444 static const _PATTERN_PER_MILLE = '\u2030'; | |
445 static const _PATTERN_PERCENT = '%'; | |
446 static const _PATTERN_EXPONENT = 'E'; | |
447 static const _PATTERN_PLUS = '+'; | |
448 | |
449 /** The format whose state we are setting. */ | |
450 final NumberFormat format; | |
451 | |
452 /** The pattern we are parsing. */ | |
453 final _StringIterator pattern; | |
454 | |
455 /** | |
456 * Create a new [_NumberFormatParser] for a particular [NumberFormat] and | |
457 * [input] pattern. | |
458 */ | |
459 _NumberFormatParser(this.format, input) : pattern = _iterator(input) { | |
460 pattern.moveNext(); | |
461 } | |
462 | |
463 /** The [NumberSymbols] for the locale in which our [format] prints. */ | |
464 NumberSymbols get symbols => format.symbols; | |
465 | |
466 /** Parse the input pattern and set the values. */ | |
467 void parse() { | |
468 format._positivePrefix = _parseAffix(); | |
469 var trunk = _parseTrunk(); | |
470 format._positiveSuffix = _parseAffix(); | |
471 // If we have separate positive and negative patterns, now parse the | |
472 // the negative version. | |
473 if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) { | |
474 pattern.moveNext(); | |
475 format._negativePrefix = _parseAffix(); | |
476 // Skip over the negative trunk, verifying that it's identical to the | |
477 // positive trunk. | |
478 for (var each in _iterator(trunk)) { | |
479 if (pattern.current != each && pattern.current != null) { | |
480 throw new FormatException( | |
481 "Positive and negative trunks must be the same"); | |
482 } | |
483 pattern.moveNext(); | |
484 } | |
485 format._negativeSuffix = _parseAffix(); | |
486 } else { | |
487 // If no negative affix is specified, they share the same positive affix. | |
488 format._negativePrefix = format._positivePrefix + format._negativePrefix; | |
489 format._negativeSuffix = format._negativeSuffix + format._positiveSuffix; | |
490 } | |
491 } | |
492 | |
493 /** Variable used in parsing prefixes and suffixes to keep track of | |
494 * whether or not we are in a quoted region. */ | |
495 bool inQuote = false; | |
496 | |
497 /** | |
498 * Parse a prefix or suffix and return the prefix/suffix string. Note that | |
499 * this also may modify the state of [format]. | |
500 */ | |
501 String _parseAffix() { | |
502 var affix = new StringBuffer(); | |
503 inQuote = false; | |
504 var loop = true; | |
505 while (loop) { | |
506 loop = parseCharacterAffix(affix) && pattern.moveNext(); | |
507 } | |
508 return affix.toString(); | |
509 } | |
510 | |
511 /** | |
512 * Parse an individual character as part of a prefix or suffix. Return true | |
513 * if we should continue to look for more affix characters, and false if | |
514 * we have reached the end. | |
515 */ | |
516 bool parseCharacterAffix(StringBuffer affix) { | |
517 var ch = pattern.current; | |
518 if (ch == null) return false; | |
519 if (ch == _QUOTE) { | |
520 var nextChar = pattern.peek; | |
521 if (nextChar == _QUOTE) { | |
522 pattern.moveNext(); | |
523 affix.write(_QUOTE); // 'don''t' | |
524 } else { | |
525 inQuote = !inQuote; | |
526 } | |
527 return true; | |
528 } | |
529 | |
530 if (inQuote) { | |
531 affix.write(ch); | |
532 } else { | |
533 switch (ch) { | |
534 case _PATTERN_DIGIT: | |
535 case _PATTERN_ZERO_DIGIT: | |
536 case _PATTERN_GROUPING_SEPARATOR: | |
537 case _PATTERN_DECIMAL_SEPARATOR: | |
538 case _PATTERN_SEPARATOR: | |
539 return false; | |
540 case _PATTERN_CURRENCY_SIGN: | |
541 // TODO(alanknight): Handle the local/global/portable currency signs | |
542 affix.write(symbols.DEF_CURRENCY_CODE); | |
543 break; | |
544 case _PATTERN_PERCENT: | |
545 if (format._multiplier != 1) { | |
546 throw new FormatException('Too many percent/permill'); | |
Emily Fortuna
2013/04/09 19:39:15
nit: can't you make these error messages specific
Alan Knight
2013/04/09 19:58:27
I think I can't, because one of the error cases is
| |
547 } | |
548 format._multiplier = 100; | |
549 affix.write(symbols.PERCENT); | |
550 break; | |
551 case _PATTERN_PER_MILLE: | |
552 if (format._multiplier != 1) { | |
553 throw new FormatException('Too many percent/permill'); | |
554 } | |
555 format._multiplier = 1000; | |
556 affix.write(symbols.PERMILL); | |
557 break; | |
558 default: | |
559 affix.write(ch); | |
560 } | |
561 } | |
562 return true; | |
563 } | |
564 | |
565 /** Variables used in [parseTrunk] and [parseTrunkCharacter]. */ | |
566 var decimalPos; | |
567 var digitLeftCount; | |
568 var zeroDigitCount; | |
569 var digitRightCount; | |
570 var groupingCount; | |
571 var trunk; | |
572 | |
573 /** | |
574 * Parse the "trunk" portion of the pattern, the piece that doesn't include | |
575 * positive or negative prefixes or suffixes. | |
576 */ | |
577 String _parseTrunk() { | |
578 decimalPos = -1; | |
579 digitLeftCount = 0; | |
580 zeroDigitCount = 0; | |
581 digitRightCount = 0; | |
582 groupingCount = -1; | |
583 | |
584 var loop = true; | |
585 trunk = new StringBuffer(); | |
586 while (pattern.current != null && loop) { | |
587 loop = parseTrunkCharacter(); | |
588 } | |
589 | |
590 if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { | |
591 // Handle '###.###' and '###.' and '.###' | |
592 var n = decimalPos; | |
593 if (n == 0) { // Handle '.###' | |
594 n++; | |
595 } | |
596 digitRightCount = digitLeftCount - n; | |
597 digitLeftCount = n - 1; | |
598 zeroDigitCount = 1; | |
599 } | |
600 | |
601 // Do syntax checking on the digits. | |
602 if (decimalPos < 0 && digitRightCount > 0 || | |
603 decimalPos >= 0 && (decimalPos < digitLeftCount || | |
604 decimalPos > digitLeftCount + zeroDigitCount) || | |
605 groupingCount == 0) { | |
606 throw new FormatException('Malformed pattern "${pattern.input}"'); | |
607 } | |
608 var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount; | |
609 | |
610 format._maximumFractionDigits = | |
611 decimalPos >= 0 ? totalDigits - decimalPos : 0; | |
612 if (decimalPos >= 0) { | |
613 format._minimumFractionDigits = | |
614 digitLeftCount + zeroDigitCount - decimalPos; | |
615 if (format._minimumFractionDigits < 0) { | |
616 format._minimumFractionDigits = 0; | |
617 } | |
618 } | |
619 | |
620 // The effectiveDecimalPos is the position the decimal is at or would be at | |
621 // if there is no decimal. Note that if decimalPos<0, then digitTotalCount | |
622 // == digitLeftCount + zeroDigitCount. | |
623 var effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits; | |
624 format._minimumIntegerDigits = effectiveDecimalPos - digitLeftCount; | |
625 if (format._useExponentialNotation) { | |
626 format._maximumIntegerDigits = | |
627 digitLeftCount + format._minimumIntegerDigits; | |
628 | |
629 // In exponential display, we need to at least show something. | |
630 if (format._maximumFractionDigits == 0 && | |
631 format._minimumIntegerDigits == 0) { | |
632 format._minimumIntegerDigits = 1; | |
633 } | |
634 } | |
635 | |
636 format._groupingSize = max(0, groupingCount); | |
637 format._decimalSeparatorAlwaysShown = decimalPos == 0 || | |
638 decimalPos == totalDigits; | |
639 | |
640 return trunk.toString(); | |
641 } | |
642 | |
643 /** | |
644 * Parse an individual character of the trunk. Return true if we should | |
645 * continue to look for additional trunk characters or false if we have | |
646 * reached the end. | |
647 */ | |
648 bool parseTrunkCharacter() { | |
649 var ch = pattern.current; | |
650 switch (ch) { | |
651 case _PATTERN_DIGIT: | |
652 if (zeroDigitCount > 0) { | |
653 digitRightCount++; | |
654 } else { | |
655 digitLeftCount++; | |
656 } | |
657 if (groupingCount >= 0 && decimalPos < 0) { | |
658 groupingCount++; | |
659 } | |
660 break; | |
661 case _PATTERN_ZERO_DIGIT: | |
662 if (digitRightCount > 0) { | |
663 throw new FormatException('Unexpected "0" in pattern "' | |
664 + pattern.input + '"'); | |
665 } | |
666 zeroDigitCount++; | |
667 if (groupingCount >= 0 && decimalPos < 0) { | |
668 groupingCount++; | |
669 } | |
670 break; | |
671 case _PATTERN_GROUPING_SEPARATOR: | |
672 groupingCount = 0; | |
673 break; | |
674 case _PATTERN_DECIMAL_SEPARATOR: | |
675 if (decimalPos >= 0) { | |
676 throw new FormatException( | |
677 'Multiple decimal separators in pattern "$pattern"'); | |
678 } | |
679 decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; | |
680 break; | |
681 case _PATTERN_EXPONENT: | |
682 trunk.write(ch); | |
683 if (format._useExponentialNotation) { | |
684 throw new FormatException( | |
685 'Multiple exponential symbols in pattern "$pattern"'); | |
686 } | |
687 format._useExponentialNotation = true; | |
688 format._minimumExponentDigits = 0; | |
689 | |
690 // exponent pattern can have a optional '+'. | |
691 pattern.moveNext(); | |
692 var nextChar = pattern.current; | |
693 if (nextChar == _PATTERN_PLUS) { | |
694 trunk.write(pattern.current); | |
695 pattern.moveNext(); | |
696 format._useSignForPositiveExponent = true; | |
697 } | |
698 | |
699 // Use lookahead to parse out the exponential part | |
700 // of the pattern, then jump into phase 2. | |
701 while (pattern.current == _PATTERN_ZERO_DIGIT) { | |
702 trunk.write(pattern.current); | |
703 pattern.moveNext(); | |
704 format._minimumExponentDigits++; | |
705 } | |
706 | |
707 if ((digitLeftCount + zeroDigitCount) < 1 || | |
708 format._minimumExponentDigits < 1) { | |
709 throw new FormatException( | |
710 'Malformed exponential pattern "$pattern"'); | |
711 } | |
712 return false; | |
713 default: | |
714 return false; | |
715 } | |
716 trunk.write(ch); | |
717 pattern.moveNext(); | |
718 return true; | |
719 } | |
720 } | |
721 | |
722 /** | |
723 * Return an iterator on the string as a list of substrings. | |
724 */ | |
725 Iterator _iterator(String s) => new _StringIterator(s); | |
726 | |
727 /** | |
728 * Provides an iterator over a string as a list of substrings, and also | |
729 * gives us a lookahead of one via the [peek] method. | |
730 */ | |
731 class _StringIterator implements Iterator<String> { | |
732 String input; | |
733 var index = -1; | |
734 inBounds(i) => i >= 0 && i < input.length; | |
735 _StringIterator(this.input); | |
736 String get current => inBounds(index) ? input[index] : null; | |
737 | |
738 bool moveNext() => inBounds(++index); | |
739 String get peek => inBounds(index + 1) ? input[index + 1] : null; | |
740 Iterator<String> get iterator => this; | |
741 } | |
OLD | NEW |