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