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 // 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
327void 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
399char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
400 return getStringInternal(flags).charAt(i);
401}
402
403int PropertiesAffixPatternProvider::length(int flags) const {
404 return getStringInternal(flags).length();
405}
406
407UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
408 return getStringInternal(flags);
409}
410
411const 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
425bool 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
432bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
433 return (
434 (negSuffix != posSuffix) ||
435 negPrefix.tempSubString(1) != posPrefix ||
436 negPrefix.charAt(0) != u'-'
437 );
438}
439
440bool 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
446bool PropertiesAffixPatternProvider::hasCurrencySign() const {
447 return isCurrencyPattern;
448}
449
450bool 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
457bool PropertiesAffixPatternProvider::hasBody() const {
458 return true;
459}
460
461
462void 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
482char16_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
487int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
488 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
489 return affixesByPlural[pluralOrdinal].length(flags);
490}
491
492UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
493 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
494 return affixesByPlural[pluralOrdinal].getString(flags);
495}
496
497bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
498 return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
499}
500
501bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
502 return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
503}
504
505bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
506 return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
507}
508
509bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
510 return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
511}
512
513bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
514 return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
515}
516
517bool CurrencyPluralInfoAffixProvider::hasBody() const {
518 return affixesByPlural[StandardPlural::OTHER].hasBody();
519}
520
521
522#endif /* #if !UCONFIG_NO_FORMATTING */
523