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