number_skeletons.cpp (65342B)
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_roundingutils.h" 14 #include "number_skeletons.h" 15 #include "umutex.h" 16 #include "ucln_in.h" 17 #include "patternprops.h" 18 #include "unicode/ucharstriebuilder.h" 19 #include "number_utils.h" 20 #include "number_decimalquantity.h" 21 #include "unicode/numberformatter.h" 22 #include "uinvchar.h" 23 #include "charstr.h" 24 #include "string_segment.h" 25 #include "unicode/errorcode.h" 26 #include "util.h" 27 #include "measunit_impl.h" 28 29 using namespace icu; 30 using namespace icu::number; 31 using namespace icu::number::impl; 32 using namespace icu::number::impl::skeleton; 33 34 namespace { 35 36 icu::UInitOnce gNumberSkeletonsInitOnce {}; 37 38 char16_t* kSerializedStemTrie = nullptr; 39 40 UBool U_CALLCONV cleanupNumberSkeletons() { 41 uprv_free(kSerializedStemTrie); 42 kSerializedStemTrie = nullptr; 43 gNumberSkeletonsInitOnce.reset(); 44 return true; 45 } 46 47 void U_CALLCONV initNumberSkeletons(UErrorCode& status) { 48 ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons); 49 50 UCharsTrieBuilder b(status); 51 if (U_FAILURE(status)) { return; } 52 53 // Section 1: 54 b.add(u"compact-short", STEM_COMPACT_SHORT, status); 55 b.add(u"compact-long", STEM_COMPACT_LONG, status); 56 b.add(u"scientific", STEM_SCIENTIFIC, status); 57 b.add(u"engineering", STEM_ENGINEERING, status); 58 b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status); 59 b.add(u"base-unit", STEM_BASE_UNIT, status); 60 b.add(u"percent", STEM_PERCENT, status); 61 b.add(u"permille", STEM_PERMILLE, status); 62 b.add(u"precision-integer", STEM_PRECISION_INTEGER, status); 63 b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status); 64 b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status); 65 b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status); 66 b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status); 67 b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status); 68 b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status); 69 b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status); 70 b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status); 71 b.add(u"rounding-mode-half-odd", STEM_ROUNDING_MODE_HALF_ODD, status); 72 b.add(u"rounding-mode-half-ceiling", STEM_ROUNDING_MODE_HALF_CEILING, status); 73 b.add(u"rounding-mode-half-floor", STEM_ROUNDING_MODE_HALF_FLOOR, status); 74 b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status); 75 b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status); 76 b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status); 77 b.add(u"integer-width-trunc", STEM_INTEGER_WIDTH_TRUNC, status); 78 b.add(u"group-off", STEM_GROUP_OFF, status); 79 b.add(u"group-min2", STEM_GROUP_MIN2, status); 80 b.add(u"group-auto", STEM_GROUP_AUTO, status); 81 b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status); 82 b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status); 83 b.add(u"latin", STEM_LATIN, status); 84 b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status); 85 b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status); 86 b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status); 87 b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status); 88 b.add(u"unit-width-formal", STEM_UNIT_WIDTH_FORMAL, status); 89 b.add(u"unit-width-variant", STEM_UNIT_WIDTH_VARIANT, status); 90 b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status); 91 b.add(u"sign-auto", STEM_SIGN_AUTO, status); 92 b.add(u"sign-always", STEM_SIGN_ALWAYS, status); 93 b.add(u"sign-never", STEM_SIGN_NEVER, status); 94 b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status); 95 b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status); 96 b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status); 97 b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); 98 b.add(u"sign-negative", STEM_SIGN_NEGATIVE, status); 99 b.add(u"sign-accounting-negative", STEM_SIGN_ACCOUNTING_NEGATIVE, status); 100 b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status); 101 b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status); 102 if (U_FAILURE(status)) { return; } 103 104 // Section 2: 105 b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status); 106 b.add(u"measure-unit", STEM_MEASURE_UNIT, status); 107 b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status); 108 b.add(u"unit", STEM_UNIT, status); 109 b.add(u"usage", STEM_UNIT_USAGE, status); 110 b.add(u"currency", STEM_CURRENCY, status); 111 b.add(u"integer-width", STEM_INTEGER_WIDTH, status); 112 b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status); 113 b.add(u"scale", STEM_SCALE, status); 114 if (U_FAILURE(status)) { return; } 115 116 // Section 3 (concise tokens): 117 b.add(u"K", STEM_COMPACT_SHORT, status); 118 b.add(u"KK", STEM_COMPACT_LONG, status); 119 b.add(u"%", STEM_PERCENT, status); 120 b.add(u"%x100", STEM_PERCENT_100, status); 121 b.add(u",_", STEM_GROUP_OFF, status); 122 b.add(u",?", STEM_GROUP_MIN2, status); 123 b.add(u",!", STEM_GROUP_ON_ALIGNED, status); 124 b.add(u"+!", STEM_SIGN_ALWAYS, status); 125 b.add(u"+_", STEM_SIGN_NEVER, status); 126 b.add(u"()", STEM_SIGN_ACCOUNTING, status); 127 b.add(u"()!", STEM_SIGN_ACCOUNTING_ALWAYS, status); 128 b.add(u"+?", STEM_SIGN_EXCEPT_ZERO, status); 129 b.add(u"()?", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); 130 b.add(u"+-", STEM_SIGN_NEGATIVE, status); 131 b.add(u"()-", STEM_SIGN_ACCOUNTING_NEGATIVE, status); 132 if (U_FAILURE(status)) { return; } 133 134 // Build the CharsTrie 135 // TODO: Use SLOW or FAST here? 136 UnicodeString result; 137 b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status); 138 if (U_FAILURE(status)) { return; } 139 140 // Copy the result into the global constant pointer 141 size_t numBytes = result.length() * sizeof(char16_t); 142 kSerializedStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes)); 143 uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes); 144 } 145 146 147 inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { 148 for (int i = 0; i < count; i++) { 149 sb.append(cp); 150 } 151 } 152 153 154 #define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \ 155 UPRV_BLOCK_MACRO_BEGIN { \ 156 if ((seen).field) { \ 157 (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ 158 return STATE_NULL; \ 159 } \ 160 (seen).field = true; \ 161 } UPRV_BLOCK_MACRO_END 162 163 164 } // anonymous namespace 165 166 167 Notation stem_to_object::notation(skeleton::StemEnum stem) { 168 switch (stem) { 169 case STEM_COMPACT_SHORT: 170 return Notation::compactShort(); 171 case STEM_COMPACT_LONG: 172 return Notation::compactLong(); 173 case STEM_SCIENTIFIC: 174 return Notation::scientific(); 175 case STEM_ENGINEERING: 176 return Notation::engineering(); 177 case STEM_NOTATION_SIMPLE: 178 return Notation::simple(); 179 default: 180 UPRV_UNREACHABLE_EXIT; 181 } 182 } 183 184 MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) { 185 switch (stem) { 186 case STEM_BASE_UNIT: 187 return {}; 188 case STEM_PERCENT: 189 return MeasureUnit::getPercent(); 190 case STEM_PERMILLE: 191 return MeasureUnit::getPermille(); 192 default: 193 UPRV_UNREACHABLE_EXIT; 194 } 195 } 196 197 Precision stem_to_object::precision(skeleton::StemEnum stem) { 198 switch (stem) { 199 case STEM_PRECISION_INTEGER: 200 return Precision::integer(); 201 case STEM_PRECISION_UNLIMITED: 202 return Precision::unlimited(); 203 case STEM_PRECISION_CURRENCY_STANDARD: 204 return Precision::currency(UCURR_USAGE_STANDARD); 205 case STEM_PRECISION_CURRENCY_CASH: 206 return Precision::currency(UCURR_USAGE_CASH); 207 default: 208 UPRV_UNREACHABLE_EXIT; 209 } 210 } 211 212 UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) { 213 switch (stem) { 214 case STEM_ROUNDING_MODE_CEILING: 215 return UNUM_ROUND_CEILING; 216 case STEM_ROUNDING_MODE_FLOOR: 217 return UNUM_ROUND_FLOOR; 218 case STEM_ROUNDING_MODE_DOWN: 219 return UNUM_ROUND_DOWN; 220 case STEM_ROUNDING_MODE_UP: 221 return UNUM_ROUND_UP; 222 case STEM_ROUNDING_MODE_HALF_EVEN: 223 return UNUM_ROUND_HALFEVEN; 224 case STEM_ROUNDING_MODE_HALF_ODD: 225 return UNUM_ROUND_HALF_ODD; 226 case STEM_ROUNDING_MODE_HALF_CEILING: 227 return UNUM_ROUND_HALF_CEILING; 228 case STEM_ROUNDING_MODE_HALF_FLOOR: 229 return UNUM_ROUND_HALF_FLOOR; 230 case STEM_ROUNDING_MODE_HALF_DOWN: 231 return UNUM_ROUND_HALFDOWN; 232 case STEM_ROUNDING_MODE_HALF_UP: 233 return UNUM_ROUND_HALFUP; 234 case STEM_ROUNDING_MODE_UNNECESSARY: 235 return UNUM_ROUND_UNNECESSARY; 236 default: 237 UPRV_UNREACHABLE_EXIT; 238 } 239 } 240 241 UNumberGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) { 242 switch (stem) { 243 case STEM_GROUP_OFF: 244 return UNUM_GROUPING_OFF; 245 case STEM_GROUP_MIN2: 246 return UNUM_GROUPING_MIN2; 247 case STEM_GROUP_AUTO: 248 return UNUM_GROUPING_AUTO; 249 case STEM_GROUP_ON_ALIGNED: 250 return UNUM_GROUPING_ON_ALIGNED; 251 case STEM_GROUP_THOUSANDS: 252 return UNUM_GROUPING_THOUSANDS; 253 default: 254 return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT 255 } 256 } 257 258 UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) { 259 switch (stem) { 260 case STEM_UNIT_WIDTH_NARROW: 261 return UNUM_UNIT_WIDTH_NARROW; 262 case STEM_UNIT_WIDTH_SHORT: 263 return UNUM_UNIT_WIDTH_SHORT; 264 case STEM_UNIT_WIDTH_FULL_NAME: 265 return UNUM_UNIT_WIDTH_FULL_NAME; 266 case STEM_UNIT_WIDTH_ISO_CODE: 267 return UNUM_UNIT_WIDTH_ISO_CODE; 268 case STEM_UNIT_WIDTH_FORMAL: 269 return UNUM_UNIT_WIDTH_FORMAL; 270 case STEM_UNIT_WIDTH_VARIANT: 271 return UNUM_UNIT_WIDTH_VARIANT; 272 case STEM_UNIT_WIDTH_HIDDEN: 273 return UNUM_UNIT_WIDTH_HIDDEN; 274 default: 275 return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT 276 } 277 } 278 279 UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) { 280 switch (stem) { 281 case STEM_SIGN_AUTO: 282 return UNUM_SIGN_AUTO; 283 case STEM_SIGN_ALWAYS: 284 return UNUM_SIGN_ALWAYS; 285 case STEM_SIGN_NEVER: 286 return UNUM_SIGN_NEVER; 287 case STEM_SIGN_ACCOUNTING: 288 return UNUM_SIGN_ACCOUNTING; 289 case STEM_SIGN_ACCOUNTING_ALWAYS: 290 return UNUM_SIGN_ACCOUNTING_ALWAYS; 291 case STEM_SIGN_EXCEPT_ZERO: 292 return UNUM_SIGN_EXCEPT_ZERO; 293 case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: 294 return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; 295 case STEM_SIGN_NEGATIVE: 296 return UNUM_SIGN_NEGATIVE; 297 case STEM_SIGN_ACCOUNTING_NEGATIVE: 298 return UNUM_SIGN_ACCOUNTING_NEGATIVE; 299 default: 300 return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT 301 } 302 } 303 304 UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) { 305 switch (stem) { 306 case STEM_DECIMAL_AUTO: 307 return UNUM_DECIMAL_SEPARATOR_AUTO; 308 case STEM_DECIMAL_ALWAYS: 309 return UNUM_DECIMAL_SEPARATOR_ALWAYS; 310 default: 311 return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT 312 } 313 } 314 315 316 void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) { 317 switch (value) { 318 case UNUM_ROUND_CEILING: 319 sb.append(u"rounding-mode-ceiling", -1); 320 break; 321 case UNUM_ROUND_FLOOR: 322 sb.append(u"rounding-mode-floor", -1); 323 break; 324 case UNUM_ROUND_DOWN: 325 sb.append(u"rounding-mode-down", -1); 326 break; 327 case UNUM_ROUND_UP: 328 sb.append(u"rounding-mode-up", -1); 329 break; 330 case UNUM_ROUND_HALFEVEN: 331 sb.append(u"rounding-mode-half-even", -1); 332 break; 333 case UNUM_ROUND_HALF_ODD: 334 sb.append(u"rounding-mode-half-odd", -1); 335 break; 336 case UNUM_ROUND_HALF_CEILING: 337 sb.append(u"rounding-mode-half-ceiling", -1); 338 break; 339 case UNUM_ROUND_HALF_FLOOR: 340 sb.append(u"rounding-mode-half-floor", -1); 341 break; 342 case UNUM_ROUND_HALFDOWN: 343 sb.append(u"rounding-mode-half-down", -1); 344 break; 345 case UNUM_ROUND_HALFUP: 346 sb.append(u"rounding-mode-half-up", -1); 347 break; 348 case UNUM_ROUND_UNNECESSARY: 349 sb.append(u"rounding-mode-unnecessary", -1); 350 break; 351 default: 352 UPRV_UNREACHABLE_EXIT; 353 } 354 } 355 356 void enum_to_stem_string::groupingStrategy(UNumberGroupingStrategy value, UnicodeString& sb) { 357 switch (value) { 358 case UNUM_GROUPING_OFF: 359 sb.append(u"group-off", -1); 360 break; 361 case UNUM_GROUPING_MIN2: 362 sb.append(u"group-min2", -1); 363 break; 364 case UNUM_GROUPING_AUTO: 365 sb.append(u"group-auto", -1); 366 break; 367 case UNUM_GROUPING_ON_ALIGNED: 368 sb.append(u"group-on-aligned", -1); 369 break; 370 case UNUM_GROUPING_THOUSANDS: 371 sb.append(u"group-thousands", -1); 372 break; 373 default: 374 UPRV_UNREACHABLE_EXIT; 375 } 376 } 377 378 void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) { 379 switch (value) { 380 case UNUM_UNIT_WIDTH_NARROW: 381 sb.append(u"unit-width-narrow", -1); 382 break; 383 case UNUM_UNIT_WIDTH_SHORT: 384 sb.append(u"unit-width-short", -1); 385 break; 386 case UNUM_UNIT_WIDTH_FULL_NAME: 387 sb.append(u"unit-width-full-name", -1); 388 break; 389 case UNUM_UNIT_WIDTH_ISO_CODE: 390 sb.append(u"unit-width-iso-code", -1); 391 break; 392 case UNUM_UNIT_WIDTH_FORMAL: 393 sb.append(u"unit-width-formal", -1); 394 break; 395 case UNUM_UNIT_WIDTH_VARIANT: 396 sb.append(u"unit-width-variant", -1); 397 break; 398 case UNUM_UNIT_WIDTH_HIDDEN: 399 sb.append(u"unit-width-hidden", -1); 400 break; 401 default: 402 UPRV_UNREACHABLE_EXIT; 403 } 404 } 405 406 void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) { 407 switch (value) { 408 case UNUM_SIGN_AUTO: 409 sb.append(u"sign-auto", -1); 410 break; 411 case UNUM_SIGN_ALWAYS: 412 sb.append(u"sign-always", -1); 413 break; 414 case UNUM_SIGN_NEVER: 415 sb.append(u"sign-never", -1); 416 break; 417 case UNUM_SIGN_ACCOUNTING: 418 sb.append(u"sign-accounting", -1); 419 break; 420 case UNUM_SIGN_ACCOUNTING_ALWAYS: 421 sb.append(u"sign-accounting-always", -1); 422 break; 423 case UNUM_SIGN_EXCEPT_ZERO: 424 sb.append(u"sign-except-zero", -1); 425 break; 426 case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO: 427 sb.append(u"sign-accounting-except-zero", -1); 428 break; 429 case UNUM_SIGN_NEGATIVE: 430 sb.append(u"sign-negative", -1); 431 break; 432 case UNUM_SIGN_ACCOUNTING_NEGATIVE: 433 sb.append(u"sign-accounting-negative", -1); 434 break; 435 default: 436 UPRV_UNREACHABLE_EXIT; 437 } 438 } 439 440 void 441 enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) { 442 switch (value) { 443 case UNUM_DECIMAL_SEPARATOR_AUTO: 444 sb.append(u"decimal-auto", -1); 445 break; 446 case UNUM_DECIMAL_SEPARATOR_ALWAYS: 447 sb.append(u"decimal-always", -1); 448 break; 449 default: 450 UPRV_UNREACHABLE_EXIT; 451 } 452 } 453 454 455 UnlocalizedNumberFormatter skeleton::create( 456 const UnicodeString& skeletonString, UParseError* perror, UErrorCode& status) { 457 458 // Initialize perror 459 if (perror != nullptr) { 460 perror->line = 0; 461 perror->offset = -1; 462 perror->preContext[0] = 0; 463 perror->postContext[0] = 0; 464 } 465 466 umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); 467 if (U_FAILURE(status)) { 468 return {}; 469 } 470 471 int32_t errOffset; 472 MacroProps macros = parseSkeleton(skeletonString, errOffset, status); 473 if (U_SUCCESS(status)) { 474 return NumberFormatter::with().macros(macros); 475 } 476 477 if (perror == nullptr) { 478 return {}; 479 } 480 481 // Populate the UParseError with the error location 482 perror->offset = errOffset; 483 int32_t contextStart = uprv_max(0, errOffset - U_PARSE_CONTEXT_LEN + 1); 484 int32_t contextEnd = uprv_min(skeletonString.length(), errOffset + U_PARSE_CONTEXT_LEN - 1); 485 skeletonString.extract(contextStart, errOffset - contextStart, perror->preContext, 0); 486 perror->preContext[errOffset - contextStart] = 0; 487 skeletonString.extract(errOffset, contextEnd - errOffset, perror->postContext, 0); 488 perror->postContext[contextEnd - errOffset] = 0; 489 return {}; 490 } 491 492 UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { 493 umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); 494 UnicodeString sb; 495 GeneratorHelpers::generateSkeleton(macros, sb, status); 496 return sb; 497 } 498 499 MacroProps skeleton::parseSkeleton( 500 const UnicodeString& skeletonString, int32_t& errOffset, UErrorCode& status) { 501 U_ASSERT(U_SUCCESS(status)); 502 U_ASSERT(kSerializedStemTrie != nullptr); 503 504 // Add a trailing whitespace to the end of the skeleton string to make code cleaner. 505 UnicodeString tempSkeletonString(skeletonString); 506 tempSkeletonString.append(u' '); 507 508 SeenMacroProps seen; 509 MacroProps macros; 510 StringSegment segment(tempSkeletonString, false); 511 UCharsTrie stemTrie(kSerializedStemTrie); 512 ParseState stem = STATE_NULL; 513 int32_t offset = 0; 514 515 // Primary skeleton parse loop: 516 while (offset < segment.length()) { 517 UChar32 cp = segment.codePointAt(offset); 518 bool isTokenSeparator = PatternProps::isWhiteSpace(cp); 519 bool isOptionSeparator = (cp == u'/'); 520 521 if (!isTokenSeparator && !isOptionSeparator) { 522 // Non-separator token; consume it. 523 offset += U16_LENGTH(cp); 524 if (stem == STATE_NULL) { 525 // We are currently consuming a stem. 526 // Go to the next state in the stem trie. 527 stemTrie.nextForCodePoint(cp); 528 } 529 continue; 530 } 531 532 // We are looking at a token or option separator. 533 // If the segment is nonempty, parse it and reset the segment. 534 // Otherwise, make sure it is a valid repeating separator. 535 if (offset != 0) { 536 segment.setLength(offset); 537 if (stem == STATE_NULL) { 538 // The first separator after the start of a token. Parse it as a stem. 539 stem = parseStem(segment, stemTrie, seen, macros, status); 540 stemTrie.reset(); 541 } else { 542 // A separator after the first separator of a token. Parse it as an option. 543 stem = parseOption(stem, segment, macros, status); 544 } 545 segment.resetLength(); 546 if (U_FAILURE(status)) { 547 errOffset = segment.getOffset(); 548 return macros; 549 } 550 551 // Consume the segment: 552 segment.adjustOffset(offset); 553 offset = 0; 554 555 } else if (stem != STATE_NULL) { 556 // A separator ('/' or whitespace) following an option separator ('/') 557 // segment.setLength(U16_LENGTH(cp)); // for error message 558 // throw new SkeletonSyntaxException("Unexpected separator character", segment); 559 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 560 errOffset = segment.getOffset(); 561 return macros; 562 563 } else { 564 // Two spaces in a row; this is OK. 565 } 566 567 // Does the current stem forbid options? 568 if (isOptionSeparator && stem == STATE_NULL) { 569 // segment.setLength(U16_LENGTH(cp)); // for error message 570 // throw new SkeletonSyntaxException("Unexpected option separator", segment); 571 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 572 errOffset = segment.getOffset(); 573 return macros; 574 } 575 576 // Does the current stem require an option? 577 if (isTokenSeparator && stem != STATE_NULL) { 578 switch (stem) { 579 case STATE_INCREMENT_PRECISION: 580 case STATE_MEASURE_UNIT: 581 case STATE_PER_MEASURE_UNIT: 582 case STATE_IDENTIFIER_UNIT: 583 case STATE_UNIT_USAGE: 584 case STATE_CURRENCY_UNIT: 585 case STATE_INTEGER_WIDTH: 586 case STATE_NUMBERING_SYSTEM: 587 case STATE_SCALE: 588 // segment.setLength(U16_LENGTH(cp)); // for error message 589 // throw new SkeletonSyntaxException("Stem requires an option", segment); 590 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 591 errOffset = segment.getOffset(); 592 return macros; 593 default: 594 break; 595 } 596 stem = STATE_NULL; 597 } 598 599 // Consume the separator: 600 segment.adjustOffset(U16_LENGTH(cp)); 601 } 602 U_ASSERT(stem == STATE_NULL); 603 return macros; 604 } 605 606 ParseState 607 skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, 608 MacroProps& macros, UErrorCode& status) { 609 U_ASSERT(U_SUCCESS(status)); 610 611 // First check for "blueprint" stems, which start with a "signal char" 612 switch (segment.charAt(0)) { 613 case u'.': 614 CHECK_NULL(seen, precision, status); 615 blueprint_helpers::parseFractionStem(segment, macros, status); 616 return STATE_FRACTION_PRECISION; 617 case u'@': 618 CHECK_NULL(seen, precision, status); 619 blueprint_helpers::parseDigitsStem(segment, macros, status); 620 return STATE_PRECISION; 621 case u'E': 622 CHECK_NULL(seen, notation, status); 623 blueprint_helpers::parseScientificStem(segment, macros, status); 624 return STATE_NULL; 625 case u'0': 626 CHECK_NULL(seen, integerWidth, status); 627 blueprint_helpers::parseIntegerStem(segment, macros, status); 628 return STATE_NULL; 629 default: 630 break; 631 } 632 633 // Now look at the stemsTrie, which is already be pointing at our stem. 634 UStringTrieResult stemResult = stemTrie.current(); 635 636 if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) { 637 // throw new SkeletonSyntaxException("Unknown stem", segment); 638 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 639 return STATE_NULL; 640 } 641 642 auto stem = static_cast<StemEnum>(stemTrie.getValue()); 643 switch (stem) { 644 645 // Stems with meaning on their own, not requiring an option: 646 647 case STEM_COMPACT_SHORT: 648 case STEM_COMPACT_LONG: 649 case STEM_SCIENTIFIC: 650 case STEM_ENGINEERING: 651 case STEM_NOTATION_SIMPLE: 652 CHECK_NULL(seen, notation, status); 653 macros.notation = stem_to_object::notation(stem); 654 switch (stem) { 655 case STEM_SCIENTIFIC: 656 case STEM_ENGINEERING: 657 return STATE_SCIENTIFIC; // allows for scientific options 658 default: 659 return STATE_NULL; 660 } 661 662 case STEM_BASE_UNIT: 663 case STEM_PERCENT: 664 case STEM_PERMILLE: 665 CHECK_NULL(seen, unit, status); 666 macros.unit = stem_to_object::unit(stem); 667 return STATE_NULL; 668 669 case STEM_PERCENT_100: 670 CHECK_NULL(seen, scale, status); 671 CHECK_NULL(seen, unit, status); 672 macros.scale = Scale::powerOfTen(2); 673 macros.unit = NoUnit::percent(); 674 return STATE_NULL; 675 676 case STEM_PRECISION_INTEGER: 677 case STEM_PRECISION_UNLIMITED: 678 case STEM_PRECISION_CURRENCY_STANDARD: 679 case STEM_PRECISION_CURRENCY_CASH: 680 CHECK_NULL(seen, precision, status); 681 macros.precision = stem_to_object::precision(stem); 682 switch (stem) { 683 case STEM_PRECISION_INTEGER: 684 return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##" 685 default: 686 return STATE_PRECISION; 687 } 688 689 case STEM_ROUNDING_MODE_CEILING: 690 case STEM_ROUNDING_MODE_FLOOR: 691 case STEM_ROUNDING_MODE_DOWN: 692 case STEM_ROUNDING_MODE_UP: 693 case STEM_ROUNDING_MODE_HALF_EVEN: 694 case STEM_ROUNDING_MODE_HALF_ODD: 695 case STEM_ROUNDING_MODE_HALF_CEILING: 696 case STEM_ROUNDING_MODE_HALF_FLOOR: 697 case STEM_ROUNDING_MODE_HALF_DOWN: 698 case STEM_ROUNDING_MODE_HALF_UP: 699 case STEM_ROUNDING_MODE_UNNECESSARY: 700 CHECK_NULL(seen, roundingMode, status); 701 macros.roundingMode = stem_to_object::roundingMode(stem); 702 return STATE_NULL; 703 704 case STEM_INTEGER_WIDTH_TRUNC: 705 CHECK_NULL(seen, integerWidth, status); 706 macros.integerWidth = IntegerWidth::zeroFillTo(0).truncateAt(0); 707 return STATE_NULL; 708 709 case STEM_GROUP_OFF: 710 case STEM_GROUP_MIN2: 711 case STEM_GROUP_AUTO: 712 case STEM_GROUP_ON_ALIGNED: 713 case STEM_GROUP_THOUSANDS: 714 CHECK_NULL(seen, grouper, status); 715 macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem)); 716 return STATE_NULL; 717 718 case STEM_LATIN: 719 CHECK_NULL(seen, symbols, status); 720 macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status)); 721 return STATE_NULL; 722 723 case STEM_UNIT_WIDTH_NARROW: 724 case STEM_UNIT_WIDTH_SHORT: 725 case STEM_UNIT_WIDTH_FULL_NAME: 726 case STEM_UNIT_WIDTH_ISO_CODE: 727 case STEM_UNIT_WIDTH_FORMAL: 728 case STEM_UNIT_WIDTH_VARIANT: 729 case STEM_UNIT_WIDTH_HIDDEN: 730 CHECK_NULL(seen, unitWidth, status); 731 macros.unitWidth = stem_to_object::unitWidth(stem); 732 return STATE_NULL; 733 734 case STEM_SIGN_AUTO: 735 case STEM_SIGN_ALWAYS: 736 case STEM_SIGN_NEVER: 737 case STEM_SIGN_ACCOUNTING: 738 case STEM_SIGN_ACCOUNTING_ALWAYS: 739 case STEM_SIGN_EXCEPT_ZERO: 740 case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: 741 case STEM_SIGN_NEGATIVE: 742 case STEM_SIGN_ACCOUNTING_NEGATIVE: 743 CHECK_NULL(seen, sign, status); 744 macros.sign = stem_to_object::signDisplay(stem); 745 return STATE_NULL; 746 747 case STEM_DECIMAL_AUTO: 748 case STEM_DECIMAL_ALWAYS: 749 CHECK_NULL(seen, decimal, status); 750 macros.decimal = stem_to_object::decimalSeparatorDisplay(stem); 751 return STATE_NULL; 752 753 // Stems requiring an option: 754 755 case STEM_PRECISION_INCREMENT: 756 CHECK_NULL(seen, precision, status); 757 return STATE_INCREMENT_PRECISION; 758 759 case STEM_MEASURE_UNIT: 760 CHECK_NULL(seen, unit, status); 761 return STATE_MEASURE_UNIT; 762 763 case STEM_PER_MEASURE_UNIT: 764 CHECK_NULL(seen, perUnit, status); 765 return STATE_PER_MEASURE_UNIT; 766 767 case STEM_UNIT: 768 CHECK_NULL(seen, unit, status); 769 CHECK_NULL(seen, perUnit, status); 770 return STATE_IDENTIFIER_UNIT; 771 772 case STEM_UNIT_USAGE: 773 CHECK_NULL(seen, usage, status); 774 return STATE_UNIT_USAGE; 775 776 case STEM_CURRENCY: 777 CHECK_NULL(seen, unit, status); 778 CHECK_NULL(seen, perUnit, status); 779 return STATE_CURRENCY_UNIT; 780 781 case STEM_INTEGER_WIDTH: 782 CHECK_NULL(seen, integerWidth, status); 783 return STATE_INTEGER_WIDTH; 784 785 case STEM_NUMBERING_SYSTEM: 786 CHECK_NULL(seen, symbols, status); 787 return STATE_NUMBERING_SYSTEM; 788 789 case STEM_SCALE: 790 CHECK_NULL(seen, scale, status); 791 return STATE_SCALE; 792 793 default: 794 UPRV_UNREACHABLE_EXIT; 795 } 796 } 797 798 ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, 799 UErrorCode& status) { 800 U_ASSERT(U_SUCCESS(status)); 801 802 ///// Required options: ///// 803 804 switch (stem) { 805 case STATE_CURRENCY_UNIT: 806 blueprint_helpers::parseCurrencyOption(segment, macros, status); 807 return STATE_NULL; 808 case STATE_MEASURE_UNIT: 809 blueprint_helpers::parseMeasureUnitOption(segment, macros, status); 810 return STATE_NULL; 811 case STATE_PER_MEASURE_UNIT: 812 blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status); 813 return STATE_NULL; 814 case STATE_IDENTIFIER_UNIT: 815 blueprint_helpers::parseIdentifierUnitOption(segment, macros, status); 816 return STATE_NULL; 817 case STATE_UNIT_USAGE: 818 blueprint_helpers::parseUnitUsageOption(segment, macros, status); 819 return STATE_NULL; 820 case STATE_INCREMENT_PRECISION: 821 blueprint_helpers::parseIncrementOption(segment, macros, status); 822 return STATE_PRECISION; 823 case STATE_INTEGER_WIDTH: 824 blueprint_helpers::parseIntegerWidthOption(segment, macros, status); 825 return STATE_NULL; 826 case STATE_NUMBERING_SYSTEM: 827 blueprint_helpers::parseNumberingSystemOption(segment, macros, status); 828 return STATE_NULL; 829 case STATE_SCALE: 830 blueprint_helpers::parseScaleOption(segment, macros, status); 831 return STATE_NULL; 832 default: 833 break; 834 } 835 836 ///// Non-required options: ///// 837 838 // Scientific options 839 switch (stem) { 840 case STATE_SCIENTIFIC: 841 if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) { 842 return STATE_SCIENTIFIC; 843 } 844 if (U_FAILURE(status)) { 845 return {}; 846 } 847 if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) { 848 return STATE_SCIENTIFIC; 849 } 850 if (U_FAILURE(status)) { 851 return {}; 852 } 853 break; 854 default: 855 break; 856 } 857 858 // Frac-sig option 859 switch (stem) { 860 case STATE_FRACTION_PRECISION: 861 if (blueprint_helpers::parseFracSigOption(segment, macros, status)) { 862 return STATE_PRECISION; 863 } 864 if (U_FAILURE(status)) { 865 return {}; 866 } 867 // If the fracSig option was not found, try normal precision options. 868 stem = STATE_PRECISION; 869 break; 870 default: 871 break; 872 } 873 874 // Trailing zeros option 875 switch (stem) { 876 case STATE_PRECISION: 877 if (blueprint_helpers::parseTrailingZeroOption(segment, macros, status)) { 878 return STATE_NULL; 879 } 880 if (U_FAILURE(status)) { 881 return {}; 882 } 883 break; 884 default: 885 break; 886 } 887 888 // Unknown option 889 // throw new SkeletonSyntaxException("Invalid option", segment); 890 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 891 return STATE_NULL; 892 } 893 894 void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 895 if (U_FAILURE(status)) { return; } 896 897 // Supported options 898 if (GeneratorHelpers::notation(macros, sb, status)) { 899 sb.append(u' '); 900 } 901 if (U_FAILURE(status)) { return; } 902 if (GeneratorHelpers::unit(macros, sb, status)) { 903 sb.append(u' '); 904 } 905 if (U_FAILURE(status)) { return; } 906 if (GeneratorHelpers::usage(macros, sb, status)) { 907 sb.append(u' '); 908 } 909 if (U_FAILURE(status)) { return; } 910 if (GeneratorHelpers::precision(macros, sb, status)) { 911 sb.append(u' '); 912 } 913 if (U_FAILURE(status)) { return; } 914 if (GeneratorHelpers::roundingMode(macros, sb, status)) { 915 sb.append(u' '); 916 } 917 if (U_FAILURE(status)) { return; } 918 if (GeneratorHelpers::grouping(macros, sb, status)) { 919 sb.append(u' '); 920 } 921 if (U_FAILURE(status)) { return; } 922 if (GeneratorHelpers::integerWidth(macros, sb, status)) { 923 sb.append(u' '); 924 } 925 if (U_FAILURE(status)) { return; } 926 if (GeneratorHelpers::symbols(macros, sb, status)) { 927 sb.append(u' '); 928 } 929 if (U_FAILURE(status)) { return; } 930 if (GeneratorHelpers::unitWidth(macros, sb, status)) { 931 sb.append(u' '); 932 } 933 if (U_FAILURE(status)) { return; } 934 if (GeneratorHelpers::sign(macros, sb, status)) { 935 sb.append(u' '); 936 } 937 if (U_FAILURE(status)) { return; } 938 if (GeneratorHelpers::decimal(macros, sb, status)) { 939 sb.append(u' '); 940 } 941 if (U_FAILURE(status)) { return; } 942 if (GeneratorHelpers::scale(macros, sb, status)) { 943 sb.append(u' '); 944 } 945 if (U_FAILURE(status)) { return; } 946 947 // Unsupported options 948 if (!macros.padder.isBogus()) { 949 status = U_UNSUPPORTED_ERROR; 950 return; 951 } 952 if (macros.unitDisplayCase.isSet()) { 953 status = U_UNSUPPORTED_ERROR; 954 return; 955 } 956 if (macros.affixProvider != nullptr) { 957 status = U_UNSUPPORTED_ERROR; 958 return; 959 } 960 if (macros.rules != nullptr) { 961 status = U_UNSUPPORTED_ERROR; 962 return; 963 } 964 965 // Remove the trailing space 966 if (sb.length() > 0) { 967 sb.truncate(sb.length() - 1); 968 } 969 } 970 971 972 bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, 973 UErrorCode&) { 974 if (!isWildcardChar(segment.charAt(0))) { 975 return false; 976 } 977 int32_t offset = 1; 978 int32_t minExp = 0; 979 for (; offset < segment.length(); offset++) { 980 if (segment.charAt(offset) == u'e') { 981 minExp++; 982 } else { 983 break; 984 } 985 } 986 if (offset < segment.length()) { 987 return false; 988 } 989 // Use the public APIs to enforce bounds checking 990 macros.notation = static_cast<ScientificNotation&>(macros.notation).withMinExponentDigits(minExp); 991 return true; 992 } 993 994 void 995 blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) { 996 sb.append(kWildcardChar); 997 appendMultiple(sb, u'e', minExponentDigits); 998 } 999 1000 bool 1001 blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { 1002 // Get the sign display type out of the CharsTrie data structure. 1003 UCharsTrie tempStemTrie(kSerializedStemTrie); 1004 UStringTrieResult result = tempStemTrie.next( 1005 segment.toTempUnicodeString().getBuffer(), 1006 segment.length()); 1007 if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) { 1008 return false; 1009 } 1010 auto sign = stem_to_object::signDisplay(static_cast<StemEnum>(tempStemTrie.getValue())); 1011 if (sign == UNUM_SIGN_COUNT) { 1012 return false; 1013 } 1014 macros.notation = static_cast<ScientificNotation&>(macros.notation).withExponentSignDisplay(sign); 1015 return true; 1016 } 1017 1018 // The function is called by skeleton::parseOption which called by skeleton::parseSkeleton 1019 // the data pointed in the return macros.unit is stack allocated in the parseSkeleton function. 1020 #if U_GCC_MAJOR_MINOR >= 1204 1021 #pragma GCC diagnostic push 1022 #pragma GCC diagnostic ignored "-Wdangling-pointer" 1023 #endif 1024 void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, 1025 UErrorCode& status) { 1026 // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us 1027 if (segment.length() != 3) { 1028 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1029 return; 1030 } 1031 const char16_t* currencyCode = segment.toTempUnicodeString().getBuffer(); 1032 UErrorCode localStatus = U_ZERO_ERROR; 1033 CurrencyUnit currency(currencyCode, localStatus); 1034 if (U_FAILURE(localStatus)) { 1035 // Not 3 ascii chars 1036 // throw new SkeletonSyntaxException("Invalid currency", segment); 1037 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1038 return; 1039 } 1040 // Slicing is OK 1041 macros.unit = currency; // NOLINT 1042 } 1043 #if U_GCC_MAJOR_MINOR >= 1204 1044 #pragma GCC diagnostic pop 1045 #endif 1046 1047 void 1048 blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) { 1049 sb.append(currency.getISOCurrency(), -1); 1050 } 1051 1052 void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, 1053 UErrorCode& status) { 1054 U_ASSERT(U_SUCCESS(status)); 1055 const UnicodeString stemString = segment.toTempUnicodeString(); 1056 1057 // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) 1058 // http://unicode.org/reports/tr35/#Validity_Data 1059 int firstHyphen = 0; 1060 while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') { 1061 firstHyphen++; 1062 } 1063 if (firstHyphen == stemString.length()) { 1064 // throw new SkeletonSyntaxException("Invalid measure unit option", segment); 1065 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1066 return; 1067 } 1068 1069 // Need to do char <-> char16_t conversion... 1070 CharString type; 1071 SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status); 1072 CharString subType; 1073 SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status); 1074 1075 static constexpr int32_t CAPACITY = 50; 1076 MeasureUnit units[CAPACITY]; 1077 UErrorCode localStatus = U_ZERO_ERROR; 1078 int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus); 1079 if (U_FAILURE(localStatus)) { 1080 // More than 30 units in this type? 1081 status = U_INTERNAL_PROGRAM_ERROR; 1082 return; 1083 } 1084 for (int32_t i = 0; i < numUnits; i++) { 1085 auto& unit = units[i]; 1086 if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) { 1087 macros.unit = unit; 1088 return; 1089 } 1090 } 1091 1092 // throw new SkeletonSyntaxException("Unknown measure unit", segment); 1093 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1094 } 1095 1096 void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, 1097 UErrorCode& status) { 1098 // A little bit of a hack: save the current unit (numerator), call the main measure unit 1099 // parsing code, put back the numerator unit, and put the new unit into per-unit. 1100 MeasureUnit numerator = macros.unit; 1101 parseMeasureUnitOption(segment, macros, status); 1102 if (U_FAILURE(status)) { return; } 1103 macros.perUnit = macros.unit; 1104 macros.unit = numerator; 1105 } 1106 1107 void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros, 1108 UErrorCode& status) { 1109 // Need to do char <-> char16_t conversion... 1110 U_ASSERT(U_SUCCESS(status)); 1111 CharString buffer; 1112 SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); 1113 1114 ErrorCode internalStatus; 1115 macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus); 1116 if (internalStatus.isFailure()) { 1117 // throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e); 1118 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1119 return; 1120 } 1121 } 1122 1123 void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps ¯os, 1124 UErrorCode &status) { 1125 // Need to do char <-> char16_t conversion... 1126 U_ASSERT(U_SUCCESS(status)); 1127 CharString buffer; 1128 SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); 1129 macros.usage.set(buffer.toStringPiece()); 1130 // We do not do any validation of the usage string: it depends on the 1131 // unitPreferenceData in the units resources. 1132 } 1133 1134 void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros, 1135 UErrorCode& status) { 1136 U_ASSERT(segment.charAt(0) == u'.'); 1137 int32_t offset = 1; 1138 int32_t minFrac = 0; 1139 int32_t maxFrac; 1140 for (; offset < segment.length(); offset++) { 1141 if (segment.charAt(offset) == u'0') { 1142 minFrac++; 1143 } else { 1144 break; 1145 } 1146 } 1147 if (offset < segment.length()) { 1148 if (isWildcardChar(segment.charAt(offset))) { 1149 maxFrac = -1; 1150 offset++; 1151 } else { 1152 maxFrac = minFrac; 1153 for (; offset < segment.length(); offset++) { 1154 if (segment.charAt(offset) == u'#') { 1155 maxFrac++; 1156 } else { 1157 break; 1158 } 1159 } 1160 } 1161 } else { 1162 maxFrac = minFrac; 1163 } 1164 if (offset < segment.length()) { 1165 // throw new SkeletonSyntaxException("Invalid fraction stem", segment); 1166 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1167 return; 1168 } 1169 // Use the public APIs to enforce bounds checking 1170 if (maxFrac == -1) { 1171 if (minFrac == 0) { 1172 macros.precision = Precision::unlimited(); 1173 } else { 1174 macros.precision = Precision::minFraction(minFrac); 1175 } 1176 } else { 1177 macros.precision = Precision::minMaxFraction(minFrac, maxFrac); 1178 } 1179 } 1180 1181 void 1182 blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) { 1183 if (minFrac == 0 && maxFrac == 0) { 1184 sb.append(u"precision-integer", -1); 1185 return; 1186 } 1187 sb.append(u'.'); 1188 appendMultiple(sb, u'0', minFrac); 1189 if (maxFrac == -1) { 1190 sb.append(kWildcardChar); 1191 } else { 1192 appendMultiple(sb, u'#', maxFrac - minFrac); 1193 } 1194 } 1195 1196 void 1197 blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { 1198 U_ASSERT(segment.charAt(0) == u'@'); 1199 int32_t offset = 0; 1200 int32_t minSig = 0; 1201 int32_t maxSig; 1202 for (; offset < segment.length(); offset++) { 1203 if (segment.charAt(offset) == u'@') { 1204 minSig++; 1205 } else { 1206 break; 1207 } 1208 } 1209 if (offset < segment.length()) { 1210 if (isWildcardChar(segment.charAt(offset))) { 1211 maxSig = -1; 1212 offset++; 1213 } else { 1214 maxSig = minSig; 1215 for (; offset < segment.length(); offset++) { 1216 if (segment.charAt(offset) == u'#') { 1217 maxSig++; 1218 } else { 1219 break; 1220 } 1221 } 1222 } 1223 } else { 1224 maxSig = minSig; 1225 } 1226 if (offset < segment.length()) { 1227 // throw new SkeletonSyntaxException("Invalid significant digits stem", segment); 1228 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1229 return; 1230 } 1231 // Use the public APIs to enforce bounds checking 1232 if (maxSig == -1) { 1233 macros.precision = Precision::minSignificantDigits(minSig); 1234 } else { 1235 macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig); 1236 } 1237 } 1238 1239 void 1240 blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) { 1241 appendMultiple(sb, u'@', minSig); 1242 if (maxSig == -1) { 1243 sb.append(kWildcardChar); 1244 } else { 1245 appendMultiple(sb, u'#', maxSig - minSig); 1246 } 1247 } 1248 1249 void blueprint_helpers::parseScientificStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { 1250 U_ASSERT(segment.charAt(0) == u'E'); 1251 { 1252 int32_t offset = 1; 1253 if (segment.length() == offset) { 1254 goto fail; 1255 } 1256 bool isEngineering = false; 1257 if (segment.charAt(offset) == u'E') { 1258 isEngineering = true; 1259 offset++; 1260 if (segment.length() == offset) { 1261 goto fail; 1262 } 1263 } 1264 UNumberSignDisplay signDisplay = UNUM_SIGN_AUTO; 1265 if (segment.charAt(offset) == u'+') { 1266 offset++; 1267 if (segment.length() == offset) { 1268 goto fail; 1269 } 1270 if (segment.charAt(offset) == u'!') { 1271 signDisplay = UNUM_SIGN_ALWAYS; 1272 } else if (segment.charAt(offset) == u'?') { 1273 signDisplay = UNUM_SIGN_EXCEPT_ZERO; 1274 } else { 1275 // NOTE: Other sign displays are not included because they aren't useful in this context 1276 goto fail; 1277 } 1278 offset++; 1279 if (segment.length() == offset) { 1280 goto fail; 1281 } 1282 } 1283 int32_t minDigits = 0; 1284 for (; offset < segment.length(); offset++) { 1285 if (segment.charAt(offset) != u'0') { 1286 goto fail; 1287 } 1288 minDigits++; 1289 } 1290 macros.notation = (isEngineering ? Notation::engineering() : Notation::scientific()) 1291 .withExponentSignDisplay(signDisplay) 1292 .withMinExponentDigits(minDigits); 1293 return; 1294 } 1295 fail: void(); 1296 // throw new SkeletonSyntaxException("Invalid scientific stem", segment); 1297 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1298 } 1299 1300 void blueprint_helpers::parseIntegerStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { 1301 U_ASSERT(segment.charAt(0) == u'0'); 1302 int32_t offset = 1; 1303 for (; offset < segment.length(); offset++) { 1304 if (segment.charAt(offset) != u'0') { 1305 offset--; 1306 break; 1307 } 1308 } 1309 if (offset < segment.length()) { 1310 // throw new SkeletonSyntaxException("Invalid integer stem", segment); 1311 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1312 return; 1313 } 1314 macros.integerWidth = IntegerWidth::zeroFillTo(offset); 1315 } 1316 1317 bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros, 1318 UErrorCode& status) { 1319 if (segment.charAt(0) != u'@') { 1320 return false; 1321 } 1322 int offset = 0; 1323 int minSig = 0; 1324 int maxSig; 1325 for (; offset < segment.length(); offset++) { 1326 if (segment.charAt(offset) == u'@') { 1327 minSig++; 1328 } else { 1329 break; 1330 } 1331 } 1332 if (offset < segment.length()) { 1333 if (isWildcardChar(segment.charAt(offset))) { 1334 // @+, @@+, @@@+ 1335 maxSig = -1; 1336 offset++; 1337 } else { 1338 // @#, @##, @### 1339 // @@#, @@##, @@@# 1340 maxSig = minSig; 1341 for (; offset < segment.length(); offset++) { 1342 if (segment.charAt(offset) == u'#') { 1343 maxSig++; 1344 } else { 1345 break; 1346 } 1347 } 1348 } 1349 } else { 1350 // @, @@, @@@ 1351 maxSig = minSig; 1352 } 1353 const auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision); 1354 if (offset < segment.length()) { 1355 UNumberRoundingPriority priority; 1356 if (maxSig == -1) { 1357 // The wildcard character is not allowed with the priority annotation 1358 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1359 return false; 1360 } 1361 if (segment.codePointAt(offset) == u'r') { 1362 priority = UNUM_ROUNDING_PRIORITY_RELAXED; 1363 offset++; 1364 } else if (segment.codePointAt(offset) == u's') { 1365 priority = UNUM_ROUNDING_PRIORITY_STRICT; 1366 offset++; 1367 } else { 1368 // Invalid digits option for fraction rounder 1369 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1370 return false; 1371 } 1372 if (offset < segment.length()) { 1373 // Invalid digits option for fraction rounder 1374 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1375 return false; 1376 } 1377 macros.precision = oldPrecision.withSignificantDigits(minSig, maxSig, priority); 1378 } else if (maxSig == -1) { 1379 // withMinDigits 1380 macros.precision = oldPrecision.withMinDigits(minSig); 1381 } else if (minSig == 1) { 1382 // withMaxDigits 1383 macros.precision = oldPrecision.withMaxDigits(maxSig); 1384 } else { 1385 // Digits options with both min and max sig require the priority option 1386 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1387 return false; 1388 } 1389 1390 return true; 1391 } 1392 1393 bool blueprint_helpers::parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { 1394 if (segment == u"w") { 1395 macros.precision = macros.precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE); 1396 return true; 1397 } 1398 return false; 1399 } 1400 1401 void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps ¯os, 1402 UErrorCode &status) { 1403 number::impl::parseIncrementOption(segment, macros.precision, status); 1404 } 1405 1406 void blueprint_helpers::generateIncrementOption( 1407 uint32_t increment, 1408 digits_t incrementMagnitude, 1409 int32_t minFrac, 1410 UnicodeString& sb, 1411 UErrorCode&) { 1412 // Utilize DecimalQuantity/double_conversion to format this for us. 1413 DecimalQuantity dq; 1414 dq.setToLong(increment); 1415 dq.adjustMagnitude(incrementMagnitude); 1416 dq.setMinFraction(minFrac); 1417 sb.append(dq.toPlainString()); 1418 } 1419 1420 void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, 1421 UErrorCode& status) { 1422 int32_t offset = 0; 1423 int32_t minInt = 0; 1424 int32_t maxInt; 1425 if (isWildcardChar(segment.charAt(0))) { 1426 maxInt = -1; 1427 offset++; 1428 } else { 1429 maxInt = 0; 1430 } 1431 for (; offset < segment.length(); offset++) { 1432 if (maxInt != -1 && segment.charAt(offset) == u'#') { 1433 maxInt++; 1434 } else { 1435 break; 1436 } 1437 } 1438 if (offset < segment.length()) { 1439 for (; offset < segment.length(); offset++) { 1440 if (segment.charAt(offset) == u'0') { 1441 minInt++; 1442 } else { 1443 break; 1444 } 1445 } 1446 } 1447 if (maxInt != -1) { 1448 maxInt += minInt; 1449 } 1450 if (offset < segment.length()) { 1451 // throw new SkeletonSyntaxException("Invalid integer width stem", segment); 1452 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1453 return; 1454 } 1455 // Use the public APIs to enforce bounds checking 1456 if (maxInt == -1) { 1457 macros.integerWidth = IntegerWidth::zeroFillTo(minInt); 1458 } else { 1459 macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); 1460 } 1461 } 1462 1463 void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, 1464 UErrorCode&) { 1465 if (maxInt == -1) { 1466 sb.append(kWildcardChar); 1467 } else { 1468 appendMultiple(sb, u'#', maxInt - minInt); 1469 } 1470 appendMultiple(sb, u'0', minInt); 1471 } 1472 1473 void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, 1474 UErrorCode& status) { 1475 // Need to do char <-> char16_t conversion... 1476 U_ASSERT(U_SUCCESS(status)); 1477 CharString buffer; 1478 SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); 1479 1480 NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status); 1481 if (ns == nullptr || U_FAILURE(status)) { 1482 // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error 1483 // throw new SkeletonSyntaxException("Unknown numbering system", segment); 1484 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1485 return; 1486 } 1487 macros.symbols.setTo(ns); 1488 } 1489 1490 void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, 1491 UErrorCode&) { 1492 // Need to do char <-> char16_t conversion... 1493 sb.append(UnicodeString(ns.getName(), -1, US_INV)); 1494 } 1495 1496 void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros, 1497 UErrorCode& status) { 1498 // Need to do char <-> char16_t conversion... 1499 U_ASSERT(U_SUCCESS(status)); 1500 CharString buffer; 1501 SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); 1502 1503 LocalPointer<DecNum> decnum(new DecNum(), status); 1504 if (U_FAILURE(status)) { return; } 1505 decnum->setTo({buffer.data(), buffer.length()}, status); 1506 if (U_FAILURE(status) || decnum->isSpecial()) { 1507 // This is a skeleton syntax error; don't let the low-level decnum error bubble up 1508 status = U_NUMBER_SKELETON_SYNTAX_ERROR; 1509 return; 1510 } 1511 1512 // NOTE: The constructor will optimize the decnum for us if possible. 1513 macros.scale = {0, decnum.orphan()}; 1514 } 1515 1516 void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, 1517 UErrorCode& status) { 1518 // Utilize DecimalQuantity/double_conversion to format this for us. 1519 DecimalQuantity dq; 1520 if (arbitrary != nullptr) { 1521 dq.setToDecNum(*arbitrary, status); 1522 if (U_FAILURE(status)) { return; } 1523 } else { 1524 dq.setToInt(1); 1525 } 1526 dq.adjustMagnitude(magnitude); 1527 dq.roundToInfinity(); 1528 sb.append(dq.toPlainString()); 1529 } 1530 1531 1532 bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1533 if (macros.notation.fType == Notation::NTN_COMPACT) { 1534 UNumberCompactStyle style = macros.notation.fUnion.compactStyle; 1535 if (style == UNumberCompactStyle::UNUM_LONG) { 1536 sb.append(u"compact-long", -1); 1537 return true; 1538 } else if (style == UNumberCompactStyle::UNUM_SHORT) { 1539 sb.append(u"compact-short", -1); 1540 return true; 1541 } else { 1542 // Compact notation generated from custom data (not supported in skeleton) 1543 // The other compact notations are literals 1544 status = U_UNSUPPORTED_ERROR; 1545 return false; 1546 } 1547 } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { 1548 const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific; 1549 if (impl.fEngineeringInterval == 3) { 1550 sb.append(u"engineering", -1); 1551 } else { 1552 sb.append(u"scientific", -1); 1553 } 1554 if (impl.fMinExponentDigits > 1) { 1555 sb.append(u'/'); 1556 blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status); 1557 if (U_FAILURE(status)) { 1558 return false; 1559 } 1560 } 1561 if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) { 1562 sb.append(u'/'); 1563 enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb); 1564 } 1565 return true; 1566 } else { 1567 // Default value is not shown in normalized form 1568 return false; 1569 } 1570 } 1571 1572 bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1573 MeasureUnit unit = macros.unit; 1574 if (!utils::unitIsBaseUnit(macros.perUnit)) { 1575 if (utils::unitIsCurrency(macros.unit) || utils::unitIsCurrency(macros.perUnit)) { 1576 status = U_UNSUPPORTED_ERROR; 1577 return false; 1578 } 1579 unit = unit.product(macros.perUnit.reciprocal(status), status); 1580 } 1581 1582 if (utils::unitIsCurrency(unit)) { 1583 sb.append(u"currency/", -1); 1584 CurrencyUnit currency(unit, status); 1585 if (U_FAILURE(status)) { 1586 return false; 1587 } 1588 blueprint_helpers::generateCurrencyOption(currency, sb, status); 1589 return true; 1590 } else if (utils::unitIsBaseUnit(unit)) { 1591 // Default value is not shown in normalized form 1592 return false; 1593 } else if (utils::unitIsPercent(unit)) { 1594 sb.append(u"percent", -1); 1595 return true; 1596 } else if (utils::unitIsPermille(unit)) { 1597 sb.append(u"permille", -1); 1598 return true; 1599 } else { 1600 sb.append(u"unit/", -1); 1601 sb.append(unit.getIdentifier()); 1602 return true; 1603 } 1604 } 1605 1606 bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& /* status */) { 1607 if (macros.usage.isSet()) { 1608 sb.append(u"usage/", -1); 1609 sb.append(UnicodeString(macros.usage.fValue, -1, US_INV)); 1610 return true; 1611 } 1612 return false; 1613 } 1614 1615 bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1616 if (macros.precision.fType == Precision::RND_NONE) { 1617 sb.append(u"precision-unlimited", -1); 1618 } else if (macros.precision.fType == Precision::RND_FRACTION) { 1619 const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; 1620 blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); 1621 } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) { 1622 const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; 1623 blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); 1624 } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) { 1625 const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; 1626 blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); 1627 sb.append(u'/'); 1628 if (impl.fRetain) { 1629 if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) { 1630 // withMinDigits 1631 blueprint_helpers::generateDigitsStem(impl.fMaxSig, -1, sb, status); 1632 } else { 1633 // withMaxDigits 1634 blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status); 1635 } 1636 } else { 1637 blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); 1638 if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) { 1639 sb.append(u'r'); 1640 } else { 1641 sb.append(u's'); 1642 } 1643 } 1644 } else if (macros.precision.fType == Precision::RND_INCREMENT 1645 || macros.precision.fType == Precision::RND_INCREMENT_ONE 1646 || macros.precision.fType == Precision::RND_INCREMENT_FIVE) { 1647 const Precision::IncrementSettings& impl = macros.precision.fUnion.increment; 1648 sb.append(u"precision-increment/", -1); 1649 blueprint_helpers::generateIncrementOption( 1650 impl.fIncrement, 1651 impl.fIncrementMagnitude, 1652 impl.fMinFrac, 1653 sb, 1654 status); 1655 } else if (macros.precision.fType == Precision::RND_CURRENCY) { 1656 UCurrencyUsage usage = macros.precision.fUnion.currencyUsage; 1657 if (usage == UCURR_USAGE_STANDARD) { 1658 sb.append(u"precision-currency-standard", -1); 1659 } else { 1660 sb.append(u"precision-currency-cash", -1); 1661 } 1662 } else { 1663 // Bogus or Error 1664 return false; 1665 } 1666 1667 if (macros.precision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_HIDE_IF_WHOLE) { 1668 sb.append(u"/w", -1); 1669 } 1670 1671 // NOTE: Always return true for rounding because the default value depends on other options. 1672 return true; 1673 } 1674 1675 bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { 1676 if (macros.roundingMode == kDefaultMode) { 1677 return false; // Default 1678 } 1679 enum_to_stem_string::roundingMode(macros.roundingMode, sb); 1680 return true; 1681 } 1682 1683 bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1684 if (macros.grouper.isBogus()) { 1685 return false; // No value 1686 } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { 1687 status = U_UNSUPPORTED_ERROR; 1688 return false; 1689 } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { 1690 return false; // Default value 1691 } else { 1692 enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb); 1693 return true; 1694 } 1695 } 1696 1697 bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1698 if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() || 1699 macros.integerWidth == IntegerWidth::standard()) { 1700 // Error or Default 1701 return false; 1702 } 1703 const auto& minMaxInt = macros.integerWidth.fUnion.minMaxInt; 1704 if (minMaxInt.fMinInt == 0 && minMaxInt.fMaxInt == 0) { 1705 sb.append(u"integer-width-trunc", -1); 1706 return true; 1707 } 1708 sb.append(u"integer-width/", -1); 1709 blueprint_helpers::generateIntegerWidthOption( 1710 minMaxInt.fMinInt, 1711 minMaxInt.fMaxInt, 1712 sb, 1713 status); 1714 return true; 1715 } 1716 1717 bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1718 if (macros.symbols.isNumberingSystem()) { 1719 const NumberingSystem& ns = *macros.symbols.getNumberingSystem(); 1720 if (uprv_strcmp(ns.getName(), "latn") == 0) { 1721 sb.append(u"latin", -1); 1722 } else { 1723 sb.append(u"numbering-system/", -1); 1724 blueprint_helpers::generateNumberingSystemOption(ns, sb, status); 1725 } 1726 return true; 1727 } else if (macros.symbols.isDecimalFormatSymbols()) { 1728 status = U_UNSUPPORTED_ERROR; 1729 return false; 1730 } else { 1731 // No custom symbols 1732 return false; 1733 } 1734 } 1735 1736 bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { 1737 if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) { 1738 return false; // Default or Bogus 1739 } 1740 enum_to_stem_string::unitWidth(macros.unitWidth, sb); 1741 return true; 1742 } 1743 1744 bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { 1745 if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) { 1746 return false; // Default or Bogus 1747 } 1748 enum_to_stem_string::signDisplay(macros.sign, sb); 1749 return true; 1750 } 1751 1752 bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { 1753 if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) { 1754 return false; // Default or Bogus 1755 } 1756 enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb); 1757 return true; 1758 } 1759 1760 bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { 1761 if (!macros.scale.isValid()) { 1762 return false; // Default or Bogus 1763 } 1764 sb.append(u"scale/", -1); 1765 blueprint_helpers::generateScaleOption( 1766 macros.scale.fMagnitude, 1767 macros.scale.fArbitrary, 1768 sb, 1769 status); 1770 return true; 1771 } 1772 1773 1774 // Definitions of public API methods (put here for dependency disentanglement) 1775 1776 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER) 1777 // Ignore MSVC warning 4661. This is generated for NumberFormatterSettings<>::toSkeleton() as this method 1778 // is defined elsewhere (in number_skeletons.cpp). The compiler is warning that the explicit template instantiation 1779 // inside this single translation unit (CPP file) is incomplete, and thus it isn't sure if the template class is 1780 // fully defined. However, since each translation unit explicitly instantiates all the necessary template classes, 1781 // they will all be passed to the linker, and the linker will still find and export all the class members. 1782 #pragma warning(push) 1783 #pragma warning(disable: 4661) 1784 #endif 1785 1786 template<typename Derived> 1787 UnicodeString NumberFormatterSettings<Derived>::toSkeleton(UErrorCode& status) const { 1788 if (U_FAILURE(status)) { 1789 return ICU_Utility::makeBogusString(); 1790 } 1791 if (fMacros.copyErrorTo(status)) { 1792 return ICU_Utility::makeBogusString(); 1793 } 1794 return skeleton::generate(fMacros, status); 1795 } 1796 1797 // Declare all classes that implement NumberFormatterSettings 1798 // See https://stackoverflow.com/a/495056/1407170 1799 template 1800 class icu::number::NumberFormatterSettings<icu::number::UnlocalizedNumberFormatter>; 1801 template 1802 class icu::number::NumberFormatterSettings<icu::number::LocalizedNumberFormatter>; 1803 1804 UnlocalizedNumberFormatter 1805 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UErrorCode& status) { 1806 return skeleton::create(skeleton, nullptr, status); 1807 } 1808 1809 UnlocalizedNumberFormatter 1810 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UParseError& perror, UErrorCode& status) { 1811 return skeleton::create(skeleton, &perror, status); 1812 } 1813 1814 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER) 1815 // Warning 4661. 1816 #pragma warning(pop) 1817 #endif 1818 1819 #endif /* #if !UCONFIG_NO_FORMATTING */