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_mapper.h"
13#include "number_patternstring.h"
14#include "unicode/errorcode.h"
15#include "number_utils.h"
16#include "number_currencysymbols.h"
17
18using namespace icu;
19using namespace icu::number;
20using namespace icu::number::impl;
21
22
23UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
24 const DecimalFormatSymbols& symbols,
25 DecimalFormatWarehouse& warehouse,
26 UErrorCode& status) {
27 return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status));
28}
29
30UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
31 const DecimalFormatSymbols& symbols,
32 DecimalFormatWarehouse& warehouse,
33 DecimalFormatProperties& exportedProperties,
34 UErrorCode& status) {
35 return NumberFormatter::with().macros(
36 oldToNew(
37 properties, symbols, warehouse, &exportedProperties, status));
38}
39
40MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties,
41 const DecimalFormatSymbols& symbols,
42 DecimalFormatWarehouse& warehouse,
43 DecimalFormatProperties* exportedProperties,
44 UErrorCode& status) {
45 MacroProps macros;
46 Locale locale = symbols.getLocale();
47
48 /////////////
49 // SYMBOLS //
50 /////////////
51
52 macros.symbols.setTo(symbols);
53
54 //////////////////
55 // PLURAL RULES //
56 //////////////////
57
58 if (!properties.currencyPluralInfo.fPtr.isNull()) {
59 macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules();
60 }
61
62 /////////////
63 // AFFIXES //
64 /////////////
65
66 AffixPatternProvider* affixProvider;
67 if (properties.currencyPluralInfo.fPtr.isNull()) {
68 warehouse.currencyPluralInfoAPP.setToBogus();
69 warehouse.propertiesAPP.setTo(properties, status);
70 affixProvider = &warehouse.propertiesAPP;
71 } else {
72 warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, properties, status);
73 warehouse.propertiesAPP.setToBogus();
74 affixProvider = &warehouse.currencyPluralInfoAPP;
75 }
76 macros.affixProvider = affixProvider;
77
78 ///////////
79 // UNITS //
80 ///////////
81
82 bool useCurrency = (
83 !properties.currency.isNull() ||
84 !properties.currencyPluralInfo.fPtr.isNull() ||
85 !properties.currencyUsage.isNull() ||
86 affixProvider->hasCurrencySign());
87 CurrencyUnit currency = resolveCurrency(properties, locale, status);
88 UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD);
89 if (useCurrency) {
90 // NOTE: Slicing is OK.
91 macros.unit = currency; // NOLINT
92 }
93 warehouse.currencySymbols = {currency, locale, symbols, status};
94 macros.currencySymbols = &warehouse.currencySymbols;
95
96 ///////////////////////
97 // ROUNDING STRATEGY //
98 ///////////////////////
99
100 int32_t maxInt = properties.maximumIntegerDigits;
101 int32_t minInt = properties.minimumIntegerDigits;
102 int32_t maxFrac = properties.maximumFractionDigits;
103 int32_t minFrac = properties.minimumFractionDigits;
104 int32_t minSig = properties.minimumSignificantDigits;
105 int32_t maxSig = properties.maximumSignificantDigits;
106 double roundingIncrement = properties.roundingIncrement;
107 RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN);
108 bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
109 bool explicitMinMaxSig = minSig != -1 || maxSig != -1;
110 // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
111 // maxFrac was
112 // set (but not both) on a currency instance.
113 // NOTE: Increments are handled in "Precision.constructCurrency()".
114 if (useCurrency && (minFrac == -1 || maxFrac == -1)) {
115 int32_t digits = ucurr_getDefaultFractionDigitsForUsage(
116 currency.getISOCurrency(), currencyUsage, &status);
117 if (minFrac == -1 && maxFrac == -1) {
118 minFrac = digits;
119 maxFrac = digits;
120 } else if (minFrac == -1) {
121 minFrac = std::min(maxFrac, digits);
122 } else /* if (maxFrac == -1) */ {
123 maxFrac = std::max(minFrac, digits);
124 }
125 }
126 // Validate min/max int/frac.
127 // For backwards compatibility, minimum overrides maximum if the two conflict.
128 if (minInt == 0 && maxFrac != 0) {
129 minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac;
130 maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
131 minInt = 0;
132 maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
133 } else {
134 // Force a digit before the decimal point.
135 minFrac = minFrac < 0 ? 0 : minFrac;
136 maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
137 minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt;
138 maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt;
139 }
140 Precision precision;
141 if (!properties.currencyUsage.isNull()) {
142 precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
143 } else if (roundingIncrement != 0.0) {
144 if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
145 precision = Precision::constructFraction(minFrac, maxFrac);
146 } else {
147 precision = Precision::constructIncrement(roundingIncrement, minFrac);
148 }
149 } else if (explicitMinMaxSig) {
150 minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
151 maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig
152 ? kMaxIntFracSig : maxSig;
153 precision = Precision::constructSignificant(minSig, maxSig);
154 } else if (explicitMinMaxFrac) {
155 precision = Precision::constructFraction(minFrac, maxFrac);
156 } else if (useCurrency) {
157 precision = Precision::constructCurrency(currencyUsage);
158 }
159 if (!precision.isBogus()) {
160 precision.fRoundingMode = roundingMode;
161 macros.precision = precision;
162 }
163
164 ///////////////////
165 // INTEGER WIDTH //
166 ///////////////////
167
168 macros.integerWidth = IntegerWidth(
169 static_cast<digits_t>(minInt),
170 static_cast<digits_t>(maxInt),
171 properties.formatFailIfMoreThanMaxDigits);
172
173 ///////////////////////
174 // GROUPING STRATEGY //
175 ///////////////////////
176
177 macros.grouper = Grouper::forProperties(properties);
178
179 /////////////
180 // PADDING //
181 /////////////
182
183 if (properties.formatWidth > 0) {
184 macros.padder = Padder::forProperties(properties);
185 }
186
187 ///////////////////////////////
188 // DECIMAL MARK ALWAYS SHOWN //
189 ///////////////////////////////
190
191 macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS
192 : UNUM_DECIMAL_SEPARATOR_AUTO;
193
194 ///////////////////////
195 // SIGN ALWAYS SHOWN //
196 ///////////////////////
197
198 macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO;
199
200 /////////////////////////
201 // SCIENTIFIC NOTATION //
202 /////////////////////////
203
204 if (properties.minimumExponentDigits != -1) {
205 // Scientific notation is required.
206 // This whole section feels like a hack, but it is needed for regression tests.
207 // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
208 if (maxInt > 8) {
209 // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
210 // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
211 maxInt = minInt;
212 macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
213 } else if (maxInt > minInt && minInt > 1) {
214 // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
215 minInt = 1;
216 macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
217 }
218 int engineering = maxInt < 0 ? -1 : maxInt;
219 macros.notation = ScientificNotation(
220 // Engineering interval:
221 static_cast<int8_t>(engineering),
222 // Enforce minimum integer digits (for patterns like "000.00E0"):
223 (engineering == minInt),
224 // Minimum exponent digits:
225 static_cast<digits_t>(properties.minimumExponentDigits),
226 // Exponent sign always shown:
227 properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO);
228 // Scientific notation also involves overriding the rounding mode.
229 // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
230 if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
231 // For the purposes of rounding, get the original min/max int/frac, since the local
232 // variables have been manipulated for display purposes.
233 int maxInt_ = properties.maximumIntegerDigits;
234 int minInt_ = properties.minimumIntegerDigits;
235 int minFrac_ = properties.minimumFractionDigits;
236 int maxFrac_ = properties.maximumFractionDigits;
237 if (minInt_ == 0 && maxFrac_ == 0) {
238 // Patterns like "#E0" and "##E0", which mean no rounding!
239 macros.precision = Precision::unlimited();
240 } else if (minInt_ == 0 && minFrac_ == 0) {
241 // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
242 macros.precision = Precision::constructSignificant(1, maxFrac_ + 1);
243 } else {
244 int maxSig_ = minInt_ + maxFrac_;
245 // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
246 if (maxInt_ > minInt_ && minInt_ > 1) {
247 minInt_ = 1;
248 }
249 int minSig_ = minInt_ + minFrac_;
250 // To avoid regression, maxSig is not reset when minInt_ set to 1.
251 // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
252 macros.precision = Precision::constructSignificant(minSig_, maxSig_);
253 }
254 macros.precision.fRoundingMode = roundingMode;
255 }
256 }
257
258 //////////////////////
259 // COMPACT NOTATION //
260 //////////////////////
261
262 if (!properties.compactStyle.isNull()) {
263 if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
264 macros.notation = Notation::compactLong();
265 } else {
266 macros.notation = Notation::compactShort();
267 }
268 // Do not forward the affix provider.
269 macros.affixProvider = nullptr;
270 }
271
272 /////////////////
273 // MULTIPLIERS //
274 /////////////////
275
276 macros.scale = scaleFromProperties(properties);
277
278 //////////////////////
279 // PROPERTY EXPORTS //
280 //////////////////////
281
282 if (exportedProperties != nullptr) {
283
284 exportedProperties->currency = currency;
285 exportedProperties->roundingMode = roundingMode;
286 exportedProperties->minimumIntegerDigits = minInt;
287 exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
288
289 Precision rounding_;
290 if (precision.fType == Precision::PrecisionType::RND_CURRENCY) {
291 rounding_ = precision.withCurrency(currency, status);
292 } else {
293 rounding_ = precision;
294 }
295 int minFrac_ = minFrac;
296 int maxFrac_ = maxFrac;
297 int minSig_ = minSig;
298 int maxSig_ = maxSig;
299 double increment_ = 0.0;
300 if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
301 minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
302 maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
303 } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT
304 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_ONE
305 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_FIVE) {
306 increment_ = rounding_.fUnion.increment.fIncrement;
307 minFrac_ = rounding_.fUnion.increment.fMinFrac;
308 maxFrac_ = rounding_.fUnion.increment.fMinFrac;
309 } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
310 minSig_ = rounding_.fUnion.fracSig.fMinSig;
311 maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
312 }
313
314 exportedProperties->minimumFractionDigits = minFrac_;
315 exportedProperties->maximumFractionDigits = maxFrac_;
316 exportedProperties->minimumSignificantDigits = minSig_;
317 exportedProperties->maximumSignificantDigits = maxSig_;
318 exportedProperties->roundingIncrement = increment_;
319 }
320
321 return macros;
322}
323
324
325void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode& status) {
326 fBogus = false;
327
328 // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
329 // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
330 //
331 // 1) If the explicit setting is present for the field, use it.
332 // 2) Otherwise, follows UTS 35 rules based on the pattern string.
333 //
334 // Importantly, the explicit setters affect only the one field they override. If you set the positive
335 // prefix, that should not affect the negative prefix.
336
337 // Convenience: Extract the properties into local variables.
338 // Variables are named with three chars: [p/n][p/s][o/p]
339 // [p/n] => p for positive, n for negative
340 // [p/s] => p for prefix, s for suffix
341 // [o/p] => o for escaped custom override string, p for pattern string
342 UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
343 UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
344 UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
345 UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
346 const UnicodeString& ppp = properties.positivePrefixPattern;
347 const UnicodeString& psp = properties.positiveSuffixPattern;
348 const UnicodeString& npp = properties.negativePrefixPattern;
349 const UnicodeString& nsp = properties.negativeSuffixPattern;
350
351 if (!properties.positivePrefix.isBogus()) {
352 posPrefix = ppo;
353 } else if (!ppp.isBogus()) {
354 posPrefix = ppp;
355 } else {
356 // UTS 35: Default positive prefix is empty string.
357 posPrefix = u"";
358 }
359
360 if (!properties.positiveSuffix.isBogus()) {
361 posSuffix = pso;
362 } else if (!psp.isBogus()) {
363 posSuffix = psp;
364 } else {
365 // UTS 35: Default positive suffix is empty string.
366 posSuffix = u"";
367 }
368
369 if (!properties.negativePrefix.isBogus()) {
370 negPrefix = npo;
371 } else if (!npp.isBogus()) {
372 negPrefix = npp;
373 } else {
374 // UTS 35: Default negative prefix is "-" with positive prefix.
375 // Important: We prepend the "-" to the pattern, not the override!
376 negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
377 }
378
379 if (!properties.negativeSuffix.isBogus()) {
380 negSuffix = nso;
381 } else if (!nsp.isBogus()) {
382 negSuffix = nsp;
383 } else {
384 // UTS 35: Default negative prefix is the positive prefix.
385 negSuffix = psp.isBogus() ? u"" : psp;
386 }
387
388 // For declaring if this is a currency pattern, we need to look at the
389 // original pattern, not at any user-specified overrides.
390 isCurrencyPattern = (
391 AffixUtils::hasCurrencySymbols(ppp, status) ||
392 AffixUtils::hasCurrencySymbols(psp, status) ||
393 AffixUtils::hasCurrencySymbols(npp, status) ||
394 AffixUtils::hasCurrencySymbols(nsp, status));
395}
396
397char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
398 return getStringInternal(flags).charAt(i);
399}
400
401int PropertiesAffixPatternProvider::length(int flags) const {
402 return getStringInternal(flags).length();
403}
404
405UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
406 return getStringInternal(flags);
407}
408
409const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
410 bool prefix = (flags & AFFIX_PREFIX) != 0;
411 bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
412 if (prefix && negative) {
413 return negPrefix;
414 } else if (prefix) {
415 return posPrefix;
416 } else if (negative) {
417 return negSuffix;
418 } else {
419 return posSuffix;
420 }
421}
422
423bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
424 // TODO: Change the internal APIs to propagate out the error?
425 ErrorCode localStatus;
426 return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
427 AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
428}
429
430bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
431 return (
432 (negSuffix != posSuffix) ||
433 negPrefix.tempSubString(1) != posPrefix ||
434 negPrefix.charAt(0) != u'-'
435 );
436}
437
438bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
439 ErrorCode localStatus;
440 return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
441 AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
442}
443
444bool PropertiesAffixPatternProvider::hasCurrencySign() const {
445 return isCurrencyPattern;
446}
447
448bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
449 return AffixUtils::containsType(posPrefix, type, status) ||
450 AffixUtils::containsType(posSuffix, type, status) ||
451 AffixUtils::containsType(negPrefix, type, status) ||
452 AffixUtils::containsType(negSuffix, type, status);
453}
454
455bool PropertiesAffixPatternProvider::hasBody() const {
456 return true;
457}
458
459
460void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
461 const DecimalFormatProperties& properties,
462 UErrorCode& status) {
463 // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
464 // because user-specified affix overrides still need to work.
465 fBogus = false;
466 DecimalFormatProperties pluralProperties(properties);
467 for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
468 const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
469 UnicodeString patternString;
470 patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
471 PatternParser::parseToExistingProperties(
472 patternString,
473 pluralProperties,
474 IGNORE_ROUNDING_NEVER,
475 status);
476 affixesByPlural[plural].setTo(pluralProperties, status);
477 }
478}
479
480char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
481 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
482 return affixesByPlural[pluralOrdinal].charAt(flags, i);
483}
484
485int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
486 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
487 return affixesByPlural[pluralOrdinal].length(flags);
488}
489
490UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
491 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
492 return affixesByPlural[pluralOrdinal].getString(flags);
493}
494
495bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
496 return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
497}
498
499bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
500 return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
501}
502
503bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
504 return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
505}
506
507bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
508 return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
509}
510
511bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
512 return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
513}
514
515bool CurrencyPluralInfoAffixProvider::hasBody() const {
516 return affixesByPlural[StandardPlural::OTHER].hasBody();
517}
518
519
520#endif /* #if !UCONFIG_NO_FORMATTING */
521