LEFT | RIGHT |
1 // © 2017 and later: Unicode, Inc. and others. | 1 // © 2017 and later: Unicode, Inc. and others. |
2 // License & terms of use: http://www.unicode.org/copyright.html#License | 2 // License & terms of use: http://www.unicode.org/copyright.html#License |
3 package com.ibm.icu.impl.number; | 3 package com.ibm.icu.impl.number; |
4 | 4 |
5 import java.math.BigDecimal; | 5 import java.math.BigDecimal; |
6 | 6 |
7 import com.ibm.icu.impl.number.formatters.PaddingFormat.PaddingLocation; | 7 import com.ibm.icu.impl.number.formatters.PaddingFormat; |
| 8 import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition; |
8 import com.ibm.icu.text.DecimalFormatSymbols; | 9 import com.ibm.icu.text.DecimalFormatSymbols; |
9 | 10 |
10 /** | 11 /** |
11 * Handles parsing and creation of the compact pattern string representation of
a decimal format. | 12 * Handles parsing and creation of the compact pattern string representation of
a decimal format. |
12 */ | 13 */ |
13 public class PatternString { | 14 public class PatternString { |
14 | 15 |
15 /** | 16 /** |
16 * Parses a pattern string into a new property bag. | 17 * Parses a pattern string into a new property bag. |
17 * | 18 * |
18 * @param pattern The pattern string, like "#,##0.00" | 19 * @param pattern The pattern string, like "#,##0.00" |
| 20 * @param ignoreRounding Whether to leave out rounding information (minFrac, m
axFrac, and rounding |
| 21 * increment) when parsing the pattern. This may be desirable if a custom
rounding mode, such |
| 22 * as CurrencyUsage, is to be used instead. |
19 * @return A property bag object. | 23 * @return A property bag object. |
20 * @throws IllegalArgumentException If there is a syntax error in the pattern
string. | 24 * @throws IllegalArgumentException If there is a syntax error in the pattern
string. |
21 */ | 25 */ |
| 26 public static Properties parseToProperties(String pattern, boolean ignoreRound
ing) { |
| 27 Properties properties = new Properties(); |
| 28 LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding); |
| 29 return properties; |
| 30 } |
| 31 |
22 public static Properties parseToProperties(String pattern) { | 32 public static Properties parseToProperties(String pattern) { |
23 Properties properties = new Properties(); | 33 return parseToProperties(pattern, false); |
24 LdmlDecimalPatternParser.parse(pattern, properties); | |
25 return properties; | |
26 } | 34 } |
27 | 35 |
28 /** | 36 /** |
29 * Parses a pattern string into an existing property bag. All properties that
can be encoded into | 37 * Parses a pattern string into an existing property bag. All properties that
can be encoded into |
30 * a pattern string will be overwritten with either their default value or wit
h the value coming | 38 * a pattern string will be overwritten with either their default value or wit
h the value coming |
31 * from the pattern string. Properties that cannot be encoded into a pattern s
tring, such as | 39 * from the pattern string. Properties that cannot be encoded into a pattern s
tring, such as |
32 * rounding mode, are not modified. | 40 * rounding mode, are not modified. |
33 * | 41 * |
34 * @param pattern The pattern string, like "#,##0.00" | 42 * @param pattern The pattern string, like "#,##0.00" |
35 * @param properties The property bag object to overwrite. | 43 * @param properties The property bag object to overwrite. |
| 44 * @param ignoreRounding Whether to leave out rounding information (minFrac, m
axFrac, and rounding |
| 45 * increment) when parsing the pattern. This may be desirable if a custom
rounding mode, such |
| 46 * as CurrencyUsage, is to be used instead. |
36 * @throws IllegalArgumentException If there was a syntax error in the pattern
string. | 47 * @throws IllegalArgumentException If there was a syntax error in the pattern
string. |
37 */ | 48 */ |
| 49 public static void parseToExistingProperties( |
| 50 String pattern, Properties properties, boolean ignoreRounding) { |
| 51 LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding); |
| 52 } |
| 53 |
38 public static void parseToExistingProperties(String pattern, Properties proper
ties) { | 54 public static void parseToExistingProperties(String pattern, Properties proper
ties) { |
39 LdmlDecimalPatternParser.parse(pattern, properties); | 55 parseToExistingProperties(pattern, properties, false); |
40 } | 56 } |
41 | 57 |
42 /** | 58 /** |
43 * Creates a pattern string from a property bag. | 59 * Creates a pattern string from a property bag. |
44 * | 60 * |
45 * <p>Since pattern strings support only a subset of the functionality availab
le in a property | 61 * <p>Since pattern strings support only a subset of the functionality availab
le in a property |
46 * bag, a new property bag created from the string returned by this function m
ay not be the same | 62 * bag, a new property bag created from the string returned by this function m
ay not be the same |
47 * as the original property bag. | 63 * as the original property bag. |
48 * | 64 * |
49 * @param properties The property bag to serialize. | 65 * @param properties The property bag to serialize. |
50 * @return A pattern string approximately serializing the property bag. | 66 * @return A pattern string approximately serializing the property bag. |
51 */ | 67 */ |
52 public static String propertiesToString(Properties properties) { | 68 public static String propertiesToString(Properties properties) { |
53 StringBuilder sb = new StringBuilder(); | 69 StringBuilder sb = new StringBuilder(); |
54 | 70 |
55 // Convenience references | 71 // Convenience references |
56 // The Math.min() calls prevent DoS | 72 // The Math.min() calls prevent DoS |
57 int dosMax = 100; | 73 int dosMax = 100; |
58 int groupingSize = Math.min(properties.getSecondaryGroupingSize(), dosMax); | 74 int groupingSize = Math.min(properties.getSecondaryGroupingSize(), dosMax); |
59 int firstGroupingSize = Math.min(properties.getGroupingSize(), dosMax); | 75 int firstGroupingSize = Math.min(properties.getGroupingSize(), dosMax); |
60 int paddingWidth = Math.min(properties.getPaddingWidth(), dosMax); | 76 int paddingWidth = Math.min(properties.getFormatWidth(), dosMax); |
61 PaddingLocation paddingLocation = properties.getPaddingLocation(); | 77 PadPosition paddingLocation = properties.getPadPosition(); |
62 CharSequence paddingString = properties.getPaddingString(); | 78 String paddingString = properties.getPadString(); |
63 int minInt = Math.max(Math.min(properties.getMinimumIntegerDigits(), dosMax)
, 0); | 79 int minInt = Math.max(Math.min(properties.getMinimumIntegerDigits(), dosMax)
, 0); |
64 int maxInt = Math.min(properties.getMaximumIntegerDigits(), dosMax); | 80 int maxInt = Math.min(properties.getMaximumIntegerDigits(), dosMax); |
65 int minFrac = Math.max(Math.min(properties.getMinimumFractionDigits(), dosMa
x), 0); | 81 int minFrac = Math.max(Math.min(properties.getMinimumFractionDigits(), dosMa
x), 0); |
66 int maxFrac = Math.min(properties.getMaximumFractionDigits(), dosMax); | 82 int maxFrac = Math.min(properties.getMaximumFractionDigits(), dosMax); |
67 int minSig = Math.min(properties.getMinimumSignificantDigits(), dosMax); | 83 int minSig = Math.min(properties.getMinimumSignificantDigits(), dosMax); |
68 int maxSig = Math.min(properties.getMaximumSignificantDigits(), dosMax); | 84 int maxSig = Math.min(properties.getMaximumSignificantDigits(), dosMax); |
69 boolean alwaysShowDecimal = properties.getAlwaysShowDecimal(); | 85 boolean alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown(); |
70 int exponentDigits = Math.min(properties.getExponentDigits(), dosMax); | 86 int exponentDigits = Math.min(properties.getMinimumExponentDigits(), dosMax)
; |
71 boolean exponentShowPlusSign = properties.getExponentShowPlusSign(); | 87 boolean exponentShowPlusSign = properties.getExponentSignAlwaysShown(); |
72 CharSequence pp = properties.getPositivePrefix(); | 88 String pp = properties.getPositivePrefix(); |
73 CharSequence ppp = properties.getPositivePrefixPattern(); | 89 String ppp = properties.getPositivePrefixPattern(); |
74 CharSequence ps = properties.getPositiveSuffix(); | 90 String ps = properties.getPositiveSuffix(); |
75 CharSequence psp = properties.getPositiveSuffixPattern(); | 91 String psp = properties.getPositiveSuffixPattern(); |
76 CharSequence np = properties.getNegativePrefix(); | 92 String np = properties.getNegativePrefix(); |
77 CharSequence npp = properties.getNegativePrefixPattern(); | 93 String npp = properties.getNegativePrefixPattern(); |
78 CharSequence ns = properties.getNegativeSuffix(); | 94 String ns = properties.getNegativeSuffix(); |
79 CharSequence nsp = properties.getNegativeSuffixPattern(); | 95 String nsp = properties.getNegativeSuffixPattern(); |
80 | 96 |
81 // Prefixes | 97 // Prefixes |
82 if (ppp != null) sb.append(ppp); | 98 if (ppp != null) sb.append(ppp); |
83 escape(pp, sb); | 99 AffixPatternUtils.escape(pp, sb); |
84 int afterPrefixPos = sb.length(); | 100 int afterPrefixPos = sb.length(); |
85 | 101 |
86 // Figure out the grouping sizes. | 102 // Figure out the grouping sizes. |
87 int grouping1, grouping2; | 103 int grouping1, grouping2, grouping; |
88 if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GROUPING_S
IZE) | 104 if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GROUPING_S
IZE) |
89 && firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING_SIZ
E) | 105 && firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING_SIZ
E) |
90 && groupingSize != firstGroupingSize) { | 106 && groupingSize != firstGroupingSize) { |
| 107 grouping = groupingSize; |
91 grouping1 = groupingSize; | 108 grouping1 = groupingSize; |
92 grouping2 = firstGroupingSize; | 109 grouping2 = firstGroupingSize; |
93 } else if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GRO
UPING_SIZE)) { | 110 } else if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GRO
UPING_SIZE)) { |
| 111 grouping = groupingSize; |
94 grouping1 = 0; | 112 grouping1 = 0; |
95 grouping2 = groupingSize; | 113 grouping2 = groupingSize; |
96 } else if (firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING
_SIZE)) { | 114 } else if (firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING
_SIZE)) { |
| 115 grouping = groupingSize; |
97 grouping1 = 0; | 116 grouping1 = 0; |
98 grouping2 = firstGroupingSize; | 117 grouping2 = firstGroupingSize; |
99 } else { | 118 } else { |
| 119 grouping = 0; |
100 grouping1 = 0; | 120 grouping1 = 0; |
101 grouping2 = 0; | 121 grouping2 = 0; |
102 } | 122 } |
103 int groupingLength = grouping1 + grouping2 + 1; | 123 int groupingLength = grouping1 + grouping2 + 1; |
104 | 124 |
105 // Figure out the digits we need to put in the pattern. | 125 // Figure out the digits we need to put in the pattern. |
106 BigDecimal roundingInterval = properties.getRoundingInterval(); | 126 BigDecimal roundingInterval = properties.getRoundingIncrement(); |
107 StringBuilder digitsString = new StringBuilder(); | 127 StringBuilder digitsString = new StringBuilder(); |
108 int digitsStringScale = 0; | 128 int digitsStringScale = 0; |
109 if (maxSig != Math.min(dosMax, Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS
)) { | 129 if (maxSig != Math.min(dosMax, Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS
)) { |
110 // Significant Digits. | 130 // Significant Digits. |
111 while (digitsString.length() < minSig) { | 131 while (digitsString.length() < minSig) { |
112 digitsString.append('@'); | 132 digitsString.append('@'); |
113 } | 133 } |
114 while (digitsString.length() < maxSig) { | 134 while (digitsString.length() < maxSig) { |
115 digitsString.append('#'); | 135 digitsString.append('#'); |
116 } | 136 } |
117 } else if (roundingInterval != Properties.DEFAULT_ROUNDING_INTERVAL) { | 137 } else if (roundingInterval != Properties.DEFAULT_ROUNDING_INCREMENT) { |
118 // Rounding Interval. | 138 // Rounding Interval. |
119 digitsStringScale = -roundingInterval.scale(); | 139 digitsStringScale = -roundingInterval.scale(); |
120 // TODO: Check for DoS here? | 140 // TODO: Check for DoS here? |
121 String str = roundingInterval.scaleByPowerOfTen(roundingInterval.scale()).
toPlainString(); | 141 String str = roundingInterval.scaleByPowerOfTen(roundingInterval.scale()).
toPlainString(); |
122 if (str.charAt(0) == '\'') { | 142 if (str.charAt(0) == '\'') { |
123 // TODO: Unsupported operation exception or fail silently? | 143 // TODO: Unsupported operation exception or fail silently? |
124 digitsString.append(str, 1, str.length()); | 144 digitsString.append(str, 1, str.length()); |
125 } else { | 145 } else { |
126 digitsString.append(str); | 146 digitsString.append(str); |
127 } | 147 } |
(...skipping 10 matching lines...) Expand all Loading... |
138 int m0 = Math.max(groupingLength, digitsString.length() + digitsStringScale)
; | 158 int m0 = Math.max(groupingLength, digitsString.length() + digitsStringScale)
; |
139 m0 = (maxInt != dosMax) ? Math.max(maxInt, m0) - 1 : m0 - 1; | 159 m0 = (maxInt != dosMax) ? Math.max(maxInt, m0) - 1 : m0 - 1; |
140 int mN = (maxFrac != dosMax) ? Math.min(-maxFrac, digitsStringScale) : digit
sStringScale; | 160 int mN = (maxFrac != dosMax) ? Math.min(-maxFrac, digitsStringScale) : digit
sStringScale; |
141 for (int magnitude = m0; magnitude >= mN; magnitude--) { | 161 for (int magnitude = m0; magnitude >= mN; magnitude--) { |
142 int di = digitsString.length() + digitsStringScale - magnitude - 1; | 162 int di = digitsString.length() + digitsStringScale - magnitude - 1; |
143 if (di < 0 || di >= digitsString.length()) { | 163 if (di < 0 || di >= digitsString.length()) { |
144 sb.append('#'); | 164 sb.append('#'); |
145 } else { | 165 } else { |
146 sb.append(digitsString.charAt(di)); | 166 sb.append(digitsString.charAt(di)); |
147 } | 167 } |
148 if (magnitude > 0 && magnitude == grouping1 + grouping2) { | 168 if (magnitude > grouping2 && grouping > 0 && (magnitude - grouping2) % gro
uping == 0) { |
149 sb.append(','); | 169 sb.append(','); |
150 } else if (magnitude > 0 && magnitude == grouping2) { | 170 } else if (magnitude > 0 && magnitude == grouping2) { |
151 sb.append(','); | 171 sb.append(','); |
152 } else if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) { | 172 } else if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) { |
153 sb.append('.'); | 173 sb.append('.'); |
154 } | 174 } |
155 } | 175 } |
156 | 176 |
157 // Exponential notation | 177 // Exponential notation |
158 if (exponentDigits != Math.min(dosMax, Properties.DEFAULT_EXPONENT_DIGITS))
{ | 178 if (exponentDigits != Math.min(dosMax, Properties.DEFAULT_MINIMUM_EXPONENT_D
IGITS)) { |
159 sb.append('E'); | 179 sb.append('E'); |
160 if (exponentShowPlusSign) { | 180 if (exponentShowPlusSign) { |
161 sb.append('+'); | 181 sb.append('+'); |
162 } | 182 } |
163 for (int i = 0; i < exponentDigits; i++) { | 183 for (int i = 0; i < exponentDigits; i++) { |
164 sb.append('0'); | 184 sb.append('0'); |
165 } | 185 } |
166 } | 186 } |
167 | 187 |
168 // Suffixes | 188 // Suffixes |
169 int beforeSuffixPos = sb.length(); | 189 int beforeSuffixPos = sb.length(); |
170 if (psp != null) sb.append(psp); | 190 if (psp != null) sb.append(psp); |
171 escape(ps, sb); | 191 AffixPatternUtils.escape(ps, sb); |
172 | 192 |
173 // Resolve Padding | 193 // Resolve Padding |
174 if (paddingWidth != Properties.DEFAULT_PADDING_WIDTH) { | 194 if (paddingWidth != Properties.DEFAULT_FORMAT_WIDTH) { |
175 while (paddingWidth - sb.length() > 0) { | 195 while (paddingWidth - sb.length() > 0) { |
176 sb.insert(afterPrefixPos, '#'); | 196 sb.insert(afterPrefixPos, '#'); |
177 beforeSuffixPos++; | 197 beforeSuffixPos++; |
178 } | 198 } |
179 int addedLength; | 199 int addedLength; |
180 switch (paddingLocation) { | 200 switch (paddingLocation) { |
181 case BEFORE_PREFIX: | 201 case BEFORE_PREFIX: |
182 addedLength = escape(paddingString, sb, 0); | 202 addedLength = escapePaddingString(paddingString, sb, 0); |
183 sb.insert(0, '*'); | 203 sb.insert(0, '*'); |
184 afterPrefixPos += addedLength + 1; | 204 afterPrefixPos += addedLength + 1; |
185 beforeSuffixPos += addedLength + 1; | 205 beforeSuffixPos += addedLength + 1; |
186 break; | 206 break; |
187 case AFTER_PREFIX: | 207 case AFTER_PREFIX: |
188 addedLength = escape(paddingString, sb, afterPrefixPos); | 208 addedLength = escapePaddingString(paddingString, sb, afterPrefixPos); |
189 sb.insert(afterPrefixPos, '*'); | 209 sb.insert(afterPrefixPos, '*'); |
190 afterPrefixPos += addedLength + 1; | 210 afterPrefixPos += addedLength + 1; |
191 beforeSuffixPos += addedLength + 1; | 211 beforeSuffixPos += addedLength + 1; |
192 break; | 212 break; |
193 case BEFORE_SUFFIX: | 213 case BEFORE_SUFFIX: |
194 escape(paddingString, sb, beforeSuffixPos); | 214 escapePaddingString(paddingString, sb, beforeSuffixPos); |
195 sb.insert(beforeSuffixPos, '*'); | 215 sb.insert(beforeSuffixPos, '*'); |
196 break; | 216 break; |
197 case AFTER_SUFFIX: | 217 case AFTER_SUFFIX: |
198 sb.append('*'); | 218 sb.append('*'); |
199 escape(paddingString, sb); | 219 escapePaddingString(paddingString, sb, sb.length()); |
200 break; | 220 break; |
201 } | 221 } |
202 } | 222 } |
203 | 223 |
204 // Negative affixes | 224 // Negative affixes |
205 if (np != null || npp != null || ns != null || nsp != null) { | 225 // Ignore if the negative prefix pattern is "-" and the negative suffix is e
mpty |
| 226 if (np != null |
| 227 || ns != null |
| 228 || (npp == null && nsp != null) |
| 229 || (npp != null && (npp.length() != 1 || npp.charAt(0) != '-' || nsp.len
gth() != 0))) { |
206 sb.append(';'); | 230 sb.append(';'); |
207 if (npp != null) sb.append(npp); | 231 if (npp != null) sb.append(npp); |
208 escape(np, sb); | 232 AffixPatternUtils.escape(np, sb); |
209 // Copy the positive digit format into the negative. | 233 // Copy the positive digit format into the negative. |
210 // This is optional; the pattern is the same as if '#' were appended here
instead. | 234 // This is optional; the pattern is the same as if '#' were appended here
instead. |
211 sb.append(sb, afterPrefixPos, beforeSuffixPos); | 235 sb.append(sb, afterPrefixPos, beforeSuffixPos); |
212 if (nsp != null) sb.append(nsp); | 236 if (nsp != null) sb.append(nsp); |
213 escape(ns, sb); | 237 AffixPatternUtils.escape(ns, sb); |
214 } | 238 } |
215 | 239 |
216 return sb.toString(); | 240 return sb.toString(); |
217 } | 241 } |
218 | 242 |
219 /** @return The number of chars inserted. */ | 243 /** @return The number of chars inserted. */ |
220 private static int escape(CharSequence input, StringBuilder sb) { | 244 private static int escapePaddingString(CharSequence input, StringBuilder outpu
t, int startIndex) { |
221 if (input == null) return 0; | 245 if (input == null || input.length() == 0) input = PaddingFormat.FALLBACK_PAD
DING_STRING; |
222 int length = input.length(); | 246 int startLength = output.length(); |
223 if (length == 0) return 0; | 247 if (input.length() == 1) { |
224 int startLength = sb.length(); | 248 if (input.equals("'")) { |
225 if (length > 1) sb.append('\''); | 249 output.insert(startIndex, "''"); |
226 for (int i = 0; i < length; i++) { | |
227 char ch = input.charAt(i); | |
228 if (ch == '\'') { | |
229 sb.append("''"); | |
230 } else { | 250 } else { |
231 sb.append(ch); | 251 output.insert(startIndex, input); |
232 } | 252 } |
233 } | 253 } else { |
234 if (length > 1) sb.append('\''); | 254 output.insert(startIndex, '\''); |
235 return sb.length() - startLength; | 255 int offset = 1; |
236 } | 256 for (int i = 0; i < input.length(); i++) { |
237 | 257 // it's okay to deal in chars here because the quote mark is the only in
teresting thing. |
238 /** @return The number of chars inserted. */ | 258 char ch = input.charAt(i); |
239 private static int escape(CharSequence input, StringBuilder sb, int insertInde
x) { | 259 if (ch == '\'') { |
240 // Although this triggers a new object creation, it reduces the number of ca
lls to insert (and | 260 output.insert(startIndex + offset, "''"); |
241 // therefore System.arraycopy). | 261 offset += 2; |
242 StringBuilder temp = new StringBuilder(); | 262 } else { |
243 int length = escape(input, temp); | 263 output.insert(startIndex + offset, ch); |
244 sb.insert(insertIndex, temp); | 264 offset += 1; |
245 return length; | 265 } |
| 266 } |
| 267 output.insert(startIndex + offset, '\''); |
| 268 } |
| 269 return output.length() - startLength; |
246 } | 270 } |
247 | 271 |
248 /** | 272 /** |
249 * Converts a pattern between standard notation and localized notation. Locali
zed notation means | 273 * Converts a pattern between standard notation and localized notation. Locali
zed notation means |
250 * that instead of using generic placeholders in the pattern, you use the corr
esponding | 274 * that instead of using generic placeholders in the pattern, you use the corr
esponding |
251 * locale-specific characters instead. | 275 * locale-specific characters instead. For example, in locale <em>fr-FR</em>,
the period in the |
| 276 * pattern "0.000" means "decimal" in standard notation (as it does in every o
ther locale), but it |
| 277 * means "grouping" in localized notation. |
252 * | 278 * |
253 * @param input The pattern to convert. | 279 * @param input The pattern to convert. |
254 * @param symbols The symbols corresponding to the localized pattern. | 280 * @param symbols The symbols corresponding to the localized pattern. |
255 * @param toLocalized true to convert from standard to localized notation; fal
se to convert from | 281 * @param toLocalized true to convert from standard to localized notation; fal
se to convert from |
256 * localized to standard notation. | 282 * localized to standard notation. |
257 * @return The pattern expressed in the other notation. | 283 * @return The pattern expressed in the other notation. |
258 * @deprecated ICU 59 This method is provided for backwards compatibility and
should not be used | 284 * @deprecated ICU 59 This method is provided for backwards compatibility and
should not be used |
259 * in any new code. | 285 * in any new code. |
260 */ | 286 */ |
261 @Deprecated | 287 @Deprecated |
262 public static String convertLocalized( | 288 public static String convertLocalized( |
263 CharSequence input, DecimalFormatSymbols symbols, boolean toLocalized) { | 289 CharSequence input, DecimalFormatSymbols symbols, boolean toLocalized) { |
264 if (input == null) return null; | 290 if (input == null) return null; |
265 | 291 |
| 292 /// This is not the prettiest function in the world, but it gets the job don
e. /// |
| 293 |
266 // Construct a table of code points to be converted between localized and st
andard. | 294 // Construct a table of code points to be converted between localized and st
andard. |
267 int[][] table = new int[6][2]; | 295 int[][] table = new int[6][2]; |
268 int standIdx = toLocalized ? 0 : 1; | 296 int standIdx = toLocalized ? 0 : 1; |
269 int localIdx = toLocalized ? 1 : 0; | 297 int localIdx = toLocalized ? 1 : 0; |
270 table[0][standIdx] = '%'; | 298 table[0][standIdx] = '%'; |
271 table[0][localIdx] = Character.codePointAt(symbols.getPercentString(), 0); | 299 table[0][localIdx] = symbols.getPercent(); |
272 table[1][standIdx] = '‰'; | 300 table[1][standIdx] = '‰'; |
273 table[1][localIdx] = Character.codePointAt(symbols.getPerMillString(), 0); | 301 table[1][localIdx] = symbols.getPerMill(); |
274 table[2][standIdx] = '.'; | 302 table[2][standIdx] = '.'; |
275 table[2][localIdx] = Character.codePointAt(symbols.getDecimalSeparatorString
(), 0); | 303 table[2][localIdx] = symbols.getDecimalSeparator(); |
276 table[3][standIdx] = ','; | 304 table[3][standIdx] = ','; |
277 table[3][localIdx] = Character.codePointAt(symbols.getGroupingSeparatorStrin
g(), 0); | 305 table[3][localIdx] = symbols.getGroupingSeparator(); |
278 table[4][standIdx] = '-'; | 306 table[4][standIdx] = '-'; |
279 table[4][localIdx] = Character.codePointAt(symbols.getMinusSignString(), 0); | 307 table[4][localIdx] = symbols.getMinusSign(); |
280 table[5][standIdx] = '+'; | 308 table[5][standIdx] = '+'; |
281 table[5][localIdx] = Character.codePointAt(symbols.getPlusSignString(), 0); | 309 table[5][localIdx] = symbols.getPlusSign(); |
| 310 |
| 311 // Special case: localIdx characters are NOT allowed to be quotes, like in d
e_CH. |
| 312 // Use '’' instead. |
| 313 for (int i = 0; i < table.length; i++) { |
| 314 if (table[i][localIdx] == '\'') { |
| 315 table[i][localIdx] = '’'; |
| 316 } |
| 317 } |
282 | 318 |
283 // Iterate through the string and convert | 319 // Iterate through the string and convert |
284 int offset = 0; | 320 int offset = 0; |
285 boolean insideQuote = false; | 321 int state = 0; |
286 StringBuilder result = new StringBuilder(); | 322 StringBuilder result = new StringBuilder(); |
287 for (; offset < input.length(); ) { | 323 for (; offset < input.length(); ) { |
288 int cp = Character.codePointAt(input, offset); | 324 int cp = Character.codePointAt(input, offset); |
289 int cpToAppend = cp; | 325 int cpToAppend = cp; |
290 if (insideQuote) { | 326 |
| 327 if (state == 1 || state == 3 || state == 4) { |
| 328 // Inside user-specified quote |
291 if (cp == '\'') { | 329 if (cp == '\'') { |
292 insideQuote = false; | 330 if (state == 1) { |
| 331 state = 0; |
| 332 } else if (state == 3) { |
| 333 state = 2; |
| 334 cpToAppend = -1; |
| 335 } else { |
| 336 state = 2; |
| 337 } |
293 } | 338 } |
294 } else { | 339 } else { |
| 340 // Base state or inside special character quote |
295 if (cp == '\'') { | 341 if (cp == '\'') { |
296 insideQuote = true; | 342 if (state == 2 && offset + 1 < input.length()) { |
297 } else { | 343 int nextCp = Character.codePointAt(input, offset + 1); |
| 344 if (nextCp == '\'') { |
| 345 // escaped quote |
| 346 state = 4; |
| 347 } else { |
| 348 // begin user-specified quote sequence |
| 349 // we are already in a quote sequence, so omit the opening quote |
| 350 state = 3; |
| 351 cpToAppend = -1; |
| 352 } |
| 353 } else { |
| 354 state = 1; |
| 355 } |
| 356 } else { |
| 357 boolean needsSpecialQuote = false; |
298 for (int i = 0; i < table.length; i++) { | 358 for (int i = 0; i < table.length; i++) { |
299 if (table[i][0] == cp) { | 359 if (table[i][0] == cp) { |
300 cpToAppend = table[i][1]; | 360 cpToAppend = table[i][1]; |
| 361 needsSpecialQuote = false; // in case an earlier translation trigg
ered it |
301 break; | 362 break; |
| 363 } else if (table[i][1] == cp) { |
| 364 needsSpecialQuote = true; |
302 } | 365 } |
303 } | 366 } |
304 } | 367 if (state == 0 && needsSpecialQuote) { |
305 } | 368 state = 2; |
306 result.appendCodePoint(cpToAppend); | 369 result.appendCodePoint('\''); |
| 370 } else if (state == 2 && !needsSpecialQuote) { |
| 371 state = 0; |
| 372 result.appendCodePoint('\''); |
| 373 } |
| 374 } |
| 375 } |
| 376 if (cpToAppend != -1) { |
| 377 result.appendCodePoint(cpToAppend); |
| 378 } |
307 offset += Character.charCount(cp); | 379 offset += Character.charCount(cp); |
| 380 } |
| 381 if (state == 2) { |
| 382 result.appendCodePoint('\''); |
308 } | 383 } |
309 return result.toString(); | 384 return result.toString(); |
310 } | 385 } |
311 | 386 |
312 /** Implements a recursive descent parser for decimal format patterns. */ | 387 /** Implements a recursive descent parser for decimal format patterns. */ |
313 static class LdmlDecimalPatternParser { | 388 static class LdmlDecimalPatternParser { |
314 | 389 |
315 /** | 390 /** |
316 * An internal, intermediate data structure used for storing parse results b
efore they are | 391 * An internal, intermediate data structure used for storing parse results b
efore they are |
317 * finalized into a DecimalFormatPattern.Builder. | 392 * finalized into a DecimalFormatPattern.Builder. |
318 */ | 393 */ |
319 private static class PatternParseResult { | 394 private static class PatternParseResult { |
320 SubpatternParseResult positive = new SubpatternParseResult(); | 395 SubpatternParseResult positive = new SubpatternParseResult(); |
321 SubpatternParseResult negative = null; | 396 SubpatternParseResult negative = null; |
322 | 397 |
323 /** Finalizes the temporary data stored in the PatternParseResult to the B
uilder. */ | 398 /** Finalizes the temporary data stored in the PatternParseResult to the B
uilder. */ |
324 void saveToProperties(Properties properties) { | 399 void saveToProperties(Properties properties, boolean ignoreRounding) { |
325 // Translate from PatternState to Properties. | 400 // Translate from PatternState to Properties. |
326 // Note that most data from "negative" is ignored per the specification
of DecimalFormat. | 401 // Note that most data from "negative" is ignored per the specification
of DecimalFormat. |
327 | 402 |
328 // Grouping settings | 403 // Grouping settings |
329 if (positive.groupingSizes[1] != -1) { | 404 if (positive.groupingSizes[1] != -1) { |
330 properties.setGroupingSize(positive.groupingSizes[0]); | 405 properties.setGroupingSize(positive.groupingSizes[0]); |
331 } else { | 406 } else { |
332 properties.setGroupingSize(Properties.DEFAULT_GROUPING_SIZE); | 407 properties.setGroupingSize(Properties.DEFAULT_GROUPING_SIZE); |
333 } | 408 } |
334 if (positive.groupingSizes[2] != -1) { | 409 if (positive.groupingSizes[2] != -1) { |
335 properties.setSecondaryGroupingSize(positive.groupingSizes[1]); | 410 properties.setSecondaryGroupingSize(positive.groupingSizes[1]); |
336 } else { | 411 } else { |
337 properties.setSecondaryGroupingSize(Properties.DEFAULT_SECONDARY_GROUP
ING_SIZE); | 412 properties.setSecondaryGroupingSize(Properties.DEFAULT_SECONDARY_GROUP
ING_SIZE); |
338 } | 413 } |
339 | 414 |
| 415 // For backwards compatibility, require that the pattern emit at least o
ne min digit. |
| 416 int minInt, minFrac; |
| 417 if (positive.totalIntegerDigits == 0 && positive.maximumFractionDigits >
0) { |
| 418 // patterns like ".##" |
| 419 minInt = 0; |
| 420 minFrac = Math.max(1, positive.minimumFractionDigits); |
| 421 } else if (positive.minimumIntegerDigits == 0 && positive.minimumFractio
nDigits == 0) { |
| 422 // patterns like "#.##" |
| 423 minInt = 1; |
| 424 minFrac = 0; |
| 425 } else { |
| 426 minInt = positive.minimumIntegerDigits; |
| 427 minFrac = positive.minimumFractionDigits; |
| 428 } |
| 429 |
340 // Rounding settings | 430 // Rounding settings |
341 // Don't set basic rounding when there is a currency sign; defer to Curr
encyUsage | 431 // Don't set basic rounding when there is a currency sign; defer to Curr
encyUsage |
342 if (positive.minimumSignificantDigits > 0) { | 432 if (positive.minimumSignificantDigits > 0) { |
343 if (!positive.hasCurrencySign) { | 433 properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACTIO
N_DIGITS); |
344 properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACT
ION_DIGITS); | 434 properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACTIO
N_DIGITS); |
345 properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACT
ION_DIGITS); | 435 properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMENT)
; |
346 properties.setRoundingInterval(Properties.DEFAULT_ROUNDING_INTERVAL)
; | |
347 } | |
348 properties.setMinimumSignificantDigits(positive.minimumSignificantDigi
ts); | 436 properties.setMinimumSignificantDigits(positive.minimumSignificantDigi
ts); |
349 properties.setMaximumSignificantDigits(positive.maximumSignificantDigi
ts); | 437 properties.setMaximumSignificantDigits(positive.maximumSignificantDigi
ts); |
350 } else if (!positive.rounding.isZero()) { | 438 } else if (!positive.rounding.isZero()) { |
351 if (!positive.hasCurrencySign) { | 439 if (!ignoreRounding) { |
352 properties.setMinimumFractionDigits(positive.minimumFractionDigits); | 440 properties.setMinimumFractionDigits(minFrac); |
353 properties.setMaximumFractionDigits(positive.maximumFractionDigits); | 441 properties.setMaximumFractionDigits(positive.maximumFractionDigits); |
354 properties.setRoundingInterval(positive.rounding.toBigDecimal()); | 442 properties.setRoundingIncrement(positive.rounding.toBigDecimal()); |
| 443 } else { |
| 444 properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACT
ION_DIGITS); |
| 445 properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACT
ION_DIGITS); |
| 446 properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMEN
T); |
355 } | 447 } |
356 properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGN
IFICANT_DIGITS); | 448 properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGN
IFICANT_DIGITS); |
357 properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGN
IFICANT_DIGITS); | 449 properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGN
IFICANT_DIGITS); |
358 } else { | 450 } else { |
359 if (!positive.hasCurrencySign) { | 451 if (!ignoreRounding) { |
360 properties.setMinimumFractionDigits(positive.minimumFractionDigits); | 452 properties.setMinimumFractionDigits(minFrac); |
361 properties.setMaximumFractionDigits(positive.maximumFractionDigits); | 453 properties.setMaximumFractionDigits(positive.maximumFractionDigits); |
362 properties.setRoundingInterval(Properties.DEFAULT_ROUNDING_INTERVAL)
; | 454 properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMEN
T); |
| 455 } else { |
| 456 properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACT
ION_DIGITS); |
| 457 properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACT
ION_DIGITS); |
| 458 properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMEN
T); |
363 } | 459 } |
364 properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGN
IFICANT_DIGITS); | 460 properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGN
IFICANT_DIGITS); |
365 properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGN
IFICANT_DIGITS); | 461 properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGN
IFICANT_DIGITS); |
366 } | 462 } |
367 | 463 |
368 // Backwards compatibility: | |
369 // If the pattern starts with '.' or if it doesn't have '.' (and isn't s
igdigit notation), | |
370 // then minInt can be zero. Otherwise, minInt needs to be at least 1. | |
371 if ((!positive.hasDecimal && positive.minimumSignificantDigits == 0) | |
372 || (positive.hasDecimal && positive.totalIntegerDigits == 0)) { | |
373 properties.setMinimumIntegerDigits(positive.minimumIntegerDigits); | |
374 } else { | |
375 properties.setMinimumIntegerDigits(Math.max(1, positive.minimumInteger
Digits)); | |
376 } | |
377 | |
378 // If the pattern ends with a '.' then force the decimal point. | 464 // If the pattern ends with a '.' then force the decimal point. |
379 if (positive.hasDecimal && positive.maximumFractionDigits == 0) { | 465 if (positive.hasDecimal && positive.maximumFractionDigits == 0) { |
380 properties.setAlwaysShowDecimal(true); | 466 properties.setDecimalSeparatorAlwaysShown(true); |
381 } else { | 467 } else { |
382 properties.setAlwaysShowDecimal(false); | 468 properties.setDecimalSeparatorAlwaysShown(false); |
383 } | 469 } |
384 | 470 |
385 // Scientific notation settings | 471 // Scientific notation settings |
386 if (positive.exponentDigits > 0) { | 472 if (positive.exponentDigits > 0) { |
387 properties.setExponentShowPlusSign(positive.exponentShowPlusSign); | 473 properties.setExponentSignAlwaysShown(positive.exponentShowPlusSign); |
388 properties.setExponentDigits(positive.exponentDigits); | 474 properties.setMinimumExponentDigits(positive.exponentDigits); |
389 if (positive.minimumSignificantDigits == 0) { | 475 if (positive.minimumSignificantDigits == 0) { |
390 // patterns without '@' can define max integer digits, used for engi
neering notation | 476 // patterns without '@' can define max integer digits, used for engi
neering notation |
| 477 properties.setMinimumIntegerDigits(positive.minimumIntegerDigits); |
391 properties.setMaximumIntegerDigits(positive.totalIntegerDigits); | 478 properties.setMaximumIntegerDigits(positive.totalIntegerDigits); |
392 } else { | 479 } else { |
393 // patterns with '@' cannot define max integer digits | 480 // patterns with '@' cannot define max integer digits |
| 481 properties.setMinimumIntegerDigits(1); |
394 properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGE
R_DIGITS); | 482 properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGE
R_DIGITS); |
395 } | 483 } |
396 } else { | 484 } else { |
397 properties.setExponentShowPlusSign(Properties.DEFAULT_EXPONENT_SHOW_PL
US_SIGN); | 485 properties.setExponentSignAlwaysShown(Properties.DEFAULT_EXPONENT_SIGN
_ALWAYS_SHOWN); |
398 properties.setExponentDigits(Properties.DEFAULT_EXPONENT_DIGITS); | 486 properties.setMinimumExponentDigits(Properties.DEFAULT_MINIMUM_EXPONEN
T_DIGITS); |
| 487 properties.setMinimumIntegerDigits(minInt); |
399 properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGER_
DIGITS); | 488 properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGER_
DIGITS); |
400 } | 489 } |
401 | 490 |
402 // Padding settings | 491 // Padding settings |
403 if (positive.padding.length() > 0) { | 492 if (positive.padding.length() > 0) { |
404 // The width of the positive prefix and suffix templates are included
in the padding | 493 // The width of the positive prefix and suffix templates are included
in the padding |
405 int paddingWidth = | 494 int paddingWidth = |
406 positive.paddingWidth | 495 positive.paddingWidth |
407 + AffixPatternUtils.unescapedLength(positive.prefix) | 496 + AffixPatternUtils.unescapedLength(positive.prefix) |
408 + AffixPatternUtils.unescapedLength(positive.suffix); | 497 + AffixPatternUtils.unescapedLength(positive.suffix); |
409 properties.setPaddingWidth(paddingWidth); | 498 properties.setFormatWidth(paddingWidth); |
410 properties.setPaddingString(positive.padding.toString()); | 499 if (positive.padding.length() == 1) { |
| 500 properties.setPadString(positive.padding.toString()); |
| 501 } else if (positive.padding.length() == 2) { |
| 502 if (positive.padding.charAt(0) == '\'') { |
| 503 properties.setPadString("'"); |
| 504 } else { |
| 505 properties.setPadString(positive.padding.toString()); |
| 506 } |
| 507 } else { |
| 508 properties.setPadString( |
| 509 positive.padding.subSequence(1, positive.padding.length() - 1).t
oString()); |
| 510 } |
411 assert positive.paddingLocation != null; | 511 assert positive.paddingLocation != null; |
412 properties.setPaddingLocation(positive.paddingLocation); | 512 properties.setPadPosition(positive.paddingLocation); |
413 } else { | 513 } else { |
414 properties.setPaddingWidth(Properties.DEFAULT_PADDING_WIDTH); | 514 properties.setFormatWidth(Properties.DEFAULT_FORMAT_WIDTH); |
415 properties.setPaddingString(Properties.DEFAULT_PADDING_STRING); | 515 properties.setPadString(Properties.DEFAULT_PAD_STRING); |
416 properties.setPaddingLocation(Properties.DEFAULT_PADDING_LOCATION); | 516 properties.setPadPosition(Properties.DEFAULT_PAD_POSITION); |
417 } | 517 } |
418 | 518 |
419 // Set the affixes | 519 // Set the affixes |
420 // Always call the setter, even if the prefixes are empty, especially in
the case of the | 520 // Always call the setter, even if the prefixes are empty, especially in
the case of the |
421 // negative prefix pattern, to prevent default values from overriding th
e pattern. | 521 // negative prefix pattern, to prevent default values from overriding th
e pattern. |
422 properties.setPositivePrefixPattern(positive.prefix); | 522 properties.setPositivePrefixPattern(positive.prefix.toString()); |
423 properties.setPositiveSuffixPattern(positive.suffix); | 523 properties.setPositiveSuffixPattern(positive.suffix.toString()); |
424 if (negative != null) { | 524 if (negative != null) { |
425 properties.setNegativePrefixPattern(negative.prefix); | 525 properties.setNegativePrefixPattern(negative.prefix.toString()); |
426 properties.setNegativeSuffixPattern(negative.suffix); | 526 properties.setNegativeSuffixPattern(negative.suffix.toString()); |
427 } else { | 527 } else { |
428 properties.setNegativePrefixPattern(null); | 528 properties.setNegativePrefixPattern(null); |
429 properties.setNegativeSuffixPattern(null); | 529 properties.setNegativeSuffixPattern(null); |
430 } | 530 } |
431 | 531 |
432 // Set the magnitude multiplier | 532 // Set the magnitude multiplier |
433 if (positive.hasPercentSign) { | 533 if (positive.hasPercentSign) { |
434 properties.setMagnitudeMultiplier(2); | 534 properties.setMagnitudeMultiplier(2); |
435 } else if (positive.hasPerMilleSign) { | 535 } else if (positive.hasPerMilleSign) { |
436 properties.setMagnitudeMultiplier(3); | 536 properties.setMagnitudeMultiplier(3); |
437 } else { | 537 } else { |
438 properties.setMagnitudeMultiplier(Properties.DEFAULT_MAGNITUDE_MULTIPL
IER); | 538 properties.setMagnitudeMultiplier(Properties.DEFAULT_MAGNITUDE_MULTIPL
IER); |
439 } | 539 } |
440 } | 540 } |
441 } | 541 } |
442 | 542 |
443 private static class SubpatternParseResult { | 543 private static class SubpatternParseResult { |
444 int[] groupingSizes = new int[] {0, -1, -1}; | 544 int[] groupingSizes = new int[] {0, -1, -1}; |
445 int minimumIntegerDigits = 0; | 545 int minimumIntegerDigits = 0; |
446 int totalIntegerDigits = 0; | 546 int totalIntegerDigits = 0; |
447 int minimumFractionDigits = 0; | 547 int minimumFractionDigits = 0; |
448 int maximumFractionDigits = 0; | 548 int maximumFractionDigits = 0; |
449 int minimumSignificantDigits = 0; | 549 int minimumSignificantDigits = 0; |
450 int maximumSignificantDigits = 0; | 550 int maximumSignificantDigits = 0; |
451 boolean hasDecimal = false; | 551 boolean hasDecimal = false; |
452 int paddingWidth = 0; | 552 int paddingWidth = 0; |
453 PaddingLocation paddingLocation = null; | 553 PadPosition paddingLocation = null; |
454 FormatQuantity4 rounding = new FormatQuantity4(); | 554 FormatQuantity4 rounding = new FormatQuantity4(); |
455 boolean exponentShowPlusSign = false; | 555 boolean exponentShowPlusSign = false; |
456 int exponentDigits = 0; | 556 int exponentDigits = 0; |
457 boolean hasPercentSign = false; | 557 boolean hasPercentSign = false; |
458 boolean hasPerMilleSign = false; | 558 boolean hasPerMilleSign = false; |
459 boolean hasCurrencySign = false; | 559 boolean hasCurrencySign = false; |
460 | 560 |
461 StringBuilder padding = new StringBuilder(); | 561 StringBuilder padding = new StringBuilder(); |
462 StringBuilder prefix = new StringBuilder(); | 562 StringBuilder prefix = new StringBuilder(); |
463 StringBuilder suffix = new StringBuilder(); | 563 StringBuilder suffix = new StringBuilder(); |
(...skipping 18 matching lines...) Expand all Loading... |
482 } | 582 } |
483 | 583 |
484 int next() { | 584 int next() { |
485 int codePoint = peek(); | 585 int codePoint = peek(); |
486 offset += Character.charCount(codePoint); | 586 offset += Character.charCount(codePoint); |
487 return codePoint; | 587 return codePoint; |
488 } | 588 } |
489 | 589 |
490 IllegalArgumentException toParseException(String message) { | 590 IllegalArgumentException toParseException(String message) { |
491 StringBuilder sb = new StringBuilder(); | 591 StringBuilder sb = new StringBuilder(); |
492 sb.append("Unexpected character in decimal format pattern: "); | 592 sb.append("Unexpected character in decimal format pattern: '"); |
| 593 sb.append(pattern); |
| 594 sb.append("': "); |
493 sb.append(message); | 595 sb.append(message); |
494 sb.append(": "); | 596 sb.append(": "); |
495 if (peek() == -1) { | 597 if (peek() == -1) { |
496 sb.append("EOL"); | 598 sb.append("EOL"); |
497 } else { | 599 } else { |
498 sb.append("'"); | 600 sb.append("'"); |
499 sb.append(Character.toChars(peek())); | 601 sb.append(Character.toChars(peek())); |
500 sb.append("'"); | 602 sb.append("'"); |
501 } | 603 } |
502 return new IllegalArgumentException(sb.toString()); | 604 return new IllegalArgumentException(sb.toString()); |
503 } | 605 } |
504 } | 606 } |
505 | 607 |
506 static void parse(String pattern, Properties properties) { | 608 static void parse(String pattern, Properties properties, boolean ignoreRound
ing) { |
507 if (pattern.isEmpty()) return; | 609 if (pattern == null || pattern.length() == 0) { |
| 610 // Backwards compatibility requires that we reset to the default values. |
| 611 // TODO: Only overwrite the properties that "saveToProperties" normally
touches? |
| 612 properties.clear(); |
| 613 return; |
| 614 } |
| 615 |
508 // TODO: Use whitespace characters from PatternProps | 616 // TODO: Use whitespace characters from PatternProps |
| 617 // TODO: Use thread locals here. |
509 ParserState state = new ParserState(pattern); | 618 ParserState state = new ParserState(pattern); |
510 PatternParseResult result = new PatternParseResult(); | 619 PatternParseResult result = new PatternParseResult(); |
511 consumePattern(state, result); | 620 consumePattern(state, result); |
512 result.saveToProperties(properties); | 621 result.saveToProperties(properties, ignoreRounding); |
513 } | 622 } |
514 | 623 |
515 private static void consumePattern(ParserState state, PatternParseResult res
ult) { | 624 private static void consumePattern(ParserState state, PatternParseResult res
ult) { |
516 // pattern := subpattern (';' subpattern)? | 625 // pattern := subpattern (';' subpattern)? |
517 consumeSubpattern(state, result.positive); | 626 consumeSubpattern(state, result.positive); |
518 if (state.peek() == ';') { | 627 if (state.peek() == ';') { |
519 state.next(); // consume the ';' | 628 state.next(); // consume the ';' |
520 result.negative = new SubpatternParseResult(); | 629 result.negative = new SubpatternParseResult(); |
521 consumeSubpattern(state, result.negative); | 630 consumeSubpattern(state, result.negative); |
522 } | 631 } |
523 if (state.peek() != -1) { | 632 if (state.peek() != -1) { |
524 throw state.toParseException("pattern"); | 633 throw state.toParseException("pattern"); |
525 } | 634 } |
526 } | 635 } |
527 | 636 |
528 private static void consumeSubpattern(ParserState state, SubpatternParseResu
lt result) { | 637 private static void consumeSubpattern(ParserState state, SubpatternParseResu
lt result) { |
529 // subpattern := literals? number exponent? literals? | 638 // subpattern := literals? number exponent? literals? |
530 consumePadding(state, result, PaddingLocation.BEFORE_PREFIX); | 639 consumePadding(state, result, PadPosition.BEFORE_PREFIX); |
531 consumeAffix(state, result, result.prefix); | 640 consumeAffix(state, result, result.prefix); |
532 consumePadding(state, result, PaddingLocation.AFTER_PREFIX); | 641 consumePadding(state, result, PadPosition.AFTER_PREFIX); |
533 consumeFormat(state, result); | 642 consumeFormat(state, result); |
534 consumeExponent(state, result); | 643 consumeExponent(state, result); |
535 consumePadding(state, result, PaddingLocation.BEFORE_SUFFIX); | 644 consumePadding(state, result, PadPosition.BEFORE_SUFFIX); |
536 consumeAffix(state, result, result.suffix); | 645 consumeAffix(state, result, result.suffix); |
537 consumePadding(state, result, PaddingLocation.AFTER_SUFFIX); | 646 consumePadding(state, result, PadPosition.AFTER_SUFFIX); |
538 } | 647 } |
539 | 648 |
540 private static void consumePadding( | 649 private static void consumePadding( |
541 ParserState state, SubpatternParseResult result, PaddingLocation padding
Location) { | 650 ParserState state, SubpatternParseResult result, PadPosition paddingLoca
tion) { |
542 if (state.peek() != '*') { | 651 if (state.peek() != '*') { |
543 return; | 652 return; |
544 } | 653 } |
545 result.paddingLocation = paddingLocation; | 654 result.paddingLocation = paddingLocation; |
546 state.next(); // consume the '*' | 655 state.next(); // consume the '*' |
547 consumeLiteral(state, result.padding); | 656 consumeLiteral(state, result.padding); |
548 } | 657 } |
549 | 658 |
550 private static void consumeAffix( | 659 private static void consumeAffix( |
551 ParserState state, SubpatternParseResult result, StringBuilder destinati
on) { | 660 ParserState state, SubpatternParseResult result, StringBuilder destinati
on) { |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
612 if (state.peek() == '.') { | 721 if (state.peek() == '.') { |
613 state.next(); // consume the decimal point | 722 state.next(); // consume the decimal point |
614 result.hasDecimal = true; | 723 result.hasDecimal = true; |
615 result.paddingWidth += 1; | 724 result.paddingWidth += 1; |
616 consumeFractionFormat(state, result); | 725 consumeFractionFormat(state, result); |
617 } | 726 } |
618 } | 727 } |
619 | 728 |
620 private static void consumeIntegerFormat(ParserState state, SubpatternParseR
esult result) { | 729 private static void consumeIntegerFormat(ParserState state, SubpatternParseR
esult result) { |
621 boolean seenSignificantDigitMarker = false; | 730 boolean seenSignificantDigitMarker = false; |
| 731 boolean seenDigit = false; |
622 | 732 |
623 while (true) { | 733 while (true) { |
624 switch (state.peek()) { | 734 switch (state.peek()) { |
625 case ',': | 735 case ',': |
626 result.paddingWidth += 1; | 736 result.paddingWidth += 1; |
627 result.groupingSizes[2] = result.groupingSizes[1]; | 737 result.groupingSizes[2] = result.groupingSizes[1]; |
628 result.groupingSizes[1] = result.groupingSizes[0]; | 738 result.groupingSizes[1] = result.groupingSizes[0]; |
629 result.groupingSizes[0] = 0; | 739 result.groupingSizes[0] = 0; |
630 break; | 740 break; |
631 | 741 |
632 case '#': | 742 case '#': |
| 743 if (seenDigit) throw state.toParseException("# cannot follow 0 befor
e decimal point"); |
633 result.paddingWidth += 1; | 744 result.paddingWidth += 1; |
634 result.groupingSizes[0] += 1; | 745 result.groupingSizes[0] += 1; |
635 result.totalIntegerDigits += (seenSignificantDigitMarker ? 0 : 1); | 746 result.totalIntegerDigits += (seenSignificantDigitMarker ? 0 : 1); |
636 // no change to result.minimumIntegerDigits | 747 // no change to result.minimumIntegerDigits |
637 // no change to result.minimumSignificantDigits | 748 // no change to result.minimumSignificantDigits |
638 result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 :
0); | 749 result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 :
0); |
639 result.rounding.appendDigit((byte) 0, 0, true); | 750 result.rounding.appendDigit((byte) 0, 0, true); |
640 break; | 751 break; |
641 | 752 |
642 case '@': | 753 case '@': |
643 seenSignificantDigitMarker = true; | 754 seenSignificantDigitMarker = true; |
| 755 if (seenDigit) throw state.toParseException("Can't mix @ and 0 in pa
ttern"); |
644 result.paddingWidth += 1; | 756 result.paddingWidth += 1; |
645 result.groupingSizes[0] += 1; | 757 result.groupingSizes[0] += 1; |
646 result.totalIntegerDigits += 1; | 758 result.totalIntegerDigits += 1; |
647 // no change to result.minimumIntegerDigits | 759 // no change to result.minimumIntegerDigits |
648 result.minimumSignificantDigits += 1; | 760 result.minimumSignificantDigits += 1; |
649 result.maximumSignificantDigits += 1; | 761 result.maximumSignificantDigits += 1; |
650 result.rounding.appendDigit((byte) 0, 0, true); | 762 result.rounding.appendDigit((byte) 0, 0, true); |
651 break; | 763 break; |
652 | 764 |
653 case '0': | 765 case '0': |
654 case '1': | 766 case '1': |
655 case '2': | 767 case '2': |
656 case '3': | 768 case '3': |
657 case '4': | 769 case '4': |
658 case '5': | 770 case '5': |
659 case '6': | 771 case '6': |
660 case '7': | 772 case '7': |
661 case '8': | 773 case '8': |
662 case '9': | 774 case '9': |
| 775 seenDigit = true; |
| 776 if (seenSignificantDigitMarker) |
| 777 throw state.toParseException("Can't mix @ and 0 in pattern"); |
663 // TODO: Crash here if we've seen the significant digit marker? See
NumberFormatTestCases.txt | 778 // TODO: Crash here if we've seen the significant digit marker? See
NumberFormatTestCases.txt |
664 result.paddingWidth += 1; | 779 result.paddingWidth += 1; |
665 result.groupingSizes[0] += 1; | 780 result.groupingSizes[0] += 1; |
666 result.totalIntegerDigits += 1; | 781 result.totalIntegerDigits += 1; |
667 result.minimumIntegerDigits += 1; | 782 result.minimumIntegerDigits += 1; |
668 // no change to result.minimumSignificantDigits | 783 // no change to result.minimumSignificantDigits |
669 result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 :
0); | 784 result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 :
0); |
670 result.rounding.appendDigit((byte) (state.peek() - '0'), 0, true); | 785 result.rounding.appendDigit((byte) (state.peek() - '0'), 0, true); |
671 break; | 786 break; |
672 | 787 |
673 default: | 788 default: |
674 return; | 789 return; |
675 } | 790 } |
676 state.next(); // consume the symbol | 791 state.next(); // consume the symbol |
677 } | 792 } |
678 } | 793 } |
679 | 794 |
680 private static void consumeFractionFormat(ParserState state, SubpatternParse
Result result) { | 795 private static void consumeFractionFormat(ParserState state, SubpatternParse
Result result) { |
681 int zeroCounter = 0; | 796 int zeroCounter = 0; |
| 797 boolean seenHash = false; |
682 while (true) { | 798 while (true) { |
683 switch (state.peek()) { | 799 switch (state.peek()) { |
684 case '#': | 800 case '#': |
| 801 seenHash = true; |
685 result.paddingWidth += 1; | 802 result.paddingWidth += 1; |
686 // no change to result.minimumFractionDigits | 803 // no change to result.minimumFractionDigits |
687 result.maximumFractionDigits += 1; | 804 result.maximumFractionDigits += 1; |
688 zeroCounter++; | 805 zeroCounter++; |
689 break; | 806 break; |
690 | 807 |
691 case '0': | 808 case '0': |
692 case '1': | 809 case '1': |
693 case '2': | 810 case '2': |
694 case '3': | 811 case '3': |
695 case '4': | 812 case '4': |
696 case '5': | 813 case '5': |
697 case '6': | 814 case '6': |
698 case '7': | 815 case '7': |
699 case '8': | 816 case '8': |
700 case '9': | 817 case '9': |
| 818 if (seenHash) throw state.toParseException("0 cannot follow # after
decimal point"); |
701 result.paddingWidth += 1; | 819 result.paddingWidth += 1; |
702 result.minimumFractionDigits += 1; | 820 result.minimumFractionDigits += 1; |
703 result.maximumFractionDigits += 1; | 821 result.maximumFractionDigits += 1; |
704 if (state.peek() == '0') { | 822 if (state.peek() == '0') { |
705 zeroCounter++; | 823 zeroCounter++; |
706 } else { | 824 } else { |
707 result.rounding.appendDigit((byte) (state.peek() - '0'), zeroCount
er, false); | 825 result.rounding.appendDigit((byte) (state.peek() - '0'), zeroCount
er, false); |
708 zeroCounter = 0; | 826 zeroCounter = 0; |
709 } | 827 } |
710 break; | 828 break; |
(...skipping 17 matching lines...) Expand all Loading... |
728 result.paddingWidth++; | 846 result.paddingWidth++; |
729 } | 847 } |
730 while (state.peek() == '0') { | 848 while (state.peek() == '0') { |
731 state.next(); // consume the 0 | 849 state.next(); // consume the 0 |
732 result.exponentDigits += 1; | 850 result.exponentDigits += 1; |
733 result.paddingWidth++; | 851 result.paddingWidth++; |
734 } | 852 } |
735 } | 853 } |
736 } | 854 } |
737 } | 855 } |
LEFT | RIGHT |