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 
18 using namespace icu;
19 using namespace icu::number;
20 using namespace icu::number::impl;
21 
22 
create(const DecimalFormatProperties & properties,const DecimalFormatSymbols & symbols,DecimalFormatWarehouse & warehouse,UErrorCode & status)23 UnlocalizedNumberFormatter 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 
create(const DecimalFormatProperties & properties,const DecimalFormatSymbols & symbols,DecimalFormatWarehouse & warehouse,DecimalFormatProperties & exportedProperties,UErrorCode & status)30 UnlocalizedNumberFormatter 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 
oldToNew(const DecimalFormatProperties & properties,const DecimalFormatSymbols & symbols,DecimalFormatWarehouse & warehouse,DecimalFormatProperties * exportedProperties,UErrorCode & status)40 MacroProps 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     // The following logic ensures that there is always a minimum of at least one digit.
129     if (minInt == 0 && maxFrac != 0) {
130         // Force a digit after the decimal point.
131         minFrac = minFrac <= 0 ? 1 : minFrac;
132         maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
133         minInt = 0;
134         maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
135     } else {
136         // Force a digit before the decimal point.
137         minFrac = minFrac < 0 ? 0 : minFrac;
138         maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
139         minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt;
140         maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt;
141     }
142     Precision precision;
143     if (!properties.currencyUsage.isNull()) {
144         precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
145     } else if (roundingIncrement != 0.0) {
146         if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
147             precision = Precision::constructFraction(minFrac, maxFrac);
148         } else {
149             precision = Precision::constructIncrement(roundingIncrement, minFrac);
150         }
151     } else if (explicitMinMaxSig) {
152         minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
153         maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig
154                                                                           ? kMaxIntFracSig : maxSig;
155         precision = Precision::constructSignificant(minSig, maxSig);
156     } else if (explicitMinMaxFrac) {
157         precision = Precision::constructFraction(minFrac, maxFrac);
158     } else if (useCurrency) {
159         precision = Precision::constructCurrency(currencyUsage);
160     }
161     if (!precision.isBogus()) {
162         precision.fRoundingMode = roundingMode;
163         macros.precision = precision;
164     }
165 
166     ///////////////////
167     // INTEGER WIDTH //
168     ///////////////////
169 
170     macros.integerWidth = IntegerWidth(
171             static_cast<digits_t>(minInt),
172             static_cast<digits_t>(maxInt),
173             properties.formatFailIfMoreThanMaxDigits);
174 
175     ///////////////////////
176     // GROUPING STRATEGY //
177     ///////////////////////
178 
179     macros.grouper = Grouper::forProperties(properties);
180 
181     /////////////
182     // PADDING //
183     /////////////
184 
185     if (properties.formatWidth > 0) {
186         macros.padder = Padder::forProperties(properties);
187     }
188 
189     ///////////////////////////////
190     // DECIMAL MARK ALWAYS SHOWN //
191     ///////////////////////////////
192 
193     macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS
194                                                             : UNUM_DECIMAL_SEPARATOR_AUTO;
195 
196     ///////////////////////
197     // SIGN ALWAYS SHOWN //
198     ///////////////////////
199 
200     macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO;
201 
202     /////////////////////////
203     // SCIENTIFIC NOTATION //
204     /////////////////////////
205 
206     if (properties.minimumExponentDigits != -1) {
207         // Scientific notation is required.
208         // This whole section feels like a hack, but it is needed for regression tests.
209         // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
210         if (maxInt > 8) {
211             // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
212             // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
213             maxInt = minInt;
214             macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
215         } else if (maxInt > minInt && minInt > 1) {
216             // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
217             minInt = 1;
218             macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
219         }
220         int engineering = maxInt < 0 ? -1 : maxInt;
221         macros.notation = ScientificNotation(
222                 // Engineering interval:
223                 static_cast<int8_t>(engineering),
224                 // Enforce minimum integer digits (for patterns like "000.00E0"):
225                 (engineering == minInt),
226                 // Minimum exponent digits:
227                 static_cast<digits_t>(properties.minimumExponentDigits),
228                 // Exponent sign always shown:
229                 properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO);
230         // Scientific notation also involves overriding the rounding mode.
231         // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
232         if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
233             // For the purposes of rounding, get the original min/max int/frac, since the local
234             // variables have been manipulated for display purposes.
235             int maxInt_ = properties.maximumIntegerDigits;
236             int minInt_ = properties.minimumIntegerDigits;
237             int minFrac_ = properties.minimumFractionDigits;
238             int maxFrac_ = properties.maximumFractionDigits;
239             if (minInt_ == 0 && maxFrac_ == 0) {
240                 // Patterns like "#E0" and "##E0", which mean no rounding!
241                 macros.precision = Precision::unlimited();
242             } else if (minInt_ == 0 && minFrac_ == 0) {
243                 // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
244                 macros.precision = Precision::constructSignificant(1, maxFrac_ + 1);
245             } else {
246                 int maxSig_ = minInt_ + maxFrac_;
247                 // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
248                 if (maxInt_ > minInt_ && minInt_ > 1) {
249                     minInt_ = 1;
250                 }
251                 int minSig_ = minInt_ + minFrac_;
252                 // To avoid regression, maxSig is not reset when minInt_ set to 1.
253                 // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
254                 macros.precision = Precision::constructSignificant(minSig_, maxSig_);
255             }
256             macros.precision.fRoundingMode = roundingMode;
257         }
258     }
259 
260     //////////////////////
261     // COMPACT NOTATION //
262     //////////////////////
263 
264     if (!properties.compactStyle.isNull()) {
265         if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
266             macros.notation = Notation::compactLong();
267         } else {
268             macros.notation = Notation::compactShort();
269         }
270         // Do not forward the affix provider.
271         macros.affixProvider = nullptr;
272     }
273 
274     /////////////////
275     // MULTIPLIERS //
276     /////////////////
277 
278     macros.scale = scaleFromProperties(properties);
279 
280     //////////////////////
281     // PROPERTY EXPORTS //
282     //////////////////////
283 
284     if (exportedProperties != nullptr) {
285 
286         exportedProperties->currency = currency;
287         exportedProperties->roundingMode = roundingMode;
288         exportedProperties->minimumIntegerDigits = minInt;
289         exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
290 
291         Precision rounding_;
292         if (precision.fType == Precision::PrecisionType::RND_CURRENCY) {
293             rounding_ = precision.withCurrency(currency, status);
294         } else {
295             rounding_ = precision;
296         }
297         int minFrac_ = minFrac;
298         int maxFrac_ = maxFrac;
299         int minSig_ = minSig;
300         int maxSig_ = maxSig;
301         double increment_ = 0.0;
302         if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
303             minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
304             maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
305         } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT
306                 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_ONE
307                 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_FIVE) {
308             increment_ = rounding_.fUnion.increment.fIncrement;
309             minFrac_ = rounding_.fUnion.increment.fMinFrac;
310             maxFrac_ = rounding_.fUnion.increment.fMinFrac;
311         } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
312             minSig_ = rounding_.fUnion.fracSig.fMinSig;
313             maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
314         }
315 
316         exportedProperties->minimumFractionDigits = minFrac_;
317         exportedProperties->maximumFractionDigits = maxFrac_;
318         exportedProperties->minimumSignificantDigits = minSig_;
319         exportedProperties->maximumSignificantDigits = maxSig_;
320         exportedProperties->roundingIncrement = increment_;
321     }
322 
323     return macros;
324 }
325 
326 
setTo(const DecimalFormatProperties & properties,UErrorCode & status)327 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode& status) {
328     fBogus = false;
329 
330     // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
331     // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
332     //
333     // 1) If the explicit setting is present for the field, use it.
334     // 2) Otherwise, follows UTS 35 rules based on the pattern string.
335     //
336     // Importantly, the explicit setters affect only the one field they override.  If you set the positive
337     // prefix, that should not affect the negative prefix.
338 
339     // Convenience: Extract the properties into local variables.
340     // Variables are named with three chars: [p/n][p/s][o/p]
341     // [p/n] => p for positive, n for negative
342     // [p/s] => p for prefix, s for suffix
343     // [o/p] => o for escaped custom override string, p for pattern string
344     UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
345     UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
346     UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
347     UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
348     const UnicodeString& ppp = properties.positivePrefixPattern;
349     const UnicodeString& psp = properties.positiveSuffixPattern;
350     const UnicodeString& npp = properties.negativePrefixPattern;
351     const UnicodeString& nsp = properties.negativeSuffixPattern;
352 
353     if (!properties.positivePrefix.isBogus()) {
354         posPrefix = ppo;
355     } else if (!ppp.isBogus()) {
356         posPrefix = ppp;
357     } else {
358         // UTS 35: Default positive prefix is empty string.
359         posPrefix = u"";
360     }
361 
362     if (!properties.positiveSuffix.isBogus()) {
363         posSuffix = pso;
364     } else if (!psp.isBogus()) {
365         posSuffix = psp;
366     } else {
367         // UTS 35: Default positive suffix is empty string.
368         posSuffix = u"";
369     }
370 
371     if (!properties.negativePrefix.isBogus()) {
372         negPrefix = npo;
373     } else if (!npp.isBogus()) {
374         negPrefix = npp;
375     } else {
376         // UTS 35: Default negative prefix is "-" with positive prefix.
377         // Important: We prepend the "-" to the pattern, not the override!
378         negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
379     }
380 
381     if (!properties.negativeSuffix.isBogus()) {
382         negSuffix = nso;
383     } else if (!nsp.isBogus()) {
384         negSuffix = nsp;
385     } else {
386         // UTS 35: Default negative prefix is the positive prefix.
387         negSuffix = psp.isBogus() ? u"" : psp;
388     }
389 
390     // For declaring if this is a currency pattern, we need to look at the
391     // original pattern, not at any user-specified overrides.
392     isCurrencyPattern = (
393         AffixUtils::hasCurrencySymbols(ppp, status) ||
394         AffixUtils::hasCurrencySymbols(psp, status) ||
395         AffixUtils::hasCurrencySymbols(npp, status) ||
396         AffixUtils::hasCurrencySymbols(nsp, status));
397 }
398 
charAt(int flags,int i) const399 char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
400     return getStringInternal(flags).charAt(i);
401 }
402 
length(int flags) const403 int PropertiesAffixPatternProvider::length(int flags) const {
404     return getStringInternal(flags).length();
405 }
406 
getString(int32_t flags) const407 UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
408     return getStringInternal(flags);
409 }
410 
getStringInternal(int32_t flags) const411 const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
412     bool prefix = (flags & AFFIX_PREFIX) != 0;
413     bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
414     if (prefix && negative) {
415         return negPrefix;
416     } else if (prefix) {
417         return posPrefix;
418     } else if (negative) {
419         return negSuffix;
420     } else {
421         return posSuffix;
422     }
423 }
424 
positiveHasPlusSign() const425 bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
426     // TODO: Change the internal APIs to propagate out the error?
427     ErrorCode localStatus;
428     return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
429            AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
430 }
431 
hasNegativeSubpattern() const432 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
433     return (
434         (negSuffix != posSuffix) ||
435         negPrefix.tempSubString(1) != posPrefix ||
436         negPrefix.charAt(0) != u'-'
437     );
438 }
439 
negativeHasMinusSign() const440 bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
441     ErrorCode localStatus;
442     return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
443            AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
444 }
445 
hasCurrencySign() const446 bool PropertiesAffixPatternProvider::hasCurrencySign() const {
447     return isCurrencyPattern;
448 }
449 
containsSymbolType(AffixPatternType type,UErrorCode & status) const450 bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
451     return AffixUtils::containsType(posPrefix, type, status) ||
452            AffixUtils::containsType(posSuffix, type, status) ||
453            AffixUtils::containsType(negPrefix, type, status) ||
454            AffixUtils::containsType(negSuffix, type, status);
455 }
456 
hasBody() const457 bool PropertiesAffixPatternProvider::hasBody() const {
458     return true;
459 }
460 
461 
setTo(const CurrencyPluralInfo & cpi,const DecimalFormatProperties & properties,UErrorCode & status)462 void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
463                                             const DecimalFormatProperties& properties,
464                                             UErrorCode& status) {
465     // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
466     // because user-specified affix overrides still need to work.
467     fBogus = false;
468     DecimalFormatProperties pluralProperties(properties);
469     for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
470         const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
471         UnicodeString patternString;
472         patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
473         PatternParser::parseToExistingProperties(
474                 patternString,
475                 pluralProperties,
476                 IGNORE_ROUNDING_NEVER,
477                 status);
478         affixesByPlural[plural].setTo(pluralProperties, status);
479     }
480 }
481 
charAt(int32_t flags,int32_t i) const482 char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
483     int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
484     return affixesByPlural[pluralOrdinal].charAt(flags, i);
485 }
486 
length(int32_t flags) const487 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
488     int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
489     return affixesByPlural[pluralOrdinal].length(flags);
490 }
491 
getString(int32_t flags) const492 UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
493     int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
494     return affixesByPlural[pluralOrdinal].getString(flags);
495 }
496 
positiveHasPlusSign() const497 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
498     return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
499 }
500 
hasNegativeSubpattern() const501 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
502     return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
503 }
504 
negativeHasMinusSign() const505 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
506     return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
507 }
508 
hasCurrencySign() const509 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
510     return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
511 }
512 
containsSymbolType(AffixPatternType type,UErrorCode & status) const513 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
514     return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
515 }
516 
hasBody() const517 bool CurrencyPluralInfoAffixProvider::hasBody() const {
518     return affixesByPlural[StandardPlural::OTHER].hasBody();
519 }
520 
521 
522 #endif /* #if !UCONFIG_NO_FORMATTING */
523