| 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 |