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