OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2014 Google Inc. All rights reserved. | 2 * Copyright 2014 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style | 4 * Use of this source code is governed by a BSD-style |
5 * license that can be found in the LICENSE file or at | 5 * license that can be found in the LICENSE file or at |
6 * https://developers.google.com/open-source/licenses/bsd | 6 * https://developers.google.com/open-source/licenses/bsd |
7 */ | 7 */ |
8 part of charted.locale.format; | 8 part of charted.locale.format; |
9 | 9 |
10 /** | 10 /** |
11 * The number formatter of a given locale. Applying the locale specific | 11 * The number formatter of a given locale. Applying the locale specific |
12 * number format, number grouping and currency symbol, etc.. The format | 12 * number format, number grouping and currency symbol, etc.. The format |
13 * function in the NumberFormat class is used to format a number by the given | 13 * function in the NumberFormat class is used to format a number by the given |
14 * specifier with the number properties of the locale. | 14 * specifier with the number properties of the locale. |
15 */ | 15 */ |
16 class NumberFormat { | 16 class NumberFormat { |
17 | |
18 // [[fill]align][sign][symbol][0][width][,][.precision][type] | 17 // [[fill]align][sign][symbol][0][width][,][.precision][type] |
19 static RegExp FORMAT_REGEX = | 18 static RegExp FORMAT_REGEX = new RegExp( |
20 new RegExp(r'(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?' | 19 r'(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?' |
21 r'(\.-?\d+)?([a-z%])?', caseSensitive: false); | 20 r'(\.-?\d+)?([a-z%])?', |
| 21 caseSensitive: false); |
22 | 22 |
23 String localeDecimal; | 23 String localeDecimal; |
24 String localeThousands; | 24 String localeThousands; |
25 List localeGrouping; | 25 List localeGrouping; |
26 List localeCurrency; | 26 List localeCurrency; |
27 Function formatGroup; | 27 Function formatGroup; |
28 | 28 |
29 NumberFormat(Locale locale) { | 29 NumberFormat(Locale locale) { |
30 localeDecimal = locale.decimal; | 30 localeDecimal = locale.decimal; |
31 localeThousands = locale.thousands; | 31 localeThousands = locale.thousands; |
32 localeGrouping = locale.grouping; | 32 localeGrouping = locale.grouping; |
33 localeCurrency = locale.currency; | 33 localeCurrency = locale.currency; |
34 formatGroup = (localeGrouping != null) ? (value) { | 34 formatGroup = (localeGrouping != null) |
35 var i = value.length, | 35 ? (value) { |
36 t = [], | 36 var i = value.length, t = [], j = 0, g = localeGrouping[0]; |
37 j = 0, | 37 while (i > 0 && g > 0) { |
38 g = localeGrouping[0]; | 38 if (i - g >= 0) { |
39 while (i > 0 && g > 0) { | 39 i = i - g; |
40 if (i - g >= 0) { | 40 } else { |
41 i = i - g; | 41 g = i; |
42 } else { | 42 i = 0; |
43 g = i; | 43 } |
44 i = 0; | 44 var length = (i + g) < value.length ? (i + g) : value.length; |
45 } | 45 t.add(value.substring(i, length)); |
46 var length = (i + g) < value.length ? (i + g) : value.length; | 46 g = localeGrouping[j = (j + 1) % localeGrouping.length]; |
47 t.add(value.substring(i, length)); | 47 } |
48 g = localeGrouping[j = (j + 1) % localeGrouping.length]; | 48 return t.reversed.join(localeThousands); |
49 } | 49 } |
50 return t.reversed.join(localeThousands); | 50 : (x) => x; |
51 } : (x) => x; | |
52 } | 51 } |
53 | 52 |
54 /** | 53 /** |
55 * Returns a new format function with the given string specifier. A format | 54 * Returns a new format function with the given string specifier. A format |
56 * function takes a number as the only argument, and returns a string | 55 * function takes a number as the only argument, and returns a string |
57 * representing the formatted number. The format specifier is modeled after | 56 * representing the formatted number. The format specifier is modeled after |
58 * Python 3.1's built-in format specification mini-language. The general form | 57 * Python 3.1's built-in format specification mini-language. The general form |
59 * of a specifier is: | 58 * of a specifier is: |
60 * [[fill]align][sign][symbol][0][width][,][.precision][type]. | 59 * [[fill]align][sign][symbol][0][width][,][.precision][type]. |
61 * | 60 * |
62 * @see <a href="http://docs.python.org/release/3.1.3/library/string.html#form
atspec">format specification mini-language</a> | 61 * @see <a href="http://docs.python.org/release/3.1.3/library/string.html#form
atspec">format specification mini-language</a> |
63 */ | 62 */ |
64 FormatFunction format(String specifier) { | 63 FormatFunction format(String specifier) { |
65 Match match = FORMAT_REGEX.firstMatch(specifier); | 64 Match match = FORMAT_REGEX.firstMatch(specifier); |
66 var fill = match.group(1) != null ? match.group(1) : ' ', | 65 var fill = match.group(1) != null ? match.group(1) : ' ', |
67 align = match.group(2) != null ? match.group(2) : '>', | 66 align = match.group(2) != null ? match.group(2) : '>', |
68 sign = match.group(3) != null ? match.group(3) : '', | 67 sign = match.group(3) != null ? match.group(3) : '', |
69 symbol = match.group(4) != null ? match.group(4) : '', | 68 symbol = match.group(4) != null ? match.group(4) : '', |
70 zfill = match.group(5), | 69 zfill = match.group(5), |
71 width = match.group(6) != null ? int.parse(match.group(6)) : 0, | 70 width = match.group(6) != null ? int.parse(match.group(6)) : 0, |
72 comma = match.group(7) != null, | 71 comma = match.group(7) != null, |
73 precision = match.group(8) != null ? | 72 precision = |
74 int.parse(match.group(8).substring(1)) : null, | 73 match.group(8) != null ? int.parse(match.group(8).substring(1)) : null, |
75 type = match.group(9), | 74 type = match.group(9), |
76 scale = 1, | 75 scale = 1, |
77 prefix = '', | 76 prefix = '', |
78 suffix = '', | 77 suffix = '', |
79 integer = false; | 78 integer = false; |
80 | 79 |
81 if (zfill != null || fill == '0' && align == '=') { | 80 if (zfill != null || fill == '0' && align == '=') { |
82 zfill = fill = '0'; | 81 zfill = fill = '0'; |
83 align = '='; | 82 align = '='; |
84 if (comma) { | 83 if (comma) { |
85 width -= ((width - 1) / 4).floor(); | 84 width -= ((width - 1) / 4).floor(); |
86 } | 85 } |
87 } | 86 } |
88 | 87 |
89 switch (type) { | 88 switch (type) { |
90 case 'n': comma = true; type = 'g'; break; | 89 case 'n': |
91 case '%': scale = 100; suffix = '%'; type = 'f'; break; | 90 comma = true; |
92 case 'p': scale = 100; suffix = '%'; type = 'r'; break; | 91 type = 'g'; |
| 92 break; |
| 93 case '%': |
| 94 scale = 100; |
| 95 suffix = '%'; |
| 96 type = 'f'; |
| 97 break; |
| 98 case 'p': |
| 99 scale = 100; |
| 100 suffix = '%'; |
| 101 type = 'r'; |
| 102 break; |
93 case 'b': | 103 case 'b': |
94 case 'o': | 104 case 'o': |
95 case 'x': | 105 case 'x': |
96 case 'X': if (symbol == '#') prefix = '0' + type.toLowerCase(); break; | 106 case 'X': |
| 107 if (symbol == '#') prefix = '0' + type.toLowerCase(); |
| 108 break; |
97 case 'c': | 109 case 'c': |
98 case 'd': integer = true; precision = 0; break; | 110 case 'd': |
99 case 's': scale = -1; type = 'r'; break; | 111 integer = true; |
| 112 precision = 0; |
| 113 break; |
| 114 case 's': |
| 115 scale = -1; |
| 116 type = 'r'; |
| 117 break; |
100 } | 118 } |
101 | 119 |
102 if (symbol == '\$') { | 120 if (symbol == '\$') { |
103 prefix = localeCurrency[0]; | 121 prefix = localeCurrency[0]; |
104 suffix = localeCurrency[1]; | 122 suffix = localeCurrency[1]; |
105 } | 123 } |
106 | 124 |
107 // If no precision is specified for r, fallback to general notation. | 125 // If no precision is specified for r, fallback to general notation. |
108 if (type == 'r' && precision == null) { | 126 if (type == 'r' && precision == null) { |
109 type = 'g'; | 127 type = 'g'; |
(...skipping 24 matching lines...) Expand all Loading... |
134 value = -value; | 152 value = -value; |
135 negative = '-'; | 153 negative = '-'; |
136 } else { | 154 } else { |
137 negative = sign; | 155 negative = sign; |
138 } | 156 } |
139 | 157 |
140 // Apply the scale, computing it from the value's exponent for si | 158 // Apply the scale, computing it from the value's exponent for si |
141 // format. Preserve the existing suffix, if any, such as the | 159 // format. Preserve the existing suffix, if any, such as the |
142 // currency symbol. | 160 // currency symbol. |
143 if (scale < 0) { | 161 if (scale < 0) { |
144 FormatPrefix unit = new FormatPrefix(value, | 162 FormatPrefix unit = |
145 (precision != null) ? precision : 0); | 163 new FormatPrefix(value, (precision != null) ? precision : 0); |
146 value = unit.scale(value); | 164 value = unit.scale(value); |
147 fullSuffix = unit.symbol + suffix; | 165 fullSuffix = unit.symbol + suffix; |
148 } else { | 166 } else { |
149 value *= scale; | 167 value *= scale; |
150 } | 168 } |
151 | 169 |
152 // Convert to the desired precision. | 170 // Convert to the desired precision. |
153 if (precision != null) { | 171 if (precision != null) { |
154 value = formatFunction(value, precision); | 172 value = formatFunction(value, precision); |
155 } else { | 173 } else { |
156 value = formatFunction(value); | 174 value = formatFunction(value); |
157 } | 175 } |
158 | 176 |
159 // Break the value into the integer part (before) and decimal part | 177 // Break the value into the integer part (before) and decimal part |
160 // (after). | 178 // (after). |
161 var i = value.lastIndexOf('.'), | 179 var i = value.lastIndexOf('.'), |
162 before = i < 0 ? value : value.substring(0, i), | 180 before = i < 0 ? value : value.substring(0, i), |
163 after = i < 0 ? '' : localeDecimal + value.substring(i + 1); | 181 after = i < 0 ? '' : localeDecimal + value.substring(i + 1); |
164 | 182 |
165 // If the fill character is not '0', grouping is applied before | 183 // If the fill character is not '0', grouping is applied before |
166 //padding. | 184 //padding. |
167 if (zfill == null && comma) { | 185 if (zfill == null && comma) { |
168 before = formatGroup(before); | 186 before = formatGroup(before); |
169 } | 187 } |
170 | 188 |
171 var length = prefix.length + before.length + after.length + | 189 var length = prefix.length + |
| 190 before.length + |
| 191 after.length + |
172 (zcomma ? 0 : negative.length), | 192 (zcomma ? 0 : negative.length), |
173 padding = length < width ? new List.filled( | 193 padding = length < width |
174 (length = width - length + 1), '').join(fill) : ''; | 194 ? new List.filled((length = width - length + 1), '').join(fill) |
| 195 : ''; |
175 | 196 |
176 // If the fill character is '0', grouping is applied after padding. | 197 // If the fill character is '0', grouping is applied after padding. |
177 if (zcomma) { | 198 if (zcomma) { |
178 before = formatGroup(padding + before); | 199 before = formatGroup(padding + before); |
179 } | 200 } |
180 | 201 |
181 // Apply prefix. | 202 // Apply prefix. |
182 negative += prefix; | 203 negative += prefix; |
183 | 204 |
184 // Rejoin integer and decimal parts. | 205 // Rejoin integer and decimal parts. |
185 value = before + after; | 206 value = before + after; |
186 | 207 |
187 // Apply any padding and alignment attributes before returning the string. | 208 // Apply any padding and alignment attributes before returning the string. |
188 return (align == '<' ? negative + value + padding | 209 return (align == '<' |
189 : align == '>' ? padding + negative + value | 210 ? negative + value + padding |
190 : align == '^' ? padding.substring(0, length >>= 1) + negative + | 211 : align == '>' |
191 value + padding.substring(length) | 212 ? padding + negative + value |
192 : negative + (zcomma ? value : padding + value)) + fullSuffix; | 213 : align == '^' |
| 214 ? padding.substring(0, length >>= 1) + |
| 215 negative + |
| 216 value + |
| 217 padding.substring(length) |
| 218 : negative + (zcomma ? value : padding + value)) + |
| 219 fullSuffix; |
193 }; | 220 }; |
194 } | 221 } |
195 | 222 |
196 // Gets the format function by given type. | 223 // Gets the format function by given type. |
197 NumberFormatFunction _getFormatFunction(String type) { | 224 NumberFormatFunction _getFormatFunction(String type) { |
198 switch(type) { | 225 switch (type) { |
199 case 'b': | 226 case 'b': |
200 return (num x, [int p = 0]) => x.toInt().toRadixString(2); | 227 return (num x, [int p = 0]) => x.toInt().toRadixString(2); |
201 case 'c': | 228 case 'c': |
202 return (num x, [int p = 0]) => new String.fromCharCodes([x]); | 229 return (num x, [int p = 0]) => new String.fromCharCodes([x]); |
203 case 'o': | 230 case 'o': |
204 return (num x, [int p = 0]) => x.toInt().toRadixString(8); | 231 return (num x, [int p = 0]) => x.toInt().toRadixString(8); |
205 case 'x': | 232 case 'x': |
206 return (num x, [int p = 0]) => x.toInt().toRadixString(16); | 233 return (num x, [int p = 0]) => x.toInt().toRadixString(16); |
207 case 'X': | 234 case 'X': |
208 return (num x, [int p = 0]) => | 235 return (num x, [int p = 0]) => |
209 x.toInt().toRadixString(16).toUpperCase(); | 236 x.toInt().toRadixString(16).toUpperCase(); |
210 case 'g': | 237 case 'g': |
211 return (num x, [int p = 1]) => x.toStringAsPrecision(p); | 238 return (num x, [int p = 1]) => x.toStringAsPrecision(p); |
212 case 'e': | 239 case 'e': |
213 return (num x, [int p = 0]) => x.toStringAsExponential(p); | 240 return (num x, [int p = 0]) => x.toStringAsExponential(p); |
214 case 'f': | 241 case 'f': |
215 return (num x, [int p = 0]) => x.toStringAsFixed(p); | 242 return (num x, [int p = 0]) => x.toStringAsFixed(p); |
216 case 'r': | 243 case 'r': |
217 default: | 244 default: |
218 return (num x, [int p = 0]) => x.toString(); | 245 return (num x, [int p = 0]) => x.toString(); |
219 } | 246 } |
220 } | 247 } |
221 } | 248 } |
OLD | NEW |