/src/mozilla-central/intl/icu/source/i18n/number_skeletons.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // © 2018 and later: Unicode, Inc. and others. |
2 | | // License & terms of use: http://www.unicode.org/copyright.html |
3 | | |
4 | | #include "unicode/utypes.h" |
5 | | |
6 | | #if !UCONFIG_NO_FORMATTING |
7 | | |
8 | | // Allow implicit conversion from char16_t* to UnicodeString for this file: |
9 | | // Helpful in toString methods and elsewhere. |
10 | | #define UNISTR_FROM_STRING_EXPLICIT |
11 | | |
12 | | #include "number_decnum.h" |
13 | | #include "number_skeletons.h" |
14 | | #include "umutex.h" |
15 | | #include "ucln_in.h" |
16 | | #include "patternprops.h" |
17 | | #include "unicode/ucharstriebuilder.h" |
18 | | #include "number_utils.h" |
19 | | #include "number_decimalquantity.h" |
20 | | #include "unicode/numberformatter.h" |
21 | | #include "uinvchar.h" |
22 | | #include "charstr.h" |
23 | | |
24 | | using namespace icu; |
25 | | using namespace icu::number; |
26 | | using namespace icu::number::impl; |
27 | | using namespace icu::number::impl::skeleton; |
28 | | |
29 | | namespace { |
30 | | |
31 | | icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER; |
32 | | |
33 | | char16_t* kSerializedStemTrie = nullptr; |
34 | | |
35 | 0 | UBool U_CALLCONV cleanupNumberSkeletons() { |
36 | 0 | uprv_free(kSerializedStemTrie); |
37 | 0 | kSerializedStemTrie = nullptr; |
38 | 0 | gNumberSkeletonsInitOnce.reset(); |
39 | 0 | return TRUE; |
40 | 0 | } |
41 | | |
42 | 0 | void U_CALLCONV initNumberSkeletons(UErrorCode& status) { |
43 | 0 | ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons); |
44 | 0 |
|
45 | 0 | UCharsTrieBuilder b(status); |
46 | 0 | if (U_FAILURE(status)) { return; } |
47 | 0 | |
48 | 0 | // Section 1: |
49 | 0 | b.add(u"compact-short", STEM_COMPACT_SHORT, status); |
50 | 0 | b.add(u"compact-long", STEM_COMPACT_LONG, status); |
51 | 0 | b.add(u"scientific", STEM_SCIENTIFIC, status); |
52 | 0 | b.add(u"engineering", STEM_ENGINEERING, status); |
53 | 0 | b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status); |
54 | 0 | b.add(u"base-unit", STEM_BASE_UNIT, status); |
55 | 0 | b.add(u"percent", STEM_PERCENT, status); |
56 | 0 | b.add(u"permille", STEM_PERMILLE, status); |
57 | 0 | b.add(u"precision-integer", STEM_PRECISION_INTEGER, status); |
58 | 0 | b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status); |
59 | 0 | b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status); |
60 | 0 | b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status); |
61 | 0 | b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status); |
62 | 0 | b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status); |
63 | 0 | b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status); |
64 | 0 | b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status); |
65 | 0 | b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status); |
66 | 0 | b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status); |
67 | 0 | b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status); |
68 | 0 | b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status); |
69 | 0 | b.add(u"group-off", STEM_GROUP_OFF, status); |
70 | 0 | b.add(u"group-min2", STEM_GROUP_MIN2, status); |
71 | 0 | b.add(u"group-auto", STEM_GROUP_AUTO, status); |
72 | 0 | b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status); |
73 | 0 | b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status); |
74 | 0 | b.add(u"latin", STEM_LATIN, status); |
75 | 0 | b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status); |
76 | 0 | b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status); |
77 | 0 | b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status); |
78 | 0 | b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status); |
79 | 0 | b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status); |
80 | 0 | b.add(u"sign-auto", STEM_SIGN_AUTO, status); |
81 | 0 | b.add(u"sign-always", STEM_SIGN_ALWAYS, status); |
82 | 0 | b.add(u"sign-never", STEM_SIGN_NEVER, status); |
83 | 0 | b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status); |
84 | 0 | b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status); |
85 | 0 | b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status); |
86 | 0 | b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); |
87 | 0 | b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status); |
88 | 0 | b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status); |
89 | 0 | if (U_FAILURE(status)) { return; } |
90 | 0 | |
91 | 0 | // Section 2: |
92 | 0 | b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status); |
93 | 0 | b.add(u"measure-unit", STEM_MEASURE_UNIT, status); |
94 | 0 | b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status); |
95 | 0 | b.add(u"currency", STEM_CURRENCY, status); |
96 | 0 | b.add(u"integer-width", STEM_INTEGER_WIDTH, status); |
97 | 0 | b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status); |
98 | 0 | b.add(u"scale", STEM_SCALE, status); |
99 | 0 | if (U_FAILURE(status)) { return; } |
100 | 0 | |
101 | 0 | // Build the CharsTrie |
102 | 0 | // TODO: Use SLOW or FAST here? |
103 | 0 | UnicodeString result; |
104 | 0 | b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status); |
105 | 0 | if (U_FAILURE(status)) { return; } |
106 | 0 | |
107 | 0 | // Copy the result into the global constant pointer |
108 | 0 | size_t numBytes = result.length() * sizeof(char16_t); |
109 | 0 | kSerializedStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes)); |
110 | 0 | uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes); |
111 | 0 | } |
112 | | |
113 | | |
114 | 0 | inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { |
115 | 0 | for (int i = 0; i < count; i++) { |
116 | 0 | sb.append(cp); |
117 | 0 | } |
118 | 0 | } |
119 | | |
120 | | |
121 | 0 | #define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \ |
122 | 0 | { \ |
123 | 0 | if ((seen).field) { \ |
124 | 0 | (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ |
125 | 0 | return STATE_NULL; \ |
126 | 0 | } \ |
127 | 0 | (seen).field = true; \ |
128 | 0 | } |
129 | | |
130 | | |
131 | 0 | #define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \ |
132 | 0 | { \ |
133 | 0 | UErrorCode conversionStatus = U_ZERO_ERROR; \ |
134 | 0 | (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \ |
135 | 0 | if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \ |
136 | 0 | /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \ |
137 | 0 | (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ |
138 | 0 | return; \ |
139 | 0 | } else if (U_FAILURE(conversionStatus)) { \ |
140 | 0 | (status) = conversionStatus; \ |
141 | 0 | return; \ |
142 | 0 | } \ |
143 | 0 | } |
144 | | |
145 | | |
146 | | } // anonymous namespace |
147 | | |
148 | | |
149 | | Notation stem_to_object::notation(skeleton::StemEnum stem) { |
150 | | switch (stem) { |
151 | | case STEM_COMPACT_SHORT: |
152 | | return Notation::compactShort(); |
153 | | case STEM_COMPACT_LONG: |
154 | | return Notation::compactLong(); |
155 | | case STEM_SCIENTIFIC: |
156 | | return Notation::scientific(); |
157 | | case STEM_ENGINEERING: |
158 | | return Notation::engineering(); |
159 | | case STEM_NOTATION_SIMPLE: |
160 | | return Notation::simple(); |
161 | | default: |
162 | | U_ASSERT(false); |
163 | | return Notation::simple(); // return a value: silence compiler warning |
164 | | } |
165 | | } |
166 | | |
167 | | MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) { |
168 | | switch (stem) { |
169 | | case STEM_BASE_UNIT: |
170 | | // Slicing is okay |
171 | | return NoUnit::base(); // NOLINT |
172 | | case STEM_PERCENT: |
173 | | // Slicing is okay |
174 | | return NoUnit::percent(); // NOLINT |
175 | | case STEM_PERMILLE: |
176 | | // Slicing is okay |
177 | | return NoUnit::permille(); // NOLINT |
178 | | default: |
179 | | U_ASSERT(false); |
180 | | return {}; // return a value: silence compiler warning |
181 | | } |
182 | | } |
183 | | |
184 | | Precision stem_to_object::precision(skeleton::StemEnum stem) { |
185 | | switch (stem) { |
186 | | case STEM_PRECISION_INTEGER: |
187 | | return Precision::integer(); |
188 | | case STEM_PRECISION_UNLIMITED: |
189 | | return Precision::unlimited(); |
190 | | case STEM_PRECISION_CURRENCY_STANDARD: |
191 | | return Precision::currency(UCURR_USAGE_STANDARD); |
192 | | case STEM_PRECISION_CURRENCY_CASH: |
193 | | return Precision::currency(UCURR_USAGE_CASH); |
194 | | default: |
195 | | U_ASSERT(false); |
196 | | return Precision::integer(); // return a value: silence compiler warning |
197 | | } |
198 | | } |
199 | | |
200 | | UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) { |
201 | | switch (stem) { |
202 | | case STEM_ROUNDING_MODE_CEILING: |
203 | | return UNUM_ROUND_CEILING; |
204 | | case STEM_ROUNDING_MODE_FLOOR: |
205 | | return UNUM_ROUND_FLOOR; |
206 | | case STEM_ROUNDING_MODE_DOWN: |
207 | | return UNUM_ROUND_DOWN; |
208 | | case STEM_ROUNDING_MODE_UP: |
209 | | return UNUM_ROUND_UP; |
210 | | case STEM_ROUNDING_MODE_HALF_EVEN: |
211 | | return UNUM_ROUND_HALFEVEN; |
212 | | case STEM_ROUNDING_MODE_HALF_DOWN: |
213 | | return UNUM_ROUND_HALFDOWN; |
214 | | case STEM_ROUNDING_MODE_HALF_UP: |
215 | | return UNUM_ROUND_HALFUP; |
216 | | case STEM_ROUNDING_MODE_UNNECESSARY: |
217 | | return UNUM_ROUND_UNNECESSARY; |
218 | | default: |
219 | | U_ASSERT(false); |
220 | | return UNUM_ROUND_UNNECESSARY; |
221 | | } |
222 | | } |
223 | | |
224 | | UGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) { |
225 | | switch (stem) { |
226 | | case STEM_GROUP_OFF: |
227 | | return UNUM_GROUPING_OFF; |
228 | | case STEM_GROUP_MIN2: |
229 | | return UNUM_GROUPING_MIN2; |
230 | | case STEM_GROUP_AUTO: |
231 | | return UNUM_GROUPING_AUTO; |
232 | | case STEM_GROUP_ON_ALIGNED: |
233 | | return UNUM_GROUPING_ON_ALIGNED; |
234 | | case STEM_GROUP_THOUSANDS: |
235 | | return UNUM_GROUPING_THOUSANDS; |
236 | | default: |
237 | | return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT |
238 | | } |
239 | | } |
240 | | |
241 | | UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) { |
242 | | switch (stem) { |
243 | | case STEM_UNIT_WIDTH_NARROW: |
244 | | return UNUM_UNIT_WIDTH_NARROW; |
245 | | case STEM_UNIT_WIDTH_SHORT: |
246 | | return UNUM_UNIT_WIDTH_SHORT; |
247 | | case STEM_UNIT_WIDTH_FULL_NAME: |
248 | | return UNUM_UNIT_WIDTH_FULL_NAME; |
249 | | case STEM_UNIT_WIDTH_ISO_CODE: |
250 | | return UNUM_UNIT_WIDTH_ISO_CODE; |
251 | | case STEM_UNIT_WIDTH_HIDDEN: |
252 | | return UNUM_UNIT_WIDTH_HIDDEN; |
253 | | default: |
254 | | return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT |
255 | | } |
256 | | } |
257 | | |
258 | | UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) { |
259 | | switch (stem) { |
260 | | case STEM_SIGN_AUTO: |
261 | | return UNUM_SIGN_AUTO; |
262 | | case STEM_SIGN_ALWAYS: |
263 | | return UNUM_SIGN_ALWAYS; |
264 | | case STEM_SIGN_NEVER: |
265 | | return UNUM_SIGN_NEVER; |
266 | | case STEM_SIGN_ACCOUNTING: |
267 | | return UNUM_SIGN_ACCOUNTING; |
268 | | case STEM_SIGN_ACCOUNTING_ALWAYS: |
269 | | return UNUM_SIGN_ACCOUNTING_ALWAYS; |
270 | | case STEM_SIGN_EXCEPT_ZERO: |
271 | | return UNUM_SIGN_EXCEPT_ZERO; |
272 | | case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: |
273 | | return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; |
274 | | default: |
275 | | return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT |
276 | | } |
277 | | } |
278 | | |
279 | | UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) { |
280 | | switch (stem) { |
281 | | case STEM_DECIMAL_AUTO: |
282 | | return UNUM_DECIMAL_SEPARATOR_AUTO; |
283 | | case STEM_DECIMAL_ALWAYS: |
284 | | return UNUM_DECIMAL_SEPARATOR_ALWAYS; |
285 | | default: |
286 | | return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT |
287 | | } |
288 | | } |
289 | | |
290 | | |
291 | | void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) { |
292 | | switch (value) { |
293 | | case UNUM_ROUND_CEILING: |
294 | | sb.append(u"rounding-mode-ceiling", -1); |
295 | | break; |
296 | | case UNUM_ROUND_FLOOR: |
297 | | sb.append(u"rounding-mode-floor", -1); |
298 | | break; |
299 | | case UNUM_ROUND_DOWN: |
300 | | sb.append(u"rounding-mode-down", -1); |
301 | | break; |
302 | | case UNUM_ROUND_UP: |
303 | | sb.append(u"rounding-mode-up", -1); |
304 | | break; |
305 | | case UNUM_ROUND_HALFEVEN: |
306 | | sb.append(u"rounding-mode-half-even", -1); |
307 | | break; |
308 | | case UNUM_ROUND_HALFDOWN: |
309 | | sb.append(u"rounding-mode-half-down", -1); |
310 | | break; |
311 | | case UNUM_ROUND_HALFUP: |
312 | | sb.append(u"rounding-mode-half-up", -1); |
313 | | break; |
314 | | case UNUM_ROUND_UNNECESSARY: |
315 | | sb.append(u"rounding-mode-unnecessary", -1); |
316 | | break; |
317 | | default: |
318 | | U_ASSERT(false); |
319 | | } |
320 | | } |
321 | | |
322 | | void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) { |
323 | | switch (value) { |
324 | | case UNUM_GROUPING_OFF: |
325 | | sb.append(u"group-off", -1); |
326 | | break; |
327 | | case UNUM_GROUPING_MIN2: |
328 | | sb.append(u"group-min2", -1); |
329 | | break; |
330 | | case UNUM_GROUPING_AUTO: |
331 | | sb.append(u"group-auto", -1); |
332 | | break; |
333 | | case UNUM_GROUPING_ON_ALIGNED: |
334 | | sb.append(u"group-on-aligned", -1); |
335 | | break; |
336 | | case UNUM_GROUPING_THOUSANDS: |
337 | | sb.append(u"group-thousands", -1); |
338 | | break; |
339 | | default: |
340 | | U_ASSERT(false); |
341 | | } |
342 | | } |
343 | | |
344 | | void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) { |
345 | | switch (value) { |
346 | | case UNUM_UNIT_WIDTH_NARROW: |
347 | | sb.append(u"unit-width-narrow", -1); |
348 | | break; |
349 | | case UNUM_UNIT_WIDTH_SHORT: |
350 | | sb.append(u"unit-width-short", -1); |
351 | | break; |
352 | | case UNUM_UNIT_WIDTH_FULL_NAME: |
353 | | sb.append(u"unit-width-full-name", -1); |
354 | | break; |
355 | | case UNUM_UNIT_WIDTH_ISO_CODE: |
356 | | sb.append(u"unit-width-iso-code", -1); |
357 | | break; |
358 | | case UNUM_UNIT_WIDTH_HIDDEN: |
359 | | sb.append(u"unit-width-hidden", -1); |
360 | | break; |
361 | | default: |
362 | | U_ASSERT(false); |
363 | | } |
364 | | } |
365 | | |
366 | | void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) { |
367 | | switch (value) { |
368 | | case UNUM_SIGN_AUTO: |
369 | | sb.append(u"sign-auto", -1); |
370 | | break; |
371 | | case UNUM_SIGN_ALWAYS: |
372 | | sb.append(u"sign-always", -1); |
373 | | break; |
374 | | case UNUM_SIGN_NEVER: |
375 | | sb.append(u"sign-never", -1); |
376 | | break; |
377 | | case UNUM_SIGN_ACCOUNTING: |
378 | | sb.append(u"sign-accounting", -1); |
379 | | break; |
380 | | case UNUM_SIGN_ACCOUNTING_ALWAYS: |
381 | | sb.append(u"sign-accounting-always", -1); |
382 | | break; |
383 | | case UNUM_SIGN_EXCEPT_ZERO: |
384 | | sb.append(u"sign-except-zero", -1); |
385 | | break; |
386 | | case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO: |
387 | | sb.append(u"sign-accounting-except-zero", -1); |
388 | | break; |
389 | | default: |
390 | | U_ASSERT(false); |
391 | | } |
392 | | } |
393 | | |
394 | | void |
395 | | enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) { |
396 | | switch (value) { |
397 | | case UNUM_DECIMAL_SEPARATOR_AUTO: |
398 | | sb.append(u"decimal-auto", -1); |
399 | | break; |
400 | | case UNUM_DECIMAL_SEPARATOR_ALWAYS: |
401 | | sb.append(u"decimal-always", -1); |
402 | | break; |
403 | | default: |
404 | | U_ASSERT(false); |
405 | | } |
406 | | } |
407 | | |
408 | | |
409 | 0 | UnlocalizedNumberFormatter skeleton::create(const UnicodeString& skeletonString, UErrorCode& status) { |
410 | 0 | umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); |
411 | 0 | MacroProps macros = parseSkeleton(skeletonString, status); |
412 | 0 | return NumberFormatter::with().macros(macros); |
413 | 0 | } |
414 | | |
415 | 0 | UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { |
416 | 0 | umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); |
417 | 0 | UnicodeString sb; |
418 | 0 | GeneratorHelpers::generateSkeleton(macros, sb, status); |
419 | 0 | return sb; |
420 | 0 | } |
421 | | |
422 | 0 | MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status) { |
423 | 0 | if (U_FAILURE(status)) { return MacroProps(); } |
424 | 0 | |
425 | 0 | // Add a trailing whitespace to the end of the skeleton string to make code cleaner. |
426 | 0 | UnicodeString tempSkeletonString(skeletonString); |
427 | 0 | tempSkeletonString.append(u' '); |
428 | 0 |
|
429 | 0 | SeenMacroProps seen; |
430 | 0 | MacroProps macros; |
431 | 0 | StringSegment segment(tempSkeletonString, false); |
432 | 0 | UCharsTrie stemTrie(kSerializedStemTrie); |
433 | 0 | ParseState stem = STATE_NULL; |
434 | 0 | int32_t offset = 0; |
435 | 0 |
|
436 | 0 | // Primary skeleton parse loop: |
437 | 0 | while (offset < segment.length()) { |
438 | 0 | UChar32 cp = segment.codePointAt(offset); |
439 | 0 | bool isTokenSeparator = PatternProps::isWhiteSpace(cp); |
440 | 0 | bool isOptionSeparator = (cp == u'/'); |
441 | 0 |
|
442 | 0 | if (!isTokenSeparator && !isOptionSeparator) { |
443 | 0 | // Non-separator token; consume it. |
444 | 0 | offset += U16_LENGTH(cp); |
445 | 0 | if (stem == STATE_NULL) { |
446 | 0 | // We are currently consuming a stem. |
447 | 0 | // Go to the next state in the stem trie. |
448 | 0 | stemTrie.nextForCodePoint(cp); |
449 | 0 | } |
450 | 0 | continue; |
451 | 0 | } |
452 | 0 |
|
453 | 0 | // We are looking at a token or option separator. |
454 | 0 | // If the segment is nonempty, parse it and reset the segment. |
455 | 0 | // Otherwise, make sure it is a valid repeating separator. |
456 | 0 | if (offset != 0) { |
457 | 0 | segment.setLength(offset); |
458 | 0 | if (stem == STATE_NULL) { |
459 | 0 | // The first separator after the start of a token. Parse it as a stem. |
460 | 0 | stem = parseStem(segment, stemTrie, seen, macros, status); |
461 | 0 | stemTrie.reset(); |
462 | 0 | } else { |
463 | 0 | // A separator after the first separator of a token. Parse it as an option. |
464 | 0 | stem = parseOption(stem, segment, macros, status); |
465 | 0 | } |
466 | 0 | segment.resetLength(); |
467 | 0 | if (U_FAILURE(status)) { return macros; } |
468 | 0 | |
469 | 0 | // Consume the segment: |
470 | 0 | segment.adjustOffset(offset); |
471 | 0 | offset = 0; |
472 | 0 |
|
473 | 0 | } else if (stem != STATE_NULL) { |
474 | 0 | // A separator ('/' or whitespace) following an option separator ('/') |
475 | 0 | // segment.setLength(U16_LENGTH(cp)); // for error message |
476 | 0 | // throw new SkeletonSyntaxException("Unexpected separator character", segment); |
477 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
478 | 0 | return macros; |
479 | 0 |
|
480 | 0 | } else { |
481 | 0 | // Two spaces in a row; this is OK. |
482 | 0 | } |
483 | 0 |
|
484 | 0 | // Does the current stem forbid options? |
485 | 0 | if (isOptionSeparator && stem == STATE_NULL) { |
486 | 0 | // segment.setLength(U16_LENGTH(cp)); // for error message |
487 | 0 | // throw new SkeletonSyntaxException("Unexpected option separator", segment); |
488 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
489 | 0 | return macros; |
490 | 0 | } |
491 | 0 | |
492 | 0 | // Does the current stem require an option? |
493 | 0 | if (isTokenSeparator && stem != STATE_NULL) { |
494 | 0 | switch (stem) { |
495 | 0 | case STATE_INCREMENT_PRECISION: |
496 | 0 | case STATE_MEASURE_UNIT: |
497 | 0 | case STATE_PER_MEASURE_UNIT: |
498 | 0 | case STATE_CURRENCY_UNIT: |
499 | 0 | case STATE_INTEGER_WIDTH: |
500 | 0 | case STATE_NUMBERING_SYSTEM: |
501 | 0 | case STATE_SCALE: |
502 | 0 | // segment.setLength(U16_LENGTH(cp)); // for error message |
503 | 0 | // throw new SkeletonSyntaxException("Stem requires an option", segment); |
504 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
505 | 0 | return macros; |
506 | 0 | default: |
507 | 0 | break; |
508 | 0 | } |
509 | 0 | stem = STATE_NULL; |
510 | 0 | } |
511 | 0 |
|
512 | 0 | // Consume the separator: |
513 | 0 | segment.adjustOffset(U16_LENGTH(cp)); |
514 | 0 | } |
515 | 0 | U_ASSERT(stem == STATE_NULL); |
516 | 0 | return macros; |
517 | 0 | } |
518 | | |
519 | | ParseState |
520 | | skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, |
521 | 0 | MacroProps& macros, UErrorCode& status) { |
522 | 0 | // First check for "blueprint" stems, which start with a "signal char" |
523 | 0 | switch (segment.charAt(0)) { |
524 | 0 | case u'.': |
525 | 0 | CHECK_NULL(seen, precision, status); |
526 | 0 | blueprint_helpers::parseFractionStem(segment, macros, status); |
527 | 0 | return STATE_FRACTION_PRECISION; |
528 | 0 | case u'@': |
529 | 0 | CHECK_NULL(seen, precision, status); |
530 | 0 | blueprint_helpers::parseDigitsStem(segment, macros, status); |
531 | 0 | return STATE_NULL; |
532 | 0 | default: |
533 | 0 | break; |
534 | 0 | } |
535 | 0 | |
536 | 0 | // Now look at the stemsTrie, which is already be pointing at our stem. |
537 | 0 | UStringTrieResult stemResult = stemTrie.current(); |
538 | 0 |
|
539 | 0 | if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) { |
540 | 0 | // throw new SkeletonSyntaxException("Unknown stem", segment); |
541 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
542 | 0 | return STATE_NULL; |
543 | 0 | } |
544 | 0 | |
545 | 0 | auto stem = static_cast<StemEnum>(stemTrie.getValue()); |
546 | 0 | switch (stem) { |
547 | 0 |
|
548 | 0 | // Stems with meaning on their own, not requiring an option: |
549 | 0 |
|
550 | 0 | case STEM_COMPACT_SHORT: |
551 | 0 | case STEM_COMPACT_LONG: |
552 | 0 | case STEM_SCIENTIFIC: |
553 | 0 | case STEM_ENGINEERING: |
554 | 0 | case STEM_NOTATION_SIMPLE: |
555 | 0 | CHECK_NULL(seen, notation, status); |
556 | 0 | macros.notation = stem_to_object::notation(stem); |
557 | 0 | switch (stem) { |
558 | 0 | case STEM_SCIENTIFIC: |
559 | 0 | case STEM_ENGINEERING: |
560 | 0 | return STATE_SCIENTIFIC; // allows for scientific options |
561 | 0 | default: |
562 | 0 | return STATE_NULL; |
563 | 0 | } |
564 | 0 | |
565 | 0 | case STEM_BASE_UNIT: |
566 | 0 | case STEM_PERCENT: |
567 | 0 | case STEM_PERMILLE: |
568 | 0 | CHECK_NULL(seen, unit, status); |
569 | 0 | macros.unit = stem_to_object::unit(stem); |
570 | 0 | return STATE_NULL; |
571 | 0 |
|
572 | 0 | case STEM_PRECISION_INTEGER: |
573 | 0 | case STEM_PRECISION_UNLIMITED: |
574 | 0 | case STEM_PRECISION_CURRENCY_STANDARD: |
575 | 0 | case STEM_PRECISION_CURRENCY_CASH: |
576 | 0 | CHECK_NULL(seen, precision, status); |
577 | 0 | macros.precision = stem_to_object::precision(stem); |
578 | 0 | switch (stem) { |
579 | 0 | case STEM_PRECISION_INTEGER: |
580 | 0 | return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##" |
581 | 0 | default: |
582 | 0 | return STATE_NULL; |
583 | 0 | } |
584 | 0 | |
585 | 0 | case STEM_ROUNDING_MODE_CEILING: |
586 | 0 | case STEM_ROUNDING_MODE_FLOOR: |
587 | 0 | case STEM_ROUNDING_MODE_DOWN: |
588 | 0 | case STEM_ROUNDING_MODE_UP: |
589 | 0 | case STEM_ROUNDING_MODE_HALF_EVEN: |
590 | 0 | case STEM_ROUNDING_MODE_HALF_DOWN: |
591 | 0 | case STEM_ROUNDING_MODE_HALF_UP: |
592 | 0 | case STEM_ROUNDING_MODE_UNNECESSARY: |
593 | 0 | CHECK_NULL(seen, roundingMode, status); |
594 | 0 | macros.roundingMode = stem_to_object::roundingMode(stem); |
595 | 0 | return STATE_NULL; |
596 | 0 |
|
597 | 0 | case STEM_GROUP_OFF: |
598 | 0 | case STEM_GROUP_MIN2: |
599 | 0 | case STEM_GROUP_AUTO: |
600 | 0 | case STEM_GROUP_ON_ALIGNED: |
601 | 0 | case STEM_GROUP_THOUSANDS: |
602 | 0 | CHECK_NULL(seen, grouper, status); |
603 | 0 | macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem)); |
604 | 0 | return STATE_NULL; |
605 | 0 |
|
606 | 0 | case STEM_LATIN: |
607 | 0 | CHECK_NULL(seen, symbols, status); |
608 | 0 | macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status)); |
609 | 0 | return STATE_NULL; |
610 | 0 |
|
611 | 0 | case STEM_UNIT_WIDTH_NARROW: |
612 | 0 | case STEM_UNIT_WIDTH_SHORT: |
613 | 0 | case STEM_UNIT_WIDTH_FULL_NAME: |
614 | 0 | case STEM_UNIT_WIDTH_ISO_CODE: |
615 | 0 | case STEM_UNIT_WIDTH_HIDDEN: |
616 | 0 | CHECK_NULL(seen, unitWidth, status); |
617 | 0 | macros.unitWidth = stem_to_object::unitWidth(stem); |
618 | 0 | return STATE_NULL; |
619 | 0 |
|
620 | 0 | case STEM_SIGN_AUTO: |
621 | 0 | case STEM_SIGN_ALWAYS: |
622 | 0 | case STEM_SIGN_NEVER: |
623 | 0 | case STEM_SIGN_ACCOUNTING: |
624 | 0 | case STEM_SIGN_ACCOUNTING_ALWAYS: |
625 | 0 | case STEM_SIGN_EXCEPT_ZERO: |
626 | 0 | case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: |
627 | 0 | CHECK_NULL(seen, sign, status); |
628 | 0 | macros.sign = stem_to_object::signDisplay(stem); |
629 | 0 | return STATE_NULL; |
630 | 0 |
|
631 | 0 | case STEM_DECIMAL_AUTO: |
632 | 0 | case STEM_DECIMAL_ALWAYS: |
633 | 0 | CHECK_NULL(seen, decimal, status); |
634 | 0 | macros.decimal = stem_to_object::decimalSeparatorDisplay(stem); |
635 | 0 | return STATE_NULL; |
636 | 0 |
|
637 | 0 | // Stems requiring an option: |
638 | 0 |
|
639 | 0 | case STEM_PRECISION_INCREMENT: |
640 | 0 | CHECK_NULL(seen, precision, status); |
641 | 0 | return STATE_INCREMENT_PRECISION; |
642 | 0 |
|
643 | 0 | case STEM_MEASURE_UNIT: |
644 | 0 | CHECK_NULL(seen, unit, status); |
645 | 0 | return STATE_MEASURE_UNIT; |
646 | 0 |
|
647 | 0 | case STEM_PER_MEASURE_UNIT: |
648 | 0 | CHECK_NULL(seen, perUnit, status); |
649 | 0 | return STATE_PER_MEASURE_UNIT; |
650 | 0 |
|
651 | 0 | case STEM_CURRENCY: |
652 | 0 | CHECK_NULL(seen, unit, status); |
653 | 0 | return STATE_CURRENCY_UNIT; |
654 | 0 |
|
655 | 0 | case STEM_INTEGER_WIDTH: |
656 | 0 | CHECK_NULL(seen, integerWidth, status); |
657 | 0 | return STATE_INTEGER_WIDTH; |
658 | 0 |
|
659 | 0 | case STEM_NUMBERING_SYSTEM: |
660 | 0 | CHECK_NULL(seen, symbols, status); |
661 | 0 | return STATE_NUMBERING_SYSTEM; |
662 | 0 |
|
663 | 0 | case STEM_SCALE: |
664 | 0 | CHECK_NULL(seen, scale, status); |
665 | 0 | return STATE_SCALE; |
666 | 0 |
|
667 | 0 | default: |
668 | 0 | U_ASSERT(false); |
669 | 0 | return STATE_NULL; // return a value: silence compiler warning |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | | ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, |
674 | 0 | UErrorCode& status) { |
675 | 0 |
|
676 | 0 | ///// Required options: ///// |
677 | 0 |
|
678 | 0 | switch (stem) { |
679 | 0 | case STATE_CURRENCY_UNIT: |
680 | 0 | blueprint_helpers::parseCurrencyOption(segment, macros, status); |
681 | 0 | return STATE_NULL; |
682 | 0 | case STATE_MEASURE_UNIT: |
683 | 0 | blueprint_helpers::parseMeasureUnitOption(segment, macros, status); |
684 | 0 | return STATE_NULL; |
685 | 0 | case STATE_PER_MEASURE_UNIT: |
686 | 0 | blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status); |
687 | 0 | return STATE_NULL; |
688 | 0 | case STATE_INCREMENT_PRECISION: |
689 | 0 | blueprint_helpers::parseIncrementOption(segment, macros, status); |
690 | 0 | return STATE_NULL; |
691 | 0 | case STATE_INTEGER_WIDTH: |
692 | 0 | blueprint_helpers::parseIntegerWidthOption(segment, macros, status); |
693 | 0 | return STATE_NULL; |
694 | 0 | case STATE_NUMBERING_SYSTEM: |
695 | 0 | blueprint_helpers::parseNumberingSystemOption(segment, macros, status); |
696 | 0 | return STATE_NULL; |
697 | 0 | case STATE_SCALE: |
698 | 0 | blueprint_helpers::parseScaleOption(segment, macros, status); |
699 | 0 | return STATE_NULL; |
700 | 0 | default: |
701 | 0 | break; |
702 | 0 | } |
703 | 0 | |
704 | 0 | ///// Non-required options: ///// |
705 | 0 | |
706 | 0 | // Scientific options |
707 | 0 | switch (stem) { |
708 | 0 | case STATE_SCIENTIFIC: |
709 | 0 | if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) { |
710 | 0 | return STATE_SCIENTIFIC; |
711 | 0 | } |
712 | 0 | if (U_FAILURE(status)) { |
713 | 0 | return {}; |
714 | 0 | } |
715 | 0 | if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) { |
716 | 0 | return STATE_SCIENTIFIC; |
717 | 0 | } |
718 | 0 | if (U_FAILURE(status)) { |
719 | 0 | return {}; |
720 | 0 | } |
721 | 0 | break; |
722 | 0 | default: |
723 | 0 | break; |
724 | 0 | } |
725 | 0 | |
726 | 0 | // Frac-sig option |
727 | 0 | switch (stem) { |
728 | 0 | case STATE_FRACTION_PRECISION: |
729 | 0 | if (blueprint_helpers::parseFracSigOption(segment, macros, status)) { |
730 | 0 | return STATE_NULL; |
731 | 0 | } |
732 | 0 | if (U_FAILURE(status)) { |
733 | 0 | return {}; |
734 | 0 | } |
735 | 0 | break; |
736 | 0 | default: |
737 | 0 | break; |
738 | 0 | } |
739 | 0 | |
740 | 0 | // Unknown option |
741 | 0 | // throw new SkeletonSyntaxException("Invalid option", segment); |
742 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
743 | 0 | return STATE_NULL; |
744 | 0 | } |
745 | | |
746 | 0 | void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
747 | 0 | if (U_FAILURE(status)) { return; } |
748 | 0 | |
749 | 0 | // Supported options |
750 | 0 | if (GeneratorHelpers::notation(macros, sb, status)) { |
751 | 0 | sb.append(u' '); |
752 | 0 | } |
753 | 0 | if (U_FAILURE(status)) { return; } |
754 | 0 | if (GeneratorHelpers::unit(macros, sb, status)) { |
755 | 0 | sb.append(u' '); |
756 | 0 | } |
757 | 0 | if (U_FAILURE(status)) { return; } |
758 | 0 | if (GeneratorHelpers::perUnit(macros, sb, status)) { |
759 | 0 | sb.append(u' '); |
760 | 0 | } |
761 | 0 | if (U_FAILURE(status)) { return; } |
762 | 0 | if (GeneratorHelpers::precision(macros, sb, status)) { |
763 | 0 | sb.append(u' '); |
764 | 0 | } |
765 | 0 | if (U_FAILURE(status)) { return; } |
766 | 0 | if (GeneratorHelpers::roundingMode(macros, sb, status)) { |
767 | 0 | sb.append(u' '); |
768 | 0 | } |
769 | 0 | if (U_FAILURE(status)) { return; } |
770 | 0 | if (GeneratorHelpers::grouping(macros, sb, status)) { |
771 | 0 | sb.append(u' '); |
772 | 0 | } |
773 | 0 | if (U_FAILURE(status)) { return; } |
774 | 0 | if (GeneratorHelpers::integerWidth(macros, sb, status)) { |
775 | 0 | sb.append(u' '); |
776 | 0 | } |
777 | 0 | if (U_FAILURE(status)) { return; } |
778 | 0 | if (GeneratorHelpers::symbols(macros, sb, status)) { |
779 | 0 | sb.append(u' '); |
780 | 0 | } |
781 | 0 | if (U_FAILURE(status)) { return; } |
782 | 0 | if (GeneratorHelpers::unitWidth(macros, sb, status)) { |
783 | 0 | sb.append(u' '); |
784 | 0 | } |
785 | 0 | if (U_FAILURE(status)) { return; } |
786 | 0 | if (GeneratorHelpers::sign(macros, sb, status)) { |
787 | 0 | sb.append(u' '); |
788 | 0 | } |
789 | 0 | if (U_FAILURE(status)) { return; } |
790 | 0 | if (GeneratorHelpers::decimal(macros, sb, status)) { |
791 | 0 | sb.append(u' '); |
792 | 0 | } |
793 | 0 | if (U_FAILURE(status)) { return; } |
794 | 0 | if (GeneratorHelpers::scale(macros, sb, status)) { |
795 | 0 | sb.append(u' '); |
796 | 0 | } |
797 | 0 | if (U_FAILURE(status)) { return; } |
798 | 0 | |
799 | 0 | // Unsupported options |
800 | 0 | if (!macros.padder.isBogus()) { |
801 | 0 | status = U_UNSUPPORTED_ERROR; |
802 | 0 | return; |
803 | 0 | } |
804 | 0 | if (macros.affixProvider != nullptr) { |
805 | 0 | status = U_UNSUPPORTED_ERROR; |
806 | 0 | return; |
807 | 0 | } |
808 | 0 | if (macros.rules != nullptr) { |
809 | 0 | status = U_UNSUPPORTED_ERROR; |
810 | 0 | return; |
811 | 0 | } |
812 | 0 | if (macros.currencySymbols != nullptr) { |
813 | 0 | status = U_UNSUPPORTED_ERROR; |
814 | 0 | return; |
815 | 0 | } |
816 | 0 | |
817 | 0 | // Remove the trailing space |
818 | 0 | if (sb.length() > 0) { |
819 | 0 | sb.truncate(sb.length() - 1); |
820 | 0 | } |
821 | 0 | } |
822 | | |
823 | | |
824 | | bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, |
825 | 0 | UErrorCode&) { |
826 | 0 | if (segment.charAt(0) != u'+') { |
827 | 0 | return false; |
828 | 0 | } |
829 | 0 | int32_t offset = 1; |
830 | 0 | int32_t minExp = 0; |
831 | 0 | for (; offset < segment.length(); offset++) { |
832 | 0 | if (segment.charAt(offset) == u'e') { |
833 | 0 | minExp++; |
834 | 0 | } else { |
835 | 0 | break; |
836 | 0 | } |
837 | 0 | } |
838 | 0 | if (offset < segment.length()) { |
839 | 0 | return false; |
840 | 0 | } |
841 | 0 | // Use the public APIs to enforce bounds checking |
842 | 0 | macros.notation = static_cast<ScientificNotation&>(macros.notation).withMinExponentDigits(minExp); |
843 | 0 | return true; |
844 | 0 | } |
845 | | |
846 | | void |
847 | 0 | blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) { |
848 | 0 | sb.append(u'+'); |
849 | 0 | appendMultiple(sb, u'e', minExponentDigits); |
850 | 0 | } |
851 | | |
852 | | bool |
853 | 0 | blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { |
854 | 0 | // Get the sign display type out of the CharsTrie data structure. |
855 | 0 | UCharsTrie tempStemTrie(kSerializedStemTrie); |
856 | 0 | UStringTrieResult result = tempStemTrie.next( |
857 | 0 | segment.toTempUnicodeString().getBuffer(), |
858 | 0 | segment.length()); |
859 | 0 | if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) { |
860 | 0 | return false; |
861 | 0 | } |
862 | 0 | auto sign = stem_to_object::signDisplay(static_cast<StemEnum>(tempStemTrie.getValue())); |
863 | 0 | if (sign == UNUM_SIGN_COUNT) { |
864 | 0 | return false; |
865 | 0 | } |
866 | 0 | macros.notation = static_cast<ScientificNotation&>(macros.notation).withExponentSignDisplay(sign); |
867 | 0 | return true; |
868 | 0 | } |
869 | | |
870 | | void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, |
871 | 0 | UErrorCode& status) { |
872 | 0 | // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us |
873 | 0 | if (segment.length() != 3) { |
874 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
875 | 0 | return; |
876 | 0 | } |
877 | 0 | const UChar* currencyCode = segment.toTempUnicodeString().getBuffer(); |
878 | 0 | UErrorCode localStatus = U_ZERO_ERROR; |
879 | 0 | CurrencyUnit currency(currencyCode, localStatus); |
880 | 0 | if (U_FAILURE(localStatus)) { |
881 | 0 | // Not 3 ascii chars |
882 | 0 | // throw new SkeletonSyntaxException("Invalid currency", segment); |
883 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
884 | 0 | return; |
885 | 0 | } |
886 | 0 | // Slicing is OK |
887 | 0 | macros.unit = currency; // NOLINT |
888 | 0 | } |
889 | | |
890 | | void |
891 | 0 | blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) { |
892 | 0 | sb.append(currency.getISOCurrency(), -1); |
893 | 0 | } |
894 | | |
895 | | void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, |
896 | 0 | UErrorCode& status) { |
897 | 0 | const UnicodeString stemString = segment.toTempUnicodeString(); |
898 | 0 |
|
899 | 0 | // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) |
900 | 0 | // http://unicode.org/reports/tr35/#Validity_Data |
901 | 0 | int firstHyphen = 0; |
902 | 0 | while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') { |
903 | 0 | firstHyphen++; |
904 | 0 | } |
905 | 0 | if (firstHyphen == stemString.length()) { |
906 | 0 | // throw new SkeletonSyntaxException("Invalid measure unit option", segment); |
907 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
908 | 0 | return; |
909 | 0 | } |
910 | 0 | |
911 | 0 | // Need to do char <-> UChar conversion... |
912 | 0 | U_ASSERT(U_SUCCESS(status)); |
913 | 0 | CharString type; |
914 | 0 | SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status); |
915 | 0 | CharString subType; |
916 | 0 | SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status); |
917 | 0 |
|
918 | 0 | // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units. |
919 | 0 | static constexpr int32_t CAPACITY = 30; |
920 | 0 | MeasureUnit units[CAPACITY]; |
921 | 0 | UErrorCode localStatus = U_ZERO_ERROR; |
922 | 0 | int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus); |
923 | 0 | if (U_FAILURE(localStatus)) { |
924 | 0 | // More than 30 units in this type? |
925 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
926 | 0 | return; |
927 | 0 | } |
928 | 0 | for (int32_t i = 0; i < numUnits; i++) { |
929 | 0 | auto& unit = units[i]; |
930 | 0 | if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) { |
931 | 0 | macros.unit = unit; |
932 | 0 | return; |
933 | 0 | } |
934 | 0 | } |
935 | 0 |
|
936 | 0 | // throw new SkeletonSyntaxException("Unknown measure unit", segment); |
937 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
938 | 0 | } |
939 | | |
940 | | void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, |
941 | 0 | UErrorCode&) { |
942 | 0 | // Need to do char <-> UChar conversion... |
943 | 0 | sb.append(UnicodeString(measureUnit.getType(), -1, US_INV)); |
944 | 0 | sb.append(u'-'); |
945 | 0 | sb.append(UnicodeString(measureUnit.getSubtype(), -1, US_INV)); |
946 | 0 | } |
947 | | |
948 | | void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, |
949 | 0 | UErrorCode& status) { |
950 | 0 | // A little bit of a hack: safe the current unit (numerator), call the main measure unit |
951 | 0 | // parsing code, put back the numerator unit, and put the new unit into per-unit. |
952 | 0 | MeasureUnit numerator = macros.unit; |
953 | 0 | parseMeasureUnitOption(segment, macros, status); |
954 | 0 | if (U_FAILURE(status)) { return; } |
955 | 0 | macros.perUnit = macros.unit; |
956 | 0 | macros.unit = numerator; |
957 | 0 | } |
958 | | |
959 | | void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros, |
960 | 0 | UErrorCode& status) { |
961 | 0 | U_ASSERT(segment.charAt(0) == u'.'); |
962 | 0 | int32_t offset = 1; |
963 | 0 | int32_t minFrac = 0; |
964 | 0 | int32_t maxFrac; |
965 | 0 | for (; offset < segment.length(); offset++) { |
966 | 0 | if (segment.charAt(offset) == u'0') { |
967 | 0 | minFrac++; |
968 | 0 | } else { |
969 | 0 | break; |
970 | 0 | } |
971 | 0 | } |
972 | 0 | if (offset < segment.length()) { |
973 | 0 | if (segment.charAt(offset) == u'+') { |
974 | 0 | maxFrac = -1; |
975 | 0 | offset++; |
976 | 0 | } else { |
977 | 0 | maxFrac = minFrac; |
978 | 0 | for (; offset < segment.length(); offset++) { |
979 | 0 | if (segment.charAt(offset) == u'#') { |
980 | 0 | maxFrac++; |
981 | 0 | } else { |
982 | 0 | break; |
983 | 0 | } |
984 | 0 | } |
985 | 0 | } |
986 | 0 | } else { |
987 | 0 | maxFrac = minFrac; |
988 | 0 | } |
989 | 0 | if (offset < segment.length()) { |
990 | 0 | // throw new SkeletonSyntaxException("Invalid fraction stem", segment); |
991 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
992 | 0 | return; |
993 | 0 | } |
994 | 0 | // Use the public APIs to enforce bounds checking |
995 | 0 | if (maxFrac == -1) { |
996 | 0 | macros.precision = Precision::minFraction(minFrac); |
997 | 0 | } else { |
998 | 0 | macros.precision = Precision::minMaxFraction(minFrac, maxFrac); |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | | void |
1003 | 0 | blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) { |
1004 | 0 | if (minFrac == 0 && maxFrac == 0) { |
1005 | 0 | sb.append(u"precision-integer", -1); |
1006 | 0 | return; |
1007 | 0 | } |
1008 | 0 | sb.append(u'.'); |
1009 | 0 | appendMultiple(sb, u'0', minFrac); |
1010 | 0 | if (maxFrac == -1) { |
1011 | 0 | sb.append(u'+'); |
1012 | 0 | } else { |
1013 | 0 | appendMultiple(sb, u'#', maxFrac - minFrac); |
1014 | 0 | } |
1015 | 0 | } |
1016 | | |
1017 | | void |
1018 | 0 | blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { |
1019 | 0 | U_ASSERT(segment.charAt(0) == u'@'); |
1020 | 0 | int offset = 0; |
1021 | 0 | int minSig = 0; |
1022 | 0 | int maxSig; |
1023 | 0 | for (; offset < segment.length(); offset++) { |
1024 | 0 | if (segment.charAt(offset) == u'@') { |
1025 | 0 | minSig++; |
1026 | 0 | } else { |
1027 | 0 | break; |
1028 | 0 | } |
1029 | 0 | } |
1030 | 0 | if (offset < segment.length()) { |
1031 | 0 | if (segment.charAt(offset) == u'+') { |
1032 | 0 | maxSig = -1; |
1033 | 0 | offset++; |
1034 | 0 | } else { |
1035 | 0 | maxSig = minSig; |
1036 | 0 | for (; offset < segment.length(); offset++) { |
1037 | 0 | if (segment.charAt(offset) == u'#') { |
1038 | 0 | maxSig++; |
1039 | 0 | } else { |
1040 | 0 | break; |
1041 | 0 | } |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 | } else { |
1045 | 0 | maxSig = minSig; |
1046 | 0 | } |
1047 | 0 | if (offset < segment.length()) { |
1048 | 0 | // throw new SkeletonSyntaxException("Invalid significant digits stem", segment); |
1049 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1050 | 0 | return; |
1051 | 0 | } |
1052 | 0 | // Use the public APIs to enforce bounds checking |
1053 | 0 | if (maxSig == -1) { |
1054 | 0 | macros.precision = Precision::minSignificantDigits(minSig); |
1055 | 0 | } else { |
1056 | 0 | macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig); |
1057 | 0 | } |
1058 | 0 | } |
1059 | | |
1060 | | void |
1061 | 0 | blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) { |
1062 | 0 | appendMultiple(sb, u'@', minSig); |
1063 | 0 | if (maxSig == -1) { |
1064 | 0 | sb.append(u'+'); |
1065 | 0 | } else { |
1066 | 0 | appendMultiple(sb, u'#', maxSig - minSig); |
1067 | 0 | } |
1068 | 0 | } |
1069 | | |
1070 | | bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros, |
1071 | 0 | UErrorCode& status) { |
1072 | 0 | if (segment.charAt(0) != u'@') { |
1073 | 0 | return false; |
1074 | 0 | } |
1075 | 0 | int offset = 0; |
1076 | 0 | int minSig = 0; |
1077 | 0 | int maxSig; |
1078 | 0 | for (; offset < segment.length(); offset++) { |
1079 | 0 | if (segment.charAt(offset) == u'@') { |
1080 | 0 | minSig++; |
1081 | 0 | } else { |
1082 | 0 | break; |
1083 | 0 | } |
1084 | 0 | } |
1085 | 0 | // For the frac-sig option, there must be minSig or maxSig but not both. |
1086 | 0 | // Valid: @+, @@+, @@@+ |
1087 | 0 | // Valid: @#, @##, @### |
1088 | 0 | // Invalid: @, @@, @@@ |
1089 | 0 | // Invalid: @@#, @@##, @@@# |
1090 | 0 | if (offset < segment.length()) { |
1091 | 0 | if (segment.charAt(offset) == u'+') { |
1092 | 0 | maxSig = -1; |
1093 | 0 | offset++; |
1094 | 0 | } else if (minSig > 1) { |
1095 | 0 | // @@#, @@##, @@@# |
1096 | 0 | // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); |
1097 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1098 | 0 | return false; |
1099 | 0 | } else { |
1100 | 0 | maxSig = minSig; |
1101 | 0 | for (; offset < segment.length(); offset++) { |
1102 | 0 | if (segment.charAt(offset) == u'#') { |
1103 | 0 | maxSig++; |
1104 | 0 | } else { |
1105 | 0 | break; |
1106 | 0 | } |
1107 | 0 | } |
1108 | 0 | } |
1109 | 0 | } else { |
1110 | 0 | // @, @@, @@@ |
1111 | 0 | // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); |
1112 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1113 | 0 | return false; |
1114 | 0 | } |
1115 | 0 | if (offset < segment.length()) { |
1116 | 0 | // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); |
1117 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1118 | 0 | return false; |
1119 | 0 | } |
1120 | 0 | |
1121 | 0 | auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision); |
1122 | 0 | if (maxSig == -1) { |
1123 | 0 | macros.precision = oldPrecision.withMinDigits(minSig); |
1124 | 0 | } else { |
1125 | 0 | macros.precision = oldPrecision.withMaxDigits(maxSig); |
1126 | 0 | } |
1127 | 0 | return true; |
1128 | 0 | } |
1129 | | |
1130 | | void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros, |
1131 | 0 | UErrorCode& status) { |
1132 | 0 | // Need to do char <-> UChar conversion... |
1133 | 0 | U_ASSERT(U_SUCCESS(status)); |
1134 | 0 | CharString buffer; |
1135 | 0 | SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); |
1136 | 0 |
|
1137 | 0 | // Utilize DecimalQuantity/decNumber to parse this for us. |
1138 | 0 | DecimalQuantity dq; |
1139 | 0 | UErrorCode localStatus = U_ZERO_ERROR; |
1140 | 0 | dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); |
1141 | 0 | if (U_FAILURE(localStatus)) { |
1142 | 0 | // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); |
1143 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1144 | 0 | return; |
1145 | 0 | } |
1146 | 0 | double increment = dq.toDouble(); |
1147 | 0 |
|
1148 | 0 | // We also need to figure out how many digits. Do a brute force string operation. |
1149 | 0 | int decimalOffset = 0; |
1150 | 0 | while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') { |
1151 | 0 | decimalOffset++; |
1152 | 0 | } |
1153 | 0 | if (decimalOffset == segment.length()) { |
1154 | 0 | macros.precision = Precision::increment(increment); |
1155 | 0 | } else { |
1156 | 0 | int32_t fractionLength = segment.length() - decimalOffset - 1; |
1157 | 0 | macros.precision = Precision::increment(increment).withMinFraction(fractionLength); |
1158 | 0 | } |
1159 | 0 | } |
1160 | | |
1161 | | void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, |
1162 | 0 | UErrorCode&) { |
1163 | 0 | // Utilize DecimalQuantity/double_conversion to format this for us. |
1164 | 0 | DecimalQuantity dq; |
1165 | 0 | dq.setToDouble(increment); |
1166 | 0 | dq.roundToInfinity(); |
1167 | 0 | sb.append(dq.toPlainString()); |
1168 | 0 |
|
1169 | 0 | // We might need to append extra trailing zeros for min fraction... |
1170 | 0 | if (trailingZeros > 0) { |
1171 | 0 | appendMultiple(sb, u'0', trailingZeros); |
1172 | 0 | } |
1173 | 0 | } |
1174 | | |
1175 | | void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, |
1176 | 0 | UErrorCode& status) { |
1177 | 0 | int32_t offset = 0; |
1178 | 0 | int32_t minInt = 0; |
1179 | 0 | int32_t maxInt; |
1180 | 0 | if (segment.charAt(0) == u'+') { |
1181 | 0 | maxInt = -1; |
1182 | 0 | offset++; |
1183 | 0 | } else { |
1184 | 0 | maxInt = 0; |
1185 | 0 | } |
1186 | 0 | for (; offset < segment.length(); offset++) { |
1187 | 0 | if (segment.charAt(offset) == u'#') { |
1188 | 0 | maxInt++; |
1189 | 0 | } else { |
1190 | 0 | break; |
1191 | 0 | } |
1192 | 0 | } |
1193 | 0 | if (offset < segment.length()) { |
1194 | 0 | for (; offset < segment.length(); offset++) { |
1195 | 0 | if (segment.charAt(offset) == u'0') { |
1196 | 0 | minInt++; |
1197 | 0 | } else { |
1198 | 0 | break; |
1199 | 0 | } |
1200 | 0 | } |
1201 | 0 | } |
1202 | 0 | if (maxInt != -1) { |
1203 | 0 | maxInt += minInt; |
1204 | 0 | } |
1205 | 0 | if (offset < segment.length()) { |
1206 | 0 | // throw new SkeletonSyntaxException("Invalid integer width stem", segment); |
1207 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1208 | 0 | return; |
1209 | 0 | } |
1210 | 0 | // Use the public APIs to enforce bounds checking |
1211 | 0 | if (maxInt == -1) { |
1212 | 0 | macros.integerWidth = IntegerWidth::zeroFillTo(minInt); |
1213 | 0 | } else { |
1214 | 0 | macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); |
1215 | 0 | } |
1216 | 0 | } |
1217 | | |
1218 | | void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, |
1219 | 0 | UErrorCode&) { |
1220 | 0 | if (maxInt == -1) { |
1221 | 0 | sb.append(u'+'); |
1222 | 0 | } else { |
1223 | 0 | appendMultiple(sb, u'#', maxInt - minInt); |
1224 | 0 | } |
1225 | 0 | appendMultiple(sb, u'0', minInt); |
1226 | 0 | } |
1227 | | |
1228 | | void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, |
1229 | 0 | UErrorCode& status) { |
1230 | 0 | // Need to do char <-> UChar conversion... |
1231 | 0 | U_ASSERT(U_SUCCESS(status)); |
1232 | 0 | CharString buffer; |
1233 | 0 | SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); |
1234 | 0 |
|
1235 | 0 | NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status); |
1236 | 0 | if (ns == nullptr || U_FAILURE(status)) { |
1237 | 0 | // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error |
1238 | 0 | // throw new SkeletonSyntaxException("Unknown numbering system", segment); |
1239 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1240 | 0 | return; |
1241 | 0 | } |
1242 | 0 | macros.symbols.setTo(ns); |
1243 | 0 | } |
1244 | | |
1245 | | void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, |
1246 | 0 | UErrorCode&) { |
1247 | 0 | // Need to do char <-> UChar conversion... |
1248 | 0 | sb.append(UnicodeString(ns.getName(), -1, US_INV)); |
1249 | 0 | } |
1250 | | |
1251 | | void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros, |
1252 | 0 | UErrorCode& status) { |
1253 | 0 | // Need to do char <-> UChar conversion... |
1254 | 0 | U_ASSERT(U_SUCCESS(status)); |
1255 | 0 | CharString buffer; |
1256 | 0 | SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); |
1257 | 0 |
|
1258 | 0 | LocalPointer<DecNum> decnum(new DecNum(), status); |
1259 | 0 | if (U_FAILURE(status)) { return; } |
1260 | 0 | decnum->setTo({buffer.data(), buffer.length()}, status); |
1261 | 0 | if (U_FAILURE(status)) { |
1262 | 0 | // This is a skeleton syntax error; don't let the low-level decnum error bubble up |
1263 | 0 | status = U_NUMBER_SKELETON_SYNTAX_ERROR; |
1264 | 0 | return; |
1265 | 0 | } |
1266 | 0 | |
1267 | 0 | // NOTE: The constructor will optimize the decnum for us if possible. |
1268 | 0 | macros.scale = {0, decnum.orphan()}; |
1269 | 0 | } |
1270 | | |
1271 | | void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, |
1272 | 0 | UErrorCode& status) { |
1273 | 0 | // Utilize DecimalQuantity/double_conversion to format this for us. |
1274 | 0 | DecimalQuantity dq; |
1275 | 0 | if (arbitrary != nullptr) { |
1276 | 0 | dq.setToDecNum(*arbitrary, status); |
1277 | 0 | if (U_FAILURE(status)) { return; } |
1278 | 0 | } else { |
1279 | 0 | dq.setToInt(1); |
1280 | 0 | } |
1281 | 0 | dq.adjustMagnitude(magnitude); |
1282 | 0 | dq.roundToInfinity(); |
1283 | 0 | sb.append(dq.toPlainString()); |
1284 | 0 | } |
1285 | | |
1286 | | |
1287 | 0 | bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1288 | 0 | if (macros.notation.fType == Notation::NTN_COMPACT) { |
1289 | 0 | UNumberCompactStyle style = macros.notation.fUnion.compactStyle; |
1290 | 0 | if (style == UNumberCompactStyle::UNUM_LONG) { |
1291 | 0 | sb.append(u"compact-long", -1); |
1292 | 0 | return true; |
1293 | 0 | } else if (style == UNumberCompactStyle::UNUM_SHORT) { |
1294 | 0 | sb.append(u"compact-short", -1); |
1295 | 0 | return true; |
1296 | 0 | } else { |
1297 | 0 | // Compact notation generated from custom data (not supported in skeleton) |
1298 | 0 | // The other compact notations are literals |
1299 | 0 | status = U_UNSUPPORTED_ERROR; |
1300 | 0 | return false; |
1301 | 0 | } |
1302 | 0 | } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { |
1303 | 0 | const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific; |
1304 | 0 | if (impl.fEngineeringInterval == 3) { |
1305 | 0 | sb.append(u"engineering", -1); |
1306 | 0 | } else { |
1307 | 0 | sb.append(u"scientific", -1); |
1308 | 0 | } |
1309 | 0 | if (impl.fMinExponentDigits > 1) { |
1310 | 0 | sb.append(u'/'); |
1311 | 0 | blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status); |
1312 | 0 | if (U_FAILURE(status)) { |
1313 | 0 | return false; |
1314 | 0 | } |
1315 | 0 | } |
1316 | 0 | if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) { |
1317 | 0 | sb.append(u'/'); |
1318 | 0 | enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb); |
1319 | 0 | } |
1320 | 0 | return true; |
1321 | 0 | } else { |
1322 | 0 | // Default value is not shown in normalized form |
1323 | 0 | return false; |
1324 | 0 | } |
1325 | 0 | } |
1326 | | |
1327 | 0 | bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1328 | 0 | if (utils::unitIsCurrency(macros.unit)) { |
1329 | 0 | sb.append(u"currency/", -1); |
1330 | 0 | CurrencyUnit currency(macros.unit, status); |
1331 | 0 | if (U_FAILURE(status)) { |
1332 | 0 | return false; |
1333 | 0 | } |
1334 | 0 | blueprint_helpers::generateCurrencyOption(currency, sb, status); |
1335 | 0 | return true; |
1336 | 0 | } else if (utils::unitIsNoUnit(macros.unit)) { |
1337 | 0 | if (utils::unitIsPercent(macros.unit)) { |
1338 | 0 | sb.append(u"percent", -1); |
1339 | 0 | return true; |
1340 | 0 | } else if (utils::unitIsPermille(macros.unit)) { |
1341 | 0 | sb.append(u"permille", -1); |
1342 | 0 | return true; |
1343 | 0 | } else { |
1344 | 0 | // Default value is not shown in normalized form |
1345 | 0 | return false; |
1346 | 0 | } |
1347 | 0 | } else { |
1348 | 0 | sb.append(u"measure-unit/", -1); |
1349 | 0 | blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status); |
1350 | 0 | return true; |
1351 | 0 | } |
1352 | 0 | } |
1353 | | |
1354 | 0 | bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1355 | 0 | // Per-units are currently expected to be only MeasureUnits. |
1356 | 0 | if (utils::unitIsNoUnit(macros.perUnit)) { |
1357 | 0 | if (utils::unitIsPercent(macros.perUnit) || utils::unitIsPermille(macros.perUnit)) { |
1358 | 0 | status = U_UNSUPPORTED_ERROR; |
1359 | 0 | return false; |
1360 | 0 | } else { |
1361 | 0 | // Default value: ok to ignore |
1362 | 0 | return false; |
1363 | 0 | } |
1364 | 0 | } else if (utils::unitIsCurrency(macros.perUnit)) { |
1365 | 0 | status = U_UNSUPPORTED_ERROR; |
1366 | 0 | return false; |
1367 | 0 | } else { |
1368 | 0 | sb.append(u"per-measure-unit/", -1); |
1369 | 0 | blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status); |
1370 | 0 | return true; |
1371 | 0 | } |
1372 | 0 | } |
1373 | | |
1374 | 0 | bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1375 | 0 | if (macros.precision.fType == Precision::RND_NONE) { |
1376 | 0 | sb.append(u"precision-unlimited", -1); |
1377 | 0 | } else if (macros.precision.fType == Precision::RND_FRACTION) { |
1378 | 0 | const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; |
1379 | 0 | blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); |
1380 | 0 | } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) { |
1381 | 0 | const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; |
1382 | 0 | blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); |
1383 | 0 | } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) { |
1384 | 0 | const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; |
1385 | 0 | blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); |
1386 | 0 | sb.append(u'/'); |
1387 | 0 | if (impl.fMinSig == -1) { |
1388 | 0 | blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status); |
1389 | 0 | } else { |
1390 | 0 | blueprint_helpers::generateDigitsStem(impl.fMinSig, -1, sb, status); |
1391 | 0 | } |
1392 | 0 | } else if (macros.precision.fType == Precision::RND_INCREMENT) { |
1393 | 0 | const Precision::IncrementSettings& impl = macros.precision.fUnion.increment; |
1394 | 0 | sb.append(u"precision-increment/", -1); |
1395 | 0 | blueprint_helpers::generateIncrementOption( |
1396 | 0 | impl.fIncrement, |
1397 | 0 | impl.fMinFrac - impl.fMaxFrac, |
1398 | 0 | sb, |
1399 | 0 | status); |
1400 | 0 | } else if (macros.precision.fType == Precision::RND_CURRENCY) { |
1401 | 0 | UCurrencyUsage usage = macros.precision.fUnion.currencyUsage; |
1402 | 0 | if (usage == UCURR_USAGE_STANDARD) { |
1403 | 0 | sb.append(u"precision-currency-standard", -1); |
1404 | 0 | } else { |
1405 | 0 | sb.append(u"precision-currency-cash", -1); |
1406 | 0 | } |
1407 | 0 | } else { |
1408 | 0 | // Bogus or Error |
1409 | 0 | return false; |
1410 | 0 | } |
1411 | 0 | |
1412 | 0 | // NOTE: Always return true for rounding because the default value depends on other options. |
1413 | 0 | return true; |
1414 | 0 | } |
1415 | | |
1416 | 0 | bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { |
1417 | 0 | if (macros.roundingMode == kDefaultMode) { |
1418 | 0 | return false; // Default |
1419 | 0 | } |
1420 | 0 | enum_to_stem_string::roundingMode(macros.roundingMode, sb); |
1421 | 0 | return true; |
1422 | 0 | } |
1423 | | |
1424 | 0 | bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1425 | 0 | if (macros.grouper.isBogus()) { |
1426 | 0 | return false; // No value |
1427 | 0 | } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { |
1428 | 0 | status = U_UNSUPPORTED_ERROR; |
1429 | 0 | return false; |
1430 | 0 | } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { |
1431 | 0 | return false; // Default value |
1432 | 0 | } else { |
1433 | 0 | enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb); |
1434 | 0 | return true; |
1435 | 0 | } |
1436 | 0 | } |
1437 | | |
1438 | 0 | bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1439 | 0 | if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() || |
1440 | 0 | macros.integerWidth == IntegerWidth::standard()) { |
1441 | 0 | // Error or Default |
1442 | 0 | return false; |
1443 | 0 | } |
1444 | 0 | sb.append(u"integer-width/", -1); |
1445 | 0 | blueprint_helpers::generateIntegerWidthOption( |
1446 | 0 | macros.integerWidth.fUnion.minMaxInt.fMinInt, |
1447 | 0 | macros.integerWidth.fUnion.minMaxInt.fMaxInt, |
1448 | 0 | sb, |
1449 | 0 | status); |
1450 | 0 | return true; |
1451 | 0 | } |
1452 | | |
1453 | 0 | bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1454 | 0 | if (macros.symbols.isNumberingSystem()) { |
1455 | 0 | const NumberingSystem& ns = *macros.symbols.getNumberingSystem(); |
1456 | 0 | if (uprv_strcmp(ns.getName(), "latn") == 0) { |
1457 | 0 | sb.append(u"latin", -1); |
1458 | 0 | } else { |
1459 | 0 | sb.append(u"numbering-system/", -1); |
1460 | 0 | blueprint_helpers::generateNumberingSystemOption(ns, sb, status); |
1461 | 0 | } |
1462 | 0 | return true; |
1463 | 0 | } else if (macros.symbols.isDecimalFormatSymbols()) { |
1464 | 0 | status = U_UNSUPPORTED_ERROR; |
1465 | 0 | return false; |
1466 | 0 | } else { |
1467 | 0 | // No custom symbols |
1468 | 0 | return false; |
1469 | 0 | } |
1470 | 0 | } |
1471 | | |
1472 | 0 | bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { |
1473 | 0 | if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) { |
1474 | 0 | return false; // Default or Bogus |
1475 | 0 | } |
1476 | 0 | enum_to_stem_string::unitWidth(macros.unitWidth, sb); |
1477 | 0 | return true; |
1478 | 0 | } |
1479 | | |
1480 | 0 | bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { |
1481 | 0 | if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) { |
1482 | 0 | return false; // Default or Bogus |
1483 | 0 | } |
1484 | 0 | enum_to_stem_string::signDisplay(macros.sign, sb); |
1485 | 0 | return true; |
1486 | 0 | } |
1487 | | |
1488 | 0 | bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { |
1489 | 0 | if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) { |
1490 | 0 | return false; // Default or Bogus |
1491 | 0 | } |
1492 | 0 | enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb); |
1493 | 0 | return true; |
1494 | 0 | } |
1495 | | |
1496 | 0 | bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { |
1497 | 0 | if (!macros.scale.isValid()) { |
1498 | 0 | return false; // Default or Bogus |
1499 | 0 | } |
1500 | 0 | sb.append(u"scale/", -1); |
1501 | 0 | blueprint_helpers::generateScaleOption( |
1502 | 0 | macros.scale.fMagnitude, |
1503 | 0 | macros.scale.fArbitrary, |
1504 | 0 | sb, |
1505 | 0 | status); |
1506 | 0 | return true; |
1507 | 0 | } |
1508 | | |
1509 | | |
1510 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |