1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
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
12 #include "number_decnum.h"
13 #include "number_skeletons.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"
23 #include "string_segment.h"
26 using namespace icu::number
;
27 using namespace icu::number::impl
;
28 using namespace icu::number::impl::skeleton
;
32 icu::UInitOnce gNumberSkeletonsInitOnce
= U_INITONCE_INITIALIZER
;
34 char16_t* kSerializedStemTrie
= nullptr;
36 UBool U_CALLCONV
cleanupNumberSkeletons() {
37 uprv_free(kSerializedStemTrie
);
38 kSerializedStemTrie
= nullptr;
39 gNumberSkeletonsInitOnce
.reset();
43 void U_CALLCONV
initNumberSkeletons(UErrorCode
& status
) {
44 ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS
, cleanupNumberSkeletons
);
46 UCharsTrieBuilder
b(status
);
47 if (U_FAILURE(status
)) { return; }
50 b
.add(u
"compact-short", STEM_COMPACT_SHORT
, status
);
51 b
.add(u
"compact-long", STEM_COMPACT_LONG
, status
);
52 b
.add(u
"scientific", STEM_SCIENTIFIC
, status
);
53 b
.add(u
"engineering", STEM_ENGINEERING
, status
);
54 b
.add(u
"notation-simple", STEM_NOTATION_SIMPLE
, status
);
55 b
.add(u
"base-unit", STEM_BASE_UNIT
, status
);
56 b
.add(u
"percent", STEM_PERCENT
, status
);
57 b
.add(u
"permille", STEM_PERMILLE
, status
);
58 b
.add(u
"precision-integer", STEM_PRECISION_INTEGER
, status
);
59 b
.add(u
"precision-unlimited", STEM_PRECISION_UNLIMITED
, status
);
60 b
.add(u
"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD
, status
);
61 b
.add(u
"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH
, status
);
62 b
.add(u
"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING
, status
);
63 b
.add(u
"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR
, status
);
64 b
.add(u
"rounding-mode-down", STEM_ROUNDING_MODE_DOWN
, status
);
65 b
.add(u
"rounding-mode-up", STEM_ROUNDING_MODE_UP
, status
);
66 b
.add(u
"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN
, status
);
67 b
.add(u
"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN
, status
);
68 b
.add(u
"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP
, status
);
69 b
.add(u
"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY
, status
);
70 b
.add(u
"group-off", STEM_GROUP_OFF
, status
);
71 b
.add(u
"group-min2", STEM_GROUP_MIN2
, status
);
72 b
.add(u
"group-auto", STEM_GROUP_AUTO
, status
);
73 b
.add(u
"group-on-aligned", STEM_GROUP_ON_ALIGNED
, status
);
74 b
.add(u
"group-thousands", STEM_GROUP_THOUSANDS
, status
);
75 b
.add(u
"latin", STEM_LATIN
, status
);
76 b
.add(u
"unit-width-narrow", STEM_UNIT_WIDTH_NARROW
, status
);
77 b
.add(u
"unit-width-short", STEM_UNIT_WIDTH_SHORT
, status
);
78 b
.add(u
"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME
, status
);
79 b
.add(u
"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE
, status
);
80 b
.add(u
"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN
, status
);
81 b
.add(u
"sign-auto", STEM_SIGN_AUTO
, status
);
82 b
.add(u
"sign-always", STEM_SIGN_ALWAYS
, status
);
83 b
.add(u
"sign-never", STEM_SIGN_NEVER
, status
);
84 b
.add(u
"sign-accounting", STEM_SIGN_ACCOUNTING
, status
);
85 b
.add(u
"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS
, status
);
86 b
.add(u
"sign-except-zero", STEM_SIGN_EXCEPT_ZERO
, status
);
87 b
.add(u
"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO
, status
);
88 b
.add(u
"decimal-auto", STEM_DECIMAL_AUTO
, status
);
89 b
.add(u
"decimal-always", STEM_DECIMAL_ALWAYS
, status
);
90 if (U_FAILURE(status
)) { return; }
93 b
.add(u
"precision-increment", STEM_PRECISION_INCREMENT
, status
);
94 b
.add(u
"measure-unit", STEM_MEASURE_UNIT
, status
);
95 b
.add(u
"per-measure-unit", STEM_PER_MEASURE_UNIT
, status
);
96 b
.add(u
"currency", STEM_CURRENCY
, status
);
97 b
.add(u
"integer-width", STEM_INTEGER_WIDTH
, status
);
98 b
.add(u
"numbering-system", STEM_NUMBERING_SYSTEM
, status
);
99 b
.add(u
"scale", STEM_SCALE
, status
);
100 if (U_FAILURE(status
)) { return; }
102 // Build the CharsTrie
103 // TODO: Use SLOW or FAST here?
104 UnicodeString result
;
105 b
.buildUnicodeString(USTRINGTRIE_BUILD_FAST
, result
, status
);
106 if (U_FAILURE(status
)) { return; }
108 // Copy the result into the global constant pointer
109 size_t numBytes
= result
.length() * sizeof(char16_t);
110 kSerializedStemTrie
= static_cast<char16_t*>(uprv_malloc(numBytes
));
111 uprv_memcpy(kSerializedStemTrie
, result
.getBuffer(), numBytes
);
115 inline void appendMultiple(UnicodeString
& sb
, UChar32 cp
, int32_t count
) {
116 for (int i
= 0; i
< count
; i
++) {
122 #define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \
123 UPRV_BLOCK_MACRO_BEGIN { \
124 if ((seen).field) { \
125 (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
128 (seen).field = true; \
129 } UPRV_BLOCK_MACRO_END
132 #define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \
133 UPRV_BLOCK_MACRO_BEGIN { \
134 UErrorCode conversionStatus = U_ZERO_ERROR; \
135 (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \
136 if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \
137 /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \
138 (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
140 } else if (U_FAILURE(conversionStatus)) { \
141 (status) = conversionStatus; \
144 } UPRV_BLOCK_MACRO_END
147 } // anonymous namespace
150 Notation
stem_to_object::notation(skeleton::StemEnum stem
) {
152 case STEM_COMPACT_SHORT
:
153 return Notation::compactShort();
154 case STEM_COMPACT_LONG
:
155 return Notation::compactLong();
156 case STEM_SCIENTIFIC
:
157 return Notation::scientific();
158 case STEM_ENGINEERING
:
159 return Notation::engineering();
160 case STEM_NOTATION_SIMPLE
:
161 return Notation::simple();
167 MeasureUnit
stem_to_object::unit(skeleton::StemEnum stem
) {
171 return NoUnit::base(); // NOLINT
174 return NoUnit::percent(); // NOLINT
177 return NoUnit::permille(); // NOLINT
183 Precision
stem_to_object::precision(skeleton::StemEnum stem
) {
185 case STEM_PRECISION_INTEGER
:
186 return Precision::integer();
187 case STEM_PRECISION_UNLIMITED
:
188 return Precision::unlimited();
189 case STEM_PRECISION_CURRENCY_STANDARD
:
190 return Precision::currency(UCURR_USAGE_STANDARD
);
191 case STEM_PRECISION_CURRENCY_CASH
:
192 return Precision::currency(UCURR_USAGE_CASH
);
198 UNumberFormatRoundingMode
stem_to_object::roundingMode(skeleton::StemEnum stem
) {
200 case STEM_ROUNDING_MODE_CEILING
:
201 return UNUM_ROUND_CEILING
;
202 case STEM_ROUNDING_MODE_FLOOR
:
203 return UNUM_ROUND_FLOOR
;
204 case STEM_ROUNDING_MODE_DOWN
:
205 return UNUM_ROUND_DOWN
;
206 case STEM_ROUNDING_MODE_UP
:
207 return UNUM_ROUND_UP
;
208 case STEM_ROUNDING_MODE_HALF_EVEN
:
209 return UNUM_ROUND_HALFEVEN
;
210 case STEM_ROUNDING_MODE_HALF_DOWN
:
211 return UNUM_ROUND_HALFDOWN
;
212 case STEM_ROUNDING_MODE_HALF_UP
:
213 return UNUM_ROUND_HALFUP
;
214 case STEM_ROUNDING_MODE_UNNECESSARY
:
215 return UNUM_ROUND_UNNECESSARY
;
221 UNumberGroupingStrategy
stem_to_object::groupingStrategy(skeleton::StemEnum stem
) {
224 return UNUM_GROUPING_OFF
;
225 case STEM_GROUP_MIN2
:
226 return UNUM_GROUPING_MIN2
;
227 case STEM_GROUP_AUTO
:
228 return UNUM_GROUPING_AUTO
;
229 case STEM_GROUP_ON_ALIGNED
:
230 return UNUM_GROUPING_ON_ALIGNED
;
231 case STEM_GROUP_THOUSANDS
:
232 return UNUM_GROUPING_THOUSANDS
;
234 return UNUM_GROUPING_COUNT
; // for objects, throw; for enums, return COUNT
238 UNumberUnitWidth
stem_to_object::unitWidth(skeleton::StemEnum stem
) {
240 case STEM_UNIT_WIDTH_NARROW
:
241 return UNUM_UNIT_WIDTH_NARROW
;
242 case STEM_UNIT_WIDTH_SHORT
:
243 return UNUM_UNIT_WIDTH_SHORT
;
244 case STEM_UNIT_WIDTH_FULL_NAME
:
245 return UNUM_UNIT_WIDTH_FULL_NAME
;
246 case STEM_UNIT_WIDTH_ISO_CODE
:
247 return UNUM_UNIT_WIDTH_ISO_CODE
;
248 case STEM_UNIT_WIDTH_HIDDEN
:
249 return UNUM_UNIT_WIDTH_HIDDEN
;
251 return UNUM_UNIT_WIDTH_COUNT
; // for objects, throw; for enums, return COUNT
255 UNumberSignDisplay
stem_to_object::signDisplay(skeleton::StemEnum stem
) {
258 return UNUM_SIGN_AUTO
;
259 case STEM_SIGN_ALWAYS
:
260 return UNUM_SIGN_ALWAYS
;
261 case STEM_SIGN_NEVER
:
262 return UNUM_SIGN_NEVER
;
263 case STEM_SIGN_ACCOUNTING
:
264 return UNUM_SIGN_ACCOUNTING
;
265 case STEM_SIGN_ACCOUNTING_ALWAYS
:
266 return UNUM_SIGN_ACCOUNTING_ALWAYS
;
267 case STEM_SIGN_EXCEPT_ZERO
:
268 return UNUM_SIGN_EXCEPT_ZERO
;
269 case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO
:
270 return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO
;
272 return UNUM_SIGN_COUNT
; // for objects, throw; for enums, return COUNT
276 UNumberDecimalSeparatorDisplay
stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem
) {
278 case STEM_DECIMAL_AUTO
:
279 return UNUM_DECIMAL_SEPARATOR_AUTO
;
280 case STEM_DECIMAL_ALWAYS
:
281 return UNUM_DECIMAL_SEPARATOR_ALWAYS
;
283 return UNUM_DECIMAL_SEPARATOR_COUNT
; // for objects, throw; for enums, return COUNT
288 void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value
, UnicodeString
& sb
) {
290 case UNUM_ROUND_CEILING
:
291 sb
.append(u
"rounding-mode-ceiling", -1);
293 case UNUM_ROUND_FLOOR
:
294 sb
.append(u
"rounding-mode-floor", -1);
296 case UNUM_ROUND_DOWN
:
297 sb
.append(u
"rounding-mode-down", -1);
300 sb
.append(u
"rounding-mode-up", -1);
302 case UNUM_ROUND_HALFEVEN
:
303 sb
.append(u
"rounding-mode-half-even", -1);
305 case UNUM_ROUND_HALFDOWN
:
306 sb
.append(u
"rounding-mode-half-down", -1);
308 case UNUM_ROUND_HALFUP
:
309 sb
.append(u
"rounding-mode-half-up", -1);
311 case UNUM_ROUND_UNNECESSARY
:
312 sb
.append(u
"rounding-mode-unnecessary", -1);
319 void enum_to_stem_string::groupingStrategy(UNumberGroupingStrategy value
, UnicodeString
& sb
) {
321 case UNUM_GROUPING_OFF
:
322 sb
.append(u
"group-off", -1);
324 case UNUM_GROUPING_MIN2
:
325 sb
.append(u
"group-min2", -1);
327 case UNUM_GROUPING_AUTO
:
328 sb
.append(u
"group-auto", -1);
330 case UNUM_GROUPING_ON_ALIGNED
:
331 sb
.append(u
"group-on-aligned", -1);
333 case UNUM_GROUPING_THOUSANDS
:
334 sb
.append(u
"group-thousands", -1);
341 void enum_to_stem_string::unitWidth(UNumberUnitWidth value
, UnicodeString
& sb
) {
343 case UNUM_UNIT_WIDTH_NARROW
:
344 sb
.append(u
"unit-width-narrow", -1);
346 case UNUM_UNIT_WIDTH_SHORT
:
347 sb
.append(u
"unit-width-short", -1);
349 case UNUM_UNIT_WIDTH_FULL_NAME
:
350 sb
.append(u
"unit-width-full-name", -1);
352 case UNUM_UNIT_WIDTH_ISO_CODE
:
353 sb
.append(u
"unit-width-iso-code", -1);
355 case UNUM_UNIT_WIDTH_HIDDEN
:
356 sb
.append(u
"unit-width-hidden", -1);
363 void enum_to_stem_string::signDisplay(UNumberSignDisplay value
, UnicodeString
& sb
) {
366 sb
.append(u
"sign-auto", -1);
368 case UNUM_SIGN_ALWAYS
:
369 sb
.append(u
"sign-always", -1);
371 case UNUM_SIGN_NEVER
:
372 sb
.append(u
"sign-never", -1);
374 case UNUM_SIGN_ACCOUNTING
:
375 sb
.append(u
"sign-accounting", -1);
377 case UNUM_SIGN_ACCOUNTING_ALWAYS
:
378 sb
.append(u
"sign-accounting-always", -1);
380 case UNUM_SIGN_EXCEPT_ZERO
:
381 sb
.append(u
"sign-except-zero", -1);
383 case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO
:
384 sb
.append(u
"sign-accounting-except-zero", -1);
392 enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value
, UnicodeString
& sb
) {
394 case UNUM_DECIMAL_SEPARATOR_AUTO
:
395 sb
.append(u
"decimal-auto", -1);
397 case UNUM_DECIMAL_SEPARATOR_ALWAYS
:
398 sb
.append(u
"decimal-always", -1);
406 UnlocalizedNumberFormatter
skeleton::create(
407 const UnicodeString
& skeletonString
, UParseError
* perror
, UErrorCode
& status
) {
410 if (perror
!= nullptr) {
413 perror
->preContext
[0] = 0;
414 perror
->postContext
[0] = 0;
417 umtx_initOnce(gNumberSkeletonsInitOnce
, &initNumberSkeletons
, status
);
418 if (U_FAILURE(status
)) {
423 MacroProps macros
= parseSkeleton(skeletonString
, errOffset
, status
);
424 if (U_SUCCESS(status
)) {
425 return NumberFormatter::with().macros(macros
);
428 if (perror
== nullptr) {
432 // Populate the UParseError with the error location
433 perror
->offset
= errOffset
;
434 int32_t contextStart
= uprv_max(0, errOffset
- U_PARSE_CONTEXT_LEN
+ 1);
435 int32_t contextEnd
= uprv_min(skeletonString
.length(), errOffset
+ U_PARSE_CONTEXT_LEN
- 1);
436 skeletonString
.extract(contextStart
, errOffset
- contextStart
, perror
->preContext
, 0);
437 perror
->preContext
[errOffset
- contextStart
] = 0;
438 skeletonString
.extract(errOffset
, contextEnd
- errOffset
, perror
->postContext
, 0);
439 perror
->postContext
[contextEnd
- errOffset
] = 0;
443 UnicodeString
skeleton::generate(const MacroProps
& macros
, UErrorCode
& status
) {
444 umtx_initOnce(gNumberSkeletonsInitOnce
, &initNumberSkeletons
, status
);
446 GeneratorHelpers::generateSkeleton(macros
, sb
, status
);
450 MacroProps
skeleton::parseSkeleton(
451 const UnicodeString
& skeletonString
, int32_t& errOffset
, UErrorCode
& status
) {
452 U_ASSERT(U_SUCCESS(status
));
454 // Add a trailing whitespace to the end of the skeleton string to make code cleaner.
455 UnicodeString
tempSkeletonString(skeletonString
);
456 tempSkeletonString
.append(u
' ');
460 StringSegment
segment(tempSkeletonString
, false);
461 UCharsTrie
stemTrie(kSerializedStemTrie
);
462 ParseState stem
= STATE_NULL
;
465 // Primary skeleton parse loop:
466 while (offset
< segment
.length()) {
467 UChar32 cp
= segment
.codePointAt(offset
);
468 bool isTokenSeparator
= PatternProps::isWhiteSpace(cp
);
469 bool isOptionSeparator
= (cp
== u
'/');
471 if (!isTokenSeparator
&& !isOptionSeparator
) {
472 // Non-separator token; consume it.
473 offset
+= U16_LENGTH(cp
);
474 if (stem
== STATE_NULL
) {
475 // We are currently consuming a stem.
476 // Go to the next state in the stem trie.
477 stemTrie
.nextForCodePoint(cp
);
482 // We are looking at a token or option separator.
483 // If the segment is nonempty, parse it and reset the segment.
484 // Otherwise, make sure it is a valid repeating separator.
486 segment
.setLength(offset
);
487 if (stem
== STATE_NULL
) {
488 // The first separator after the start of a token. Parse it as a stem.
489 stem
= parseStem(segment
, stemTrie
, seen
, macros
, status
);
492 // A separator after the first separator of a token. Parse it as an option.
493 stem
= parseOption(stem
, segment
, macros
, status
);
495 segment
.resetLength();
496 if (U_FAILURE(status
)) {
497 errOffset
= segment
.getOffset();
501 // Consume the segment:
502 segment
.adjustOffset(offset
);
505 } else if (stem
!= STATE_NULL
) {
506 // A separator ('/' or whitespace) following an option separator ('/')
507 // segment.setLength(U16_LENGTH(cp)); // for error message
508 // throw new SkeletonSyntaxException("Unexpected separator character", segment);
509 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
510 errOffset
= segment
.getOffset();
514 // Two spaces in a row; this is OK.
517 // Does the current stem forbid options?
518 if (isOptionSeparator
&& stem
== STATE_NULL
) {
519 // segment.setLength(U16_LENGTH(cp)); // for error message
520 // throw new SkeletonSyntaxException("Unexpected option separator", segment);
521 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
522 errOffset
= segment
.getOffset();
526 // Does the current stem require an option?
527 if (isTokenSeparator
&& stem
!= STATE_NULL
) {
529 case STATE_INCREMENT_PRECISION
:
530 case STATE_MEASURE_UNIT
:
531 case STATE_PER_MEASURE_UNIT
:
532 case STATE_CURRENCY_UNIT
:
533 case STATE_INTEGER_WIDTH
:
534 case STATE_NUMBERING_SYSTEM
:
536 // segment.setLength(U16_LENGTH(cp)); // for error message
537 // throw new SkeletonSyntaxException("Stem requires an option", segment);
538 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
539 errOffset
= segment
.getOffset();
547 // Consume the separator:
548 segment
.adjustOffset(U16_LENGTH(cp
));
550 U_ASSERT(stem
== STATE_NULL
);
555 skeleton::parseStem(const StringSegment
& segment
, const UCharsTrie
& stemTrie
, SeenMacroProps
& seen
,
556 MacroProps
& macros
, UErrorCode
& status
) {
557 // First check for "blueprint" stems, which start with a "signal char"
558 switch (segment
.charAt(0)) {
560 CHECK_NULL(seen
, precision
, status
);
561 blueprint_helpers::parseFractionStem(segment
, macros
, status
);
562 return STATE_FRACTION_PRECISION
;
564 CHECK_NULL(seen
, precision
, status
);
565 blueprint_helpers::parseDigitsStem(segment
, macros
, status
);
571 // Now look at the stemsTrie, which is already be pointing at our stem.
572 UStringTrieResult stemResult
= stemTrie
.current();
574 if (stemResult
!= USTRINGTRIE_INTERMEDIATE_VALUE
&& stemResult
!= USTRINGTRIE_FINAL_VALUE
) {
575 // throw new SkeletonSyntaxException("Unknown stem", segment);
576 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
580 auto stem
= static_cast<StemEnum
>(stemTrie
.getValue());
583 // Stems with meaning on their own, not requiring an option:
585 case STEM_COMPACT_SHORT
:
586 case STEM_COMPACT_LONG
:
587 case STEM_SCIENTIFIC
:
588 case STEM_ENGINEERING
:
589 case STEM_NOTATION_SIMPLE
:
590 CHECK_NULL(seen
, notation
, status
);
591 macros
.notation
= stem_to_object::notation(stem
);
593 case STEM_SCIENTIFIC
:
594 case STEM_ENGINEERING
:
595 return STATE_SCIENTIFIC
; // allows for scientific options
603 CHECK_NULL(seen
, unit
, status
);
604 macros
.unit
= stem_to_object::unit(stem
);
607 case STEM_PRECISION_INTEGER
:
608 case STEM_PRECISION_UNLIMITED
:
609 case STEM_PRECISION_CURRENCY_STANDARD
:
610 case STEM_PRECISION_CURRENCY_CASH
:
611 CHECK_NULL(seen
, precision
, status
);
612 macros
.precision
= stem_to_object::precision(stem
);
614 case STEM_PRECISION_INTEGER
:
615 return STATE_FRACTION_PRECISION
; // allows for "precision-integer/@##"
620 case STEM_ROUNDING_MODE_CEILING
:
621 case STEM_ROUNDING_MODE_FLOOR
:
622 case STEM_ROUNDING_MODE_DOWN
:
623 case STEM_ROUNDING_MODE_UP
:
624 case STEM_ROUNDING_MODE_HALF_EVEN
:
625 case STEM_ROUNDING_MODE_HALF_DOWN
:
626 case STEM_ROUNDING_MODE_HALF_UP
:
627 case STEM_ROUNDING_MODE_UNNECESSARY
:
628 CHECK_NULL(seen
, roundingMode
, status
);
629 macros
.roundingMode
= stem_to_object::roundingMode(stem
);
633 case STEM_GROUP_MIN2
:
634 case STEM_GROUP_AUTO
:
635 case STEM_GROUP_ON_ALIGNED
:
636 case STEM_GROUP_THOUSANDS
:
637 CHECK_NULL(seen
, grouper
, status
);
638 macros
.grouper
= Grouper::forStrategy(stem_to_object::groupingStrategy(stem
));
642 CHECK_NULL(seen
, symbols
, status
);
643 macros
.symbols
.setTo(NumberingSystem::createInstanceByName("latn", status
));
646 case STEM_UNIT_WIDTH_NARROW
:
647 case STEM_UNIT_WIDTH_SHORT
:
648 case STEM_UNIT_WIDTH_FULL_NAME
:
649 case STEM_UNIT_WIDTH_ISO_CODE
:
650 case STEM_UNIT_WIDTH_HIDDEN
:
651 CHECK_NULL(seen
, unitWidth
, status
);
652 macros
.unitWidth
= stem_to_object::unitWidth(stem
);
656 case STEM_SIGN_ALWAYS
:
657 case STEM_SIGN_NEVER
:
658 case STEM_SIGN_ACCOUNTING
:
659 case STEM_SIGN_ACCOUNTING_ALWAYS
:
660 case STEM_SIGN_EXCEPT_ZERO
:
661 case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO
:
662 CHECK_NULL(seen
, sign
, status
);
663 macros
.sign
= stem_to_object::signDisplay(stem
);
666 case STEM_DECIMAL_AUTO
:
667 case STEM_DECIMAL_ALWAYS
:
668 CHECK_NULL(seen
, decimal
, status
);
669 macros
.decimal
= stem_to_object::decimalSeparatorDisplay(stem
);
672 // Stems requiring an option:
674 case STEM_PRECISION_INCREMENT
:
675 CHECK_NULL(seen
, precision
, status
);
676 return STATE_INCREMENT_PRECISION
;
678 case STEM_MEASURE_UNIT
:
679 CHECK_NULL(seen
, unit
, status
);
680 return STATE_MEASURE_UNIT
;
682 case STEM_PER_MEASURE_UNIT
:
683 CHECK_NULL(seen
, perUnit
, status
);
684 return STATE_PER_MEASURE_UNIT
;
687 CHECK_NULL(seen
, unit
, status
);
688 return STATE_CURRENCY_UNIT
;
690 case STEM_INTEGER_WIDTH
:
691 CHECK_NULL(seen
, integerWidth
, status
);
692 return STATE_INTEGER_WIDTH
;
694 case STEM_NUMBERING_SYSTEM
:
695 CHECK_NULL(seen
, symbols
, status
);
696 return STATE_NUMBERING_SYSTEM
;
699 CHECK_NULL(seen
, scale
, status
);
707 ParseState
skeleton::parseOption(ParseState stem
, const StringSegment
& segment
, MacroProps
& macros
,
708 UErrorCode
& status
) {
710 ///// Required options: /////
713 case STATE_CURRENCY_UNIT
:
714 blueprint_helpers::parseCurrencyOption(segment
, macros
, status
);
716 case STATE_MEASURE_UNIT
:
717 blueprint_helpers::parseMeasureUnitOption(segment
, macros
, status
);
719 case STATE_PER_MEASURE_UNIT
:
720 blueprint_helpers::parseMeasurePerUnitOption(segment
, macros
, status
);
722 case STATE_INCREMENT_PRECISION
:
723 blueprint_helpers::parseIncrementOption(segment
, macros
, status
);
725 case STATE_INTEGER_WIDTH
:
726 blueprint_helpers::parseIntegerWidthOption(segment
, macros
, status
);
728 case STATE_NUMBERING_SYSTEM
:
729 blueprint_helpers::parseNumberingSystemOption(segment
, macros
, status
);
732 blueprint_helpers::parseScaleOption(segment
, macros
, status
);
738 ///// Non-required options: /////
740 // Scientific options
742 case STATE_SCIENTIFIC
:
743 if (blueprint_helpers::parseExponentWidthOption(segment
, macros
, status
)) {
744 return STATE_SCIENTIFIC
;
746 if (U_FAILURE(status
)) {
749 if (blueprint_helpers::parseExponentSignOption(segment
, macros
, status
)) {
750 return STATE_SCIENTIFIC
;
752 if (U_FAILURE(status
)) {
762 case STATE_FRACTION_PRECISION
:
763 if (blueprint_helpers::parseFracSigOption(segment
, macros
, status
)) {
766 if (U_FAILURE(status
)) {
775 // throw new SkeletonSyntaxException("Invalid option", segment);
776 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
780 void GeneratorHelpers::generateSkeleton(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
781 if (U_FAILURE(status
)) { return; }
784 if (GeneratorHelpers::notation(macros
, sb
, status
)) {
787 if (U_FAILURE(status
)) { return; }
788 if (GeneratorHelpers::unit(macros
, sb
, status
)) {
791 if (U_FAILURE(status
)) { return; }
792 if (GeneratorHelpers::perUnit(macros
, sb
, status
)) {
795 if (U_FAILURE(status
)) { return; }
796 if (GeneratorHelpers::precision(macros
, sb
, status
)) {
799 if (U_FAILURE(status
)) { return; }
800 if (GeneratorHelpers::roundingMode(macros
, sb
, status
)) {
803 if (U_FAILURE(status
)) { return; }
804 if (GeneratorHelpers::grouping(macros
, sb
, status
)) {
807 if (U_FAILURE(status
)) { return; }
808 if (GeneratorHelpers::integerWidth(macros
, sb
, status
)) {
811 if (U_FAILURE(status
)) { return; }
812 if (GeneratorHelpers::symbols(macros
, sb
, status
)) {
815 if (U_FAILURE(status
)) { return; }
816 if (GeneratorHelpers::unitWidth(macros
, sb
, status
)) {
819 if (U_FAILURE(status
)) { return; }
820 if (GeneratorHelpers::sign(macros
, sb
, status
)) {
823 if (U_FAILURE(status
)) { return; }
824 if (GeneratorHelpers::decimal(macros
, sb
, status
)) {
827 if (U_FAILURE(status
)) { return; }
828 if (GeneratorHelpers::scale(macros
, sb
, status
)) {
831 if (U_FAILURE(status
)) { return; }
833 // Unsupported options
834 if (!macros
.padder
.isBogus()) {
835 status
= U_UNSUPPORTED_ERROR
;
838 if (macros
.affixProvider
!= nullptr) {
839 status
= U_UNSUPPORTED_ERROR
;
842 if (macros
.rules
!= nullptr) {
843 status
= U_UNSUPPORTED_ERROR
;
846 if (macros
.currencySymbols
!= nullptr) {
847 status
= U_UNSUPPORTED_ERROR
;
851 // Remove the trailing space
852 if (sb
.length() > 0) {
853 sb
.truncate(sb
.length() - 1);
858 bool blueprint_helpers::parseExponentWidthOption(const StringSegment
& segment
, MacroProps
& macros
,
860 if (segment
.charAt(0) != u
'+') {
865 for (; offset
< segment
.length(); offset
++) {
866 if (segment
.charAt(offset
) == u
'e') {
872 if (offset
< segment
.length()) {
875 // Use the public APIs to enforce bounds checking
876 macros
.notation
= static_cast<ScientificNotation
&>(macros
.notation
).withMinExponentDigits(minExp
);
881 blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits
, UnicodeString
& sb
, UErrorCode
&) {
883 appendMultiple(sb
, u
'e', minExponentDigits
);
887 blueprint_helpers::parseExponentSignOption(const StringSegment
& segment
, MacroProps
& macros
, UErrorCode
&) {
888 // Get the sign display type out of the CharsTrie data structure.
889 UCharsTrie
tempStemTrie(kSerializedStemTrie
);
890 UStringTrieResult result
= tempStemTrie
.next(
891 segment
.toTempUnicodeString().getBuffer(),
893 if (result
!= USTRINGTRIE_INTERMEDIATE_VALUE
&& result
!= USTRINGTRIE_FINAL_VALUE
) {
896 auto sign
= stem_to_object::signDisplay(static_cast<StemEnum
>(tempStemTrie
.getValue()));
897 if (sign
== UNUM_SIGN_COUNT
) {
900 macros
.notation
= static_cast<ScientificNotation
&>(macros
.notation
).withExponentSignDisplay(sign
);
904 void blueprint_helpers::parseCurrencyOption(const StringSegment
& segment
, MacroProps
& macros
,
905 UErrorCode
& status
) {
906 // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us
907 if (segment
.length() != 3) {
908 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
911 const UChar
* currencyCode
= segment
.toTempUnicodeString().getBuffer();
912 UErrorCode localStatus
= U_ZERO_ERROR
;
913 CurrencyUnit
currency(currencyCode
, localStatus
);
914 if (U_FAILURE(localStatus
)) {
916 // throw new SkeletonSyntaxException("Invalid currency", segment);
917 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
921 macros
.unit
= currency
; // NOLINT
925 blueprint_helpers::generateCurrencyOption(const CurrencyUnit
& currency
, UnicodeString
& sb
, UErrorCode
&) {
926 sb
.append(currency
.getISOCurrency(), -1);
929 void blueprint_helpers::parseMeasureUnitOption(const StringSegment
& segment
, MacroProps
& macros
,
930 UErrorCode
& status
) {
931 const UnicodeString stemString
= segment
.toTempUnicodeString();
933 // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
934 // http://unicode.org/reports/tr35/#Validity_Data
936 while (firstHyphen
< stemString
.length() && stemString
.charAt(firstHyphen
) != '-') {
939 if (firstHyphen
== stemString
.length()) {
940 // throw new SkeletonSyntaxException("Invalid measure unit option", segment);
941 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
945 // Need to do char <-> UChar conversion...
946 U_ASSERT(U_SUCCESS(status
));
948 SKELETON_UCHAR_TO_CHAR(type
, stemString
, 0, firstHyphen
, status
);
950 SKELETON_UCHAR_TO_CHAR(subType
, stemString
, firstHyphen
+ 1, stemString
.length(), status
);
952 // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units.
953 static constexpr int32_t CAPACITY
= 30;
954 MeasureUnit units
[CAPACITY
];
955 UErrorCode localStatus
= U_ZERO_ERROR
;
956 int32_t numUnits
= MeasureUnit::getAvailable(type
.data(), units
, CAPACITY
, localStatus
);
957 if (U_FAILURE(localStatus
)) {
958 // More than 30 units in this type?
959 status
= U_INTERNAL_PROGRAM_ERROR
;
962 for (int32_t i
= 0; i
< numUnits
; i
++) {
963 auto& unit
= units
[i
];
964 if (uprv_strcmp(subType
.data(), unit
.getSubtype()) == 0) {
970 // throw new SkeletonSyntaxException("Unknown measure unit", segment);
971 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
974 void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit
& measureUnit
, UnicodeString
& sb
,
976 // Need to do char <-> UChar conversion...
977 sb
.append(UnicodeString(measureUnit
.getType(), -1, US_INV
));
979 sb
.append(UnicodeString(measureUnit
.getSubtype(), -1, US_INV
));
982 void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment
& segment
, MacroProps
& macros
,
983 UErrorCode
& status
) {
984 // A little bit of a hack: safe the current unit (numerator), call the main measure unit
985 // parsing code, put back the numerator unit, and put the new unit into per-unit.
986 MeasureUnit numerator
= macros
.unit
;
987 parseMeasureUnitOption(segment
, macros
, status
);
988 if (U_FAILURE(status
)) { return; }
989 macros
.perUnit
= macros
.unit
;
990 macros
.unit
= numerator
;
993 void blueprint_helpers::parseFractionStem(const StringSegment
& segment
, MacroProps
& macros
,
994 UErrorCode
& status
) {
995 U_ASSERT(segment
.charAt(0) == u
'.');
999 for (; offset
< segment
.length(); offset
++) {
1000 if (segment
.charAt(offset
) == u
'0') {
1006 if (offset
< segment
.length()) {
1007 if (segment
.charAt(offset
) == u
'+') {
1012 for (; offset
< segment
.length(); offset
++) {
1013 if (segment
.charAt(offset
) == u
'#') {
1023 if (offset
< segment
.length()) {
1024 // throw new SkeletonSyntaxException("Invalid fraction stem", segment);
1025 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1028 // Use the public APIs to enforce bounds checking
1029 if (maxFrac
== -1) {
1030 macros
.precision
= Precision::minFraction(minFrac
);
1032 macros
.precision
= Precision::minMaxFraction(minFrac
, maxFrac
);
1037 blueprint_helpers::generateFractionStem(int32_t minFrac
, int32_t maxFrac
, UnicodeString
& sb
, UErrorCode
&) {
1038 if (minFrac
== 0 && maxFrac
== 0) {
1039 sb
.append(u
"precision-integer", -1);
1043 appendMultiple(sb
, u
'0', minFrac
);
1044 if (maxFrac
== -1) {
1047 appendMultiple(sb
, u
'#', maxFrac
- minFrac
);
1052 blueprint_helpers::parseDigitsStem(const StringSegment
& segment
, MacroProps
& macros
, UErrorCode
& status
) {
1053 U_ASSERT(segment
.charAt(0) == u
'@');
1057 for (; offset
< segment
.length(); offset
++) {
1058 if (segment
.charAt(offset
) == u
'@') {
1064 if (offset
< segment
.length()) {
1065 if (segment
.charAt(offset
) == u
'+') {
1070 for (; offset
< segment
.length(); offset
++) {
1071 if (segment
.charAt(offset
) == u
'#') {
1081 if (offset
< segment
.length()) {
1082 // throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
1083 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1086 // Use the public APIs to enforce bounds checking
1088 macros
.precision
= Precision::minSignificantDigits(minSig
);
1090 macros
.precision
= Precision::minMaxSignificantDigits(minSig
, maxSig
);
1095 blueprint_helpers::generateDigitsStem(int32_t minSig
, int32_t maxSig
, UnicodeString
& sb
, UErrorCode
&) {
1096 appendMultiple(sb
, u
'@', minSig
);
1100 appendMultiple(sb
, u
'#', maxSig
- minSig
);
1104 bool blueprint_helpers::parseFracSigOption(const StringSegment
& segment
, MacroProps
& macros
,
1105 UErrorCode
& status
) {
1106 if (segment
.charAt(0) != u
'@') {
1112 for (; offset
< segment
.length(); offset
++) {
1113 if (segment
.charAt(offset
) == u
'@') {
1119 // For the frac-sig option, there must be minSig or maxSig but not both.
1120 // Valid: @+, @@+, @@@+
1121 // Valid: @#, @##, @###
1122 // Invalid: @, @@, @@@
1123 // Invalid: @@#, @@##, @@@#
1124 if (offset
< segment
.length()) {
1125 if (segment
.charAt(offset
) == u
'+') {
1128 } else if (minSig
> 1) {
1130 // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
1131 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1135 for (; offset
< segment
.length(); offset
++) {
1136 if (segment
.charAt(offset
) == u
'#') {
1145 // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
1146 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1149 if (offset
< segment
.length()) {
1150 // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
1151 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1155 auto& oldPrecision
= static_cast<const FractionPrecision
&>(macros
.precision
);
1157 macros
.precision
= oldPrecision
.withMinDigits(minSig
);
1159 macros
.precision
= oldPrecision
.withMaxDigits(maxSig
);
1164 void blueprint_helpers::parseIncrementOption(const StringSegment
& segment
, MacroProps
& macros
,
1165 UErrorCode
& status
) {
1166 // Need to do char <-> UChar conversion...
1167 U_ASSERT(U_SUCCESS(status
));
1169 SKELETON_UCHAR_TO_CHAR(buffer
, segment
.toTempUnicodeString(), 0, segment
.length(), status
);
1171 // Utilize DecimalQuantity/decNumber to parse this for us.
1173 UErrorCode localStatus
= U_ZERO_ERROR
;
1174 dq
.setToDecNumber({buffer
.data(), buffer
.length()}, localStatus
);
1175 if (U_FAILURE(localStatus
)) {
1176 // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
1177 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1180 double increment
= dq
.toDouble();
1182 // We also need to figure out how many digits. Do a brute force string operation.
1183 int decimalOffset
= 0;
1184 while (decimalOffset
< segment
.length() && segment
.charAt(decimalOffset
) != '.') {
1187 if (decimalOffset
== segment
.length()) {
1188 macros
.precision
= Precision::increment(increment
);
1190 int32_t fractionLength
= segment
.length() - decimalOffset
- 1;
1191 macros
.precision
= Precision::increment(increment
).withMinFraction(fractionLength
);
1195 void blueprint_helpers::generateIncrementOption(double increment
, int32_t trailingZeros
, UnicodeString
& sb
,
1197 // Utilize DecimalQuantity/double_conversion to format this for us.
1199 dq
.setToDouble(increment
);
1200 dq
.roundToInfinity();
1201 sb
.append(dq
.toPlainString());
1203 // We might need to append extra trailing zeros for min fraction...
1204 if (trailingZeros
> 0) {
1205 appendMultiple(sb
, u
'0', trailingZeros
);
1209 void blueprint_helpers::parseIntegerWidthOption(const StringSegment
& segment
, MacroProps
& macros
,
1210 UErrorCode
& status
) {
1214 if (segment
.charAt(0) == u
'+') {
1220 for (; offset
< segment
.length(); offset
++) {
1221 if (maxInt
!= -1 && segment
.charAt(offset
) == u
'#') {
1227 if (offset
< segment
.length()) {
1228 for (; offset
< segment
.length(); offset
++) {
1229 if (segment
.charAt(offset
) == u
'0') {
1239 if (offset
< segment
.length()) {
1240 // throw new SkeletonSyntaxException("Invalid integer width stem", segment);
1241 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1244 // Use the public APIs to enforce bounds checking
1246 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
);
1248 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
).truncateAt(maxInt
);
1252 void blueprint_helpers::generateIntegerWidthOption(int32_t minInt
, int32_t maxInt
, UnicodeString
& sb
,
1257 appendMultiple(sb
, u
'#', maxInt
- minInt
);
1259 appendMultiple(sb
, u
'0', minInt
);
1262 void blueprint_helpers::parseNumberingSystemOption(const StringSegment
& segment
, MacroProps
& macros
,
1263 UErrorCode
& status
) {
1264 // Need to do char <-> UChar conversion...
1265 U_ASSERT(U_SUCCESS(status
));
1267 SKELETON_UCHAR_TO_CHAR(buffer
, segment
.toTempUnicodeString(), 0, segment
.length(), status
);
1269 NumberingSystem
* ns
= NumberingSystem::createInstanceByName(buffer
.data(), status
);
1270 if (ns
== nullptr || U_FAILURE(status
)) {
1271 // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error
1272 // throw new SkeletonSyntaxException("Unknown numbering system", segment);
1273 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1276 macros
.symbols
.setTo(ns
);
1279 void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem
& ns
, UnicodeString
& sb
,
1281 // Need to do char <-> UChar conversion...
1282 sb
.append(UnicodeString(ns
.getName(), -1, US_INV
));
1285 void blueprint_helpers::parseScaleOption(const StringSegment
& segment
, MacroProps
& macros
,
1286 UErrorCode
& status
) {
1287 // Need to do char <-> UChar conversion...
1288 U_ASSERT(U_SUCCESS(status
));
1290 SKELETON_UCHAR_TO_CHAR(buffer
, segment
.toTempUnicodeString(), 0, segment
.length(), status
);
1292 LocalPointer
<DecNum
> decnum(new DecNum(), status
);
1293 if (U_FAILURE(status
)) { return; }
1294 decnum
->setTo({buffer
.data(), buffer
.length()}, status
);
1295 if (U_FAILURE(status
)) {
1296 // This is a skeleton syntax error; don't let the low-level decnum error bubble up
1297 status
= U_NUMBER_SKELETON_SYNTAX_ERROR
;
1301 // NOTE: The constructor will optimize the decnum for us if possible.
1302 macros
.scale
= {0, decnum
.orphan()};
1305 void blueprint_helpers::generateScaleOption(int32_t magnitude
, const DecNum
* arbitrary
, UnicodeString
& sb
,
1306 UErrorCode
& status
) {
1307 // Utilize DecimalQuantity/double_conversion to format this for us.
1309 if (arbitrary
!= nullptr) {
1310 dq
.setToDecNum(*arbitrary
, status
);
1311 if (U_FAILURE(status
)) { return; }
1315 dq
.adjustMagnitude(magnitude
);
1316 dq
.roundToInfinity();
1317 sb
.append(dq
.toPlainString());
1321 bool GeneratorHelpers::notation(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1322 if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
1323 UNumberCompactStyle style
= macros
.notation
.fUnion
.compactStyle
;
1324 if (style
== UNumberCompactStyle::UNUM_LONG
) {
1325 sb
.append(u
"compact-long", -1);
1327 } else if (style
== UNumberCompactStyle::UNUM_SHORT
) {
1328 sb
.append(u
"compact-short", -1);
1331 // Compact notation generated from custom data (not supported in skeleton)
1332 // The other compact notations are literals
1333 status
= U_UNSUPPORTED_ERROR
;
1336 } else if (macros
.notation
.fType
== Notation::NTN_SCIENTIFIC
) {
1337 const Notation::ScientificSettings
& impl
= macros
.notation
.fUnion
.scientific
;
1338 if (impl
.fEngineeringInterval
== 3) {
1339 sb
.append(u
"engineering", -1);
1341 sb
.append(u
"scientific", -1);
1343 if (impl
.fMinExponentDigits
> 1) {
1345 blueprint_helpers::generateExponentWidthOption(impl
.fMinExponentDigits
, sb
, status
);
1346 if (U_FAILURE(status
)) {
1350 if (impl
.fExponentSignDisplay
!= UNUM_SIGN_AUTO
) {
1352 enum_to_stem_string::signDisplay(impl
.fExponentSignDisplay
, sb
);
1356 // Default value is not shown in normalized form
1361 bool GeneratorHelpers::unit(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1362 if (utils::unitIsCurrency(macros
.unit
)) {
1363 sb
.append(u
"currency/", -1);
1364 CurrencyUnit
currency(macros
.unit
, status
);
1365 if (U_FAILURE(status
)) {
1368 blueprint_helpers::generateCurrencyOption(currency
, sb
, status
);
1370 } else if (utils::unitIsNoUnit(macros
.unit
)) {
1371 if (utils::unitIsPercent(macros
.unit
)) {
1372 sb
.append(u
"percent", -1);
1374 } else if (utils::unitIsPermille(macros
.unit
)) {
1375 sb
.append(u
"permille", -1);
1378 // Default value is not shown in normalized form
1382 sb
.append(u
"measure-unit/", -1);
1383 blueprint_helpers::generateMeasureUnitOption(macros
.unit
, sb
, status
);
1388 bool GeneratorHelpers::perUnit(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1389 // Per-units are currently expected to be only MeasureUnits.
1390 if (utils::unitIsNoUnit(macros
.perUnit
)) {
1391 if (utils::unitIsPercent(macros
.perUnit
) || utils::unitIsPermille(macros
.perUnit
)) {
1392 status
= U_UNSUPPORTED_ERROR
;
1395 // Default value: ok to ignore
1398 } else if (utils::unitIsCurrency(macros
.perUnit
)) {
1399 status
= U_UNSUPPORTED_ERROR
;
1402 sb
.append(u
"per-measure-unit/", -1);
1403 blueprint_helpers::generateMeasureUnitOption(macros
.perUnit
, sb
, status
);
1408 bool GeneratorHelpers::precision(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1409 if (macros
.precision
.fType
== Precision::RND_NONE
) {
1410 sb
.append(u
"precision-unlimited", -1);
1411 } else if (macros
.precision
.fType
== Precision::RND_FRACTION
) {
1412 const Precision::FractionSignificantSettings
& impl
= macros
.precision
.fUnion
.fracSig
;
1413 blueprint_helpers::generateFractionStem(impl
.fMinFrac
, impl
.fMaxFrac
, sb
, status
);
1414 } else if (macros
.precision
.fType
== Precision::RND_SIGNIFICANT
) {
1415 const Precision::FractionSignificantSettings
& impl
= macros
.precision
.fUnion
.fracSig
;
1416 blueprint_helpers::generateDigitsStem(impl
.fMinSig
, impl
.fMaxSig
, sb
, status
);
1417 } else if (macros
.precision
.fType
== Precision::RND_INCREMENT_SIGNIFICANT
) { // Apple rdar://52538227
1418 const Precision::IncrementSignificantSettings
& impl
= macros
.precision
.fUnion
.incrSig
;
1419 blueprint_helpers::generateDigitsStem(impl
.fMinSig
, impl
.fMaxSig
, sb
, status
);
1420 } else if (macros
.precision
.fType
== Precision::RND_FRACTION_SIGNIFICANT
) {
1421 const Precision::FractionSignificantSettings
& impl
= macros
.precision
.fUnion
.fracSig
;
1422 blueprint_helpers::generateFractionStem(impl
.fMinFrac
, impl
.fMaxFrac
, sb
, status
);
1424 if (impl
.fMinSig
== -1) {
1425 blueprint_helpers::generateDigitsStem(1, impl
.fMaxSig
, sb
, status
);
1427 blueprint_helpers::generateDigitsStem(impl
.fMinSig
, -1, sb
, status
);
1429 } else if (macros
.precision
.fType
== Precision::RND_INCREMENT
1430 || macros
.precision
.fType
== Precision::RND_INCREMENT_ONE
1431 || macros
.precision
.fType
== Precision::RND_INCREMENT_FIVE
) {
1432 const Precision::IncrementSettings
& impl
= macros
.precision
.fUnion
.increment
;
1433 sb
.append(u
"precision-increment/", -1);
1434 blueprint_helpers::generateIncrementOption(
1436 impl
.fMinFrac
- impl
.fMaxFrac
,
1439 } else if (macros
.precision
.fType
== Precision::RND_CURRENCY
) {
1440 UCurrencyUsage usage
= macros
.precision
.fUnion
.currencyUsage
;
1441 if (usage
== UCURR_USAGE_STANDARD
) {
1442 sb
.append(u
"precision-currency-standard", -1);
1444 sb
.append(u
"precision-currency-cash", -1);
1451 // NOTE: Always return true for rounding because the default value depends on other options.
1455 bool GeneratorHelpers::roundingMode(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
&) {
1456 if (macros
.roundingMode
== kDefaultMode
) {
1457 return false; // Default
1459 enum_to_stem_string::roundingMode(macros
.roundingMode
, sb
);
1463 bool GeneratorHelpers::grouping(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1464 if (macros
.grouper
.isBogus()) {
1465 return false; // No value
1466 } else if (macros
.grouper
.fStrategy
== UNUM_GROUPING_COUNT
) {
1467 status
= U_UNSUPPORTED_ERROR
;
1469 } else if (macros
.grouper
.fStrategy
== UNUM_GROUPING_AUTO
) {
1470 return false; // Default value
1472 enum_to_stem_string::groupingStrategy(macros
.grouper
.fStrategy
, sb
);
1477 bool GeneratorHelpers::integerWidth(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1478 if (macros
.integerWidth
.fHasError
|| macros
.integerWidth
.isBogus() ||
1479 macros
.integerWidth
== IntegerWidth::standard()) {
1483 sb
.append(u
"integer-width/", -1);
1484 blueprint_helpers::generateIntegerWidthOption(
1485 macros
.integerWidth
.fUnion
.minMaxInt
.fMinInt
,
1486 macros
.integerWidth
.fUnion
.minMaxInt
.fMaxInt
,
1492 bool GeneratorHelpers::symbols(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1493 if (macros
.symbols
.isNumberingSystem()) {
1494 const NumberingSystem
& ns
= *macros
.symbols
.getNumberingSystem();
1495 if (uprv_strcmp(ns
.getName(), "latn") == 0) {
1496 sb
.append(u
"latin", -1);
1498 sb
.append(u
"numbering-system/", -1);
1499 blueprint_helpers::generateNumberingSystemOption(ns
, sb
, status
);
1502 } else if (macros
.symbols
.isDecimalFormatSymbols()) {
1503 status
= U_UNSUPPORTED_ERROR
;
1506 // No custom symbols
1511 bool GeneratorHelpers::unitWidth(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
&) {
1512 if (macros
.unitWidth
== UNUM_UNIT_WIDTH_SHORT
|| macros
.unitWidth
== UNUM_UNIT_WIDTH_COUNT
) {
1513 return false; // Default or Bogus
1515 enum_to_stem_string::unitWidth(macros
.unitWidth
, sb
);
1519 bool GeneratorHelpers::sign(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
&) {
1520 if (macros
.sign
== UNUM_SIGN_AUTO
|| macros
.sign
== UNUM_SIGN_COUNT
) {
1521 return false; // Default or Bogus
1523 enum_to_stem_string::signDisplay(macros
.sign
, sb
);
1527 bool GeneratorHelpers::decimal(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
&) {
1528 if (macros
.decimal
== UNUM_DECIMAL_SEPARATOR_AUTO
|| macros
.decimal
== UNUM_DECIMAL_SEPARATOR_COUNT
) {
1529 return false; // Default or Bogus
1531 enum_to_stem_string::decimalSeparatorDisplay(macros
.decimal
, sb
);
1535 bool GeneratorHelpers::scale(const MacroProps
& macros
, UnicodeString
& sb
, UErrorCode
& status
) {
1536 if (!macros
.scale
.isValid()) {
1537 return false; // Default or Bogus
1539 sb
.append(u
"scale/", -1);
1540 blueprint_helpers::generateScaleOption(
1541 macros
.scale
.fMagnitude
,
1542 macros
.scale
.fArbitrary
,
1549 #endif /* #if !UCONFIG_NO_FORMATTING */