1// © 2017 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#include "cstring.h"
9#include "unicode/ures.h"
10#include "uresimp.h"
11#include "charstr.h"
12#include "number_formatimpl.h"
13#include "unicode/numfmt.h"
14#include "number_patternstring.h"
15#include "number_utils.h"
16#include "unicode/numberformatter.h"
17#include "unicode/dcfmtsym.h"
18#include "number_scientific.h"
19#include "number_compact.h"
20#include "uresimp.h"
21#include "ureslocs.h"
22
23using namespace icu;
24using namespace icu::number;
25using namespace icu::number::impl;
26
27namespace {
28
29struct CurrencyFormatInfoResult {
30 bool exists;
31 const char16_t* pattern;
32 const char16_t* decimalSeparator;
33 const char16_t* groupingSeparator;
34};
35
36CurrencyFormatInfoResult
37getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) {
38 // TODO: Load this data in a centralized location like ICU4J?
39 // TODO: Move this into the CurrencySymbols class?
40 // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up.
41 CurrencyFormatInfoResult result = {false, nullptr, nullptr, nullptr};
42 if (U_FAILURE(status)) { return result; }
43 CharString key;
44 key.append("Currencies/", status);
45 key.append(isoCode, status);
46 UErrorCode localStatus = status;
47 LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus));
48 ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus);
49 if (U_SUCCESS(localStatus) &&
50 ures_getSize(bundle.getAlias()) > 2) { // the length is 3 if more data is present
51 ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus);
52 int32_t dummy;
53 result.exists = true;
54 result.pattern = ures_getStringByIndex(bundle.getAlias(), 0, &dummy, &localStatus);
55 result.decimalSeparator = ures_getStringByIndex(bundle.getAlias(), 1, &dummy, &localStatus);
56 result.groupingSeparator = ures_getStringByIndex(bundle.getAlias(), 2, &dummy, &localStatus);
57 status = localStatus;
58 } else if (localStatus != U_MISSING_RESOURCE_ERROR) {
59 status = localStatus;
60 }
61 return result;
62}
63
64} // namespace
65
66
67MicroPropsGenerator::~MicroPropsGenerator() = default;
68
69
70NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
71 : NumberFormatterImpl(macros, true, status) {
72}
73
74int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
75 FormattedStringBuilder& outString, UErrorCode& status) {
76 NumberFormatterImpl impl(macros, false, status);
77 MicroProps& micros = impl.preProcessUnsafe(inValue, status);
78 if (U_FAILURE(status)) { return 0; }
79 int32_t length = writeNumber(micros, inValue, outString, 0, status);
80 length += writeAffixes(micros, outString, 0, length, status);
81 return length;
82}
83
84int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
85 StandardPlural::Form plural,
86 FormattedStringBuilder& outString, UErrorCode& status) {
87 NumberFormatterImpl impl(macros, false, status);
88 return impl.getPrefixSuffixUnsafe(signum, plural, outString, status);
89}
90
91// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
92// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
93// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
94// See MicroProps::processQuantity() for details.
95
96int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, FormattedStringBuilder& outString,
97 UErrorCode& status) const {
98 MicroProps micros;
99 preProcess(inValue, micros, status);
100 if (U_FAILURE(status)) { return 0; }
101 int32_t length = writeNumber(micros, inValue, outString, 0, status);
102 length += writeAffixes(micros, outString, 0, length, status);
103 return length;
104}
105
106void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
107 UErrorCode& status) const {
108 if (U_FAILURE(status)) { return; }
109 if (fMicroPropsGenerator == nullptr) {
110 status = U_INTERNAL_PROGRAM_ERROR;
111 return;
112 }
113 fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
114 microsOut.integerWidth.apply(inValue, status);
115}
116
117MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
118 if (U_FAILURE(status)) {
119 return fMicros; // must always return a value
120 }
121 if (fMicroPropsGenerator == nullptr) {
122 status = U_INTERNAL_PROGRAM_ERROR;
123 return fMicros; // must always return a value
124 }
125 fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
126 fMicros.integerWidth.apply(inValue, status);
127 return fMicros;
128}
129
130int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural,
131 FormattedStringBuilder& outString, UErrorCode& status) const {
132 if (U_FAILURE(status)) { return 0; }
133 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
134 // Safe path: use fImmutablePatternModifier.
135 const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
136 modifier->apply(outString, 0, 0, status);
137 if (U_FAILURE(status)) { return 0; }
138 return modifier->getPrefixLength();
139}
140
141int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
142 FormattedStringBuilder& outString, UErrorCode& status) {
143 if (U_FAILURE(status)) { return 0; }
144 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
145 // Unsafe path: use fPatternModifier.
146 fPatternModifier->setNumberProperties(signum, plural);
147 fPatternModifier->apply(outString, 0, 0, status);
148 if (U_FAILURE(status)) { return 0; }
149 return fPatternModifier->getPrefixLength();
150}
151
152NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
153 fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
154}
155
156//////////
157
158const MicroPropsGenerator*
159NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) {
160 if (U_FAILURE(status)) { return nullptr; }
161 const MicroPropsGenerator* chain = &fMicros;
162
163 // Check that macros is error-free before continuing.
164 if (macros.copyErrorTo(status)) {
165 return nullptr;
166 }
167
168 // TODO: Accept currency symbols from DecimalFormatSymbols?
169
170 // Pre-compute a few values for efficiency.
171 bool isCurrency = utils::unitIsCurrency(macros.unit);
172 bool isNoUnit = utils::unitIsNoUnit(macros.unit);
173 bool isPercent = utils::unitIsPercent(macros.unit);
174 bool isPermille = utils::unitIsPermille(macros.unit);
175 bool isAccounting =
176 macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS ||
177 macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
178 CurrencyUnit currency(u"", status);
179 if (isCurrency) {
180 currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
181 }
182 const CurrencySymbols* currencySymbols;
183 if (macros.currencySymbols != nullptr) {
184 // Used by the DecimalFormat code path
185 currencySymbols = macros.currencySymbols;
186 } else {
187 fWarehouse.fCurrencySymbols = {currency, macros.locale, status};
188 currencySymbols = &fWarehouse.fCurrencySymbols;
189 }
190 UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
191 if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
192 unitWidth = macros.unitWidth;
193 }
194 bool isCldrUnit = !isCurrency && !isNoUnit &&
195 (unitWidth == UNUM_UNIT_WIDTH_FULL_NAME || !(isPercent || isPermille));
196
197 // Select the numbering system.
198 LocalPointer<const NumberingSystem> nsLocal;
199 const NumberingSystem* ns;
200 if (macros.symbols.isNumberingSystem()) {
201 ns = macros.symbols.getNumberingSystem();
202 } else {
203 // TODO: Is there a way to avoid creating the NumberingSystem object?
204 ns = NumberingSystem::createInstance(macros.locale, status);
205 // Give ownership to the function scope.
206 nsLocal.adoptInstead(ns);
207 }
208 const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn";
209 uprv_strncpy(fMicros.nsName, nsName, 8);
210 fMicros.nsName[8] = 0; // guarantee NUL-terminated
211
212 // Resolve the symbols. Do this here because currency may need to customize them.
213 if (macros.symbols.isDecimalFormatSymbols()) {
214 fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
215 } else {
216 auto newSymbols = new DecimalFormatSymbols(macros.locale, *ns, status);
217 if (newSymbols == nullptr) {
218 status = U_MEMORY_ALLOCATION_ERROR;
219 return nullptr;
220 }
221 fMicros.symbols = newSymbols;
222 // Give ownership to the NumberFormatterImpl.
223 fSymbols.adoptInstead(fMicros.symbols);
224 }
225
226 // Load and parse the pattern string. It is used for grouping sizes and affixes only.
227 // If we are formatting currency, check for a currency-specific pattern.
228 const char16_t* pattern = nullptr;
229 if (isCurrency) {
230 CurrencyFormatInfoResult info = getCurrencyFormatInfo(
231 macros.locale, currency.getSubtype(), status);
232 if (info.exists) {
233 pattern = info.pattern;
234 // It's clunky to clone an object here, but this code is not frequently executed.
235 auto symbols = new DecimalFormatSymbols(*fMicros.symbols);
236 if (symbols == nullptr) {
237 status = U_MEMORY_ALLOCATION_ERROR;
238 return nullptr;
239 }
240 fMicros.symbols = symbols;
241 fSymbols.adoptInstead(symbols);
242 symbols->setSymbol(
243 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol,
244 UnicodeString(info.decimalSeparator),
245 FALSE);
246 symbols->setSymbol(
247 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol,
248 UnicodeString(info.groupingSeparator),
249 FALSE);
250 }
251 }
252 if (pattern == nullptr) {
253 CldrPatternStyle patternStyle;
254 if (isCldrUnit) {
255 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
256 } else if (isPercent || isPermille) {
257 patternStyle = CLDR_PATTERN_STYLE_PERCENT;
258 } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
259 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
260 } else if (isAccounting) {
261 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
262 // the API contract allows us to add support to other units in the future.
263 patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
264 } else {
265 patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
266 }
267 pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
268 }
269 auto patternInfo = new ParsedPatternInfo();
270 if (patternInfo == nullptr) {
271 status = U_MEMORY_ALLOCATION_ERROR;
272 return nullptr;
273 }
274 fPatternInfo.adoptInstead(patternInfo);
275 PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
276
277 /////////////////////////////////////////////////////////////////////////////////////
278 /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
279 /////////////////////////////////////////////////////////////////////////////////////
280
281 // Multiplier
282 if (macros.scale.isValid()) {
283 fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
284 chain = &fMicros.helpers.multiplier;
285 }
286
287 // Rounding strategy
288 Precision precision;
289 if (!macros.precision.isBogus()) {
290 precision = macros.precision;
291 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
292 precision = Precision::integer().withMinDigits(2);
293 } else if (isCurrency) {
294 precision = Precision::currency(UCURR_USAGE_STANDARD);
295 } else {
296 precision = Precision::maxFraction(6);
297 }
298 UNumberFormatRoundingMode roundingMode;
299 if (macros.roundingMode != kDefaultMode) {
300 roundingMode = macros.roundingMode;
301 } else {
302 // Temporary until ICU 64
303 roundingMode = precision.fRoundingMode;
304 }
305 fMicros.rounder = {precision, roundingMode, currency, status};
306
307 // Grouping strategy
308 if (!macros.grouper.isBogus()) {
309 fMicros.grouping = macros.grouper;
310 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
311 // Compact notation uses minGrouping by default since ICU 59
312 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
313 } else {
314 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
315 }
316 fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
317
318 // Padding strategy
319 if (!macros.padder.isBogus()) {
320 fMicros.padding = macros.padder;
321 } else {
322 fMicros.padding = Padder::none();
323 }
324
325 // Integer width
326 if (!macros.integerWidth.isBogus()) {
327 fMicros.integerWidth = macros.integerWidth;
328 } else {
329 fMicros.integerWidth = IntegerWidth::standard();
330 }
331
332 // Sign display
333 if (macros.sign != UNUM_SIGN_COUNT) {
334 fMicros.sign = macros.sign;
335 } else {
336 fMicros.sign = UNUM_SIGN_AUTO;
337 }
338
339 // Decimal mark display
340 if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
341 fMicros.decimal = macros.decimal;
342 } else {
343 fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
344 }
345
346 // Use monetary separator symbols
347 fMicros.useCurrency = isCurrency;
348
349 // Inner modifier (scientific notation)
350 if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
351 auto newScientificHandler = new ScientificHandler(&macros.notation, fMicros.symbols, chain);
352 if (newScientificHandler == nullptr) {
353 status = U_MEMORY_ALLOCATION_ERROR;
354 return nullptr;
355 }
356 fScientificHandler.adoptInstead(newScientificHandler);
357 chain = fScientificHandler.getAlias();
358 } else {
359 // No inner modifier required
360 fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
361 }
362
363 // Middle modifier (patterns, positive/negative, currency symbols, percent)
364 auto patternModifier = new MutablePatternModifier(false);
365 if (patternModifier == nullptr) {
366 status = U_MEMORY_ALLOCATION_ERROR;
367 return nullptr;
368 }
369 fPatternModifier.adoptInstead(patternModifier);
370 patternModifier->setPatternInfo(
371 macros.affixProvider != nullptr ? macros.affixProvider
372 : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
373 UNUM_FIELD_COUNT);
374 patternModifier->setPatternAttributes(fMicros.sign, isPermille);
375 if (patternModifier->needsPlurals()) {
376 patternModifier->setSymbols(
377 fMicros.symbols,
378 currencySymbols,
379 unitWidth,
380 resolvePluralRules(macros.rules, macros.locale, status));
381 } else {
382 patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr);
383 }
384 if (safe) {
385 fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
386 }
387
388 // Outer modifier (CLDR units and currency long names)
389 if (isCldrUnit) {
390 fLongNameHandler.adoptInstead(
391 LongNameHandler::forMeasureUnit(
392 macros.locale,
393 macros.unit,
394 macros.perUnit,
395 unitWidth,
396 resolvePluralRules(macros.rules, macros.locale, status),
397 chain,
398 status));
399 chain = fLongNameHandler.getAlias();
400 } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
401 fLongNameHandler.adoptInstead(
402 LongNameHandler::forCurrencyLongNames(
403 macros.locale,
404 currency,
405 resolvePluralRules(macros.rules, macros.locale, status),
406 chain,
407 status));
408 chain = fLongNameHandler.getAlias();
409 } else {
410 // No outer modifier required
411 fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
412 }
413
414 // Compact notation
415 if (macros.notation.fType == Notation::NTN_COMPACT) {
416 CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
417 ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
418 auto newCompactHandler = new CompactHandler(
419 macros.notation.fUnion.compactStyle,
420 macros.locale,
421 nsName,
422 compactType,
423 resolvePluralRules(macros.rules, macros.locale, status),
424 patternModifier,
425 safe,
426 chain,
427 status);
428 if (newCompactHandler == nullptr) {
429 status = U_MEMORY_ALLOCATION_ERROR;
430 return nullptr;
431 }
432 fCompactHandler.adoptInstead(newCompactHandler);
433 chain = fCompactHandler.getAlias();
434 }
435
436 // Always add the pattern modifier as the last element of the chain.
437 if (safe) {
438 fImmutablePatternModifier->addToChain(chain);
439 chain = fImmutablePatternModifier.getAlias();
440 } else {
441 patternModifier->addToChain(chain);
442 chain = patternModifier;
443 }
444
445 return chain;
446}
447
448const PluralRules*
449NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale,
450 UErrorCode& status) {
451 if (rulesPtr != nullptr) {
452 return rulesPtr;
453 }
454 // Lazily create PluralRules
455 if (fRules.isNull()) {
456 fRules.adoptInstead(PluralRules::forLocale(locale, status));
457 }
458 return fRules.getAlias();
459}
460
461int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string,
462 int32_t start, int32_t end, UErrorCode& status) {
463 // Always apply the inner modifier (which is "strong").
464 int32_t length = micros.modInner->apply(string, start, end, status);
465 if (micros.padding.isValid()) {
466 length += micros.padding
467 .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
468 } else {
469 length += micros.modMiddle->apply(string, start, length + end, status);
470 length += micros.modOuter->apply(string, start, length + end, status);
471 }
472 return length;
473}
474
475int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
476 FormattedStringBuilder& string, int32_t index,
477 UErrorCode& status) {
478 int32_t length = 0;
479 if (quantity.isInfinite()) {
480 length += string.insert(
481 length + index,
482 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
483 UNUM_INTEGER_FIELD,
484 status);
485
486 } else if (quantity.isNaN()) {
487 length += string.insert(
488 length + index,
489 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
490 UNUM_INTEGER_FIELD,
491 status);
492
493 } else {
494 // Add the integer digits
495 length += writeIntegerDigits(micros, quantity, string, length + index, status);
496
497 // Add the decimal point
498 if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
499 length += string.insert(
500 length + index,
501 micros.useCurrency ? micros.symbols->getSymbol(
502 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
503 .symbols
504 ->getSymbol(
505 DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
506 UNUM_DECIMAL_SEPARATOR_FIELD,
507 status);
508 }
509
510 // Add the fraction digits
511 length += writeFractionDigits(micros, quantity, string, length + index, status);
512
513 if (length == 0) {
514 // Force output of the digit for value 0
515 length += utils::insertDigitFromSymbols(
516 string, index, 0, *micros.symbols, UNUM_INTEGER_FIELD, status);
517 }
518 }
519
520 return length;
521}
522
523int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
524 FormattedStringBuilder& string, int32_t index,
525 UErrorCode& status) {
526 int length = 0;
527 int integerCount = quantity.getUpperDisplayMagnitude() + 1;
528 for (int i = 0; i < integerCount; i++) {
529 // Add grouping separator
530 if (micros.grouping.groupAtPosition(i, quantity)) {
531 length += string.insert(
532 index,
533 micros.useCurrency ? micros.symbols->getSymbol(
534 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
535 : micros.symbols->getSymbol(
536 DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
537 UNUM_GROUPING_SEPARATOR_FIELD,
538 status);
539 }
540
541 // Get and append the next digit value
542 int8_t nextDigit = quantity.getDigit(i);
543 length += utils::insertDigitFromSymbols(
544 string, index, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
545 }
546 return length;
547}
548
549int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
550 FormattedStringBuilder& string, int32_t index,
551 UErrorCode& status) {
552 int length = 0;
553 int fractionCount = -quantity.getLowerDisplayMagnitude();
554 for (int i = 0; i < fractionCount; i++) {
555 // Get and append the next digit value
556 int8_t nextDigit = quantity.getDigit(-i - 1);
557 length += utils::insertDigitFromSymbols(
558 string, length + index, nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
559 }
560 return length;
561}
562
563#endif /* #if !UCONFIG_NO_FORMATTING */
564