• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 && !UPRV_INCOMPLETE_CPP11_SUPPORT
7 
8 #include "umutex.h"
9 #include "ucln_cmn.h"
10 #include "ucln_in.h"
11 #include "number_modifiers.h"
12 
13 using namespace icu;
14 using namespace icu::number;
15 using namespace icu::number::impl;
16 
17 namespace {
18 
19 // TODO: This is copied from simpleformatter.cpp
20 const int32_t ARG_NUM_LIMIT = 0x100;
21 
22 // These are the default currency spacing UnicodeSets in CLDR.
23 // Pre-compute them for performance.
24 // The Java unit test testCurrencySpacingPatternStability() will start failing if these change in CLDR.
25 icu::UInitOnce gDefaultCurrencySpacingInitOnce = U_INITONCE_INITIALIZER;
26 
27 UnicodeSet *UNISET_DIGIT = nullptr;
28 UnicodeSet *UNISET_NOTS = nullptr;
29 
cleanupDefaultCurrencySpacing()30 UBool U_CALLCONV cleanupDefaultCurrencySpacing() {
31     delete UNISET_DIGIT;
32     UNISET_DIGIT = nullptr;
33     delete UNISET_NOTS;
34     UNISET_NOTS = nullptr;
35     return TRUE;
36 }
37 
initDefaultCurrencySpacing(UErrorCode & status)38 void U_CALLCONV initDefaultCurrencySpacing(UErrorCode &status) {
39     ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY_SPACING, cleanupDefaultCurrencySpacing);
40     UNISET_DIGIT = new UnicodeSet(UnicodeString(u"[:digit:]"), status);
41     UNISET_NOTS = new UnicodeSet(UnicodeString(u"[:^S:]"), status);
42     if (UNISET_DIGIT == nullptr || UNISET_NOTS == nullptr) {
43         status = U_MEMORY_ALLOCATION_ERROR;
44         return;
45     }
46     UNISET_DIGIT->freeze();
47     UNISET_NOTS->freeze();
48 }
49 
50 }  // namespace
51 
52 
apply(NumberStringBuilder & output,int leftIndex,int rightIndex,UErrorCode & status) const53 int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
54                                      UErrorCode &status) const {
55     // Insert the suffix first since inserting the prefix will change the rightIndex
56     int length = output.insert(rightIndex, fSuffix, fField, status);
57     length += output.insert(leftIndex, fPrefix, fField, status);
58     return length;
59 }
60 
getPrefixLength(UErrorCode & status) const61 int32_t ConstantAffixModifier::getPrefixLength(UErrorCode &status) const {
62     (void)status;
63     return fPrefix.length();
64 }
65 
getCodePointCount(UErrorCode & status) const66 int32_t ConstantAffixModifier::getCodePointCount(UErrorCode &status) const {
67     (void)status;
68     return fPrefix.countChar32() + fSuffix.countChar32();
69 }
70 
isStrong() const71 bool ConstantAffixModifier::isStrong() const {
72     return fStrong;
73 }
74 
SimpleModifier(const SimpleFormatter & simpleFormatter,Field field,bool strong)75 SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong)
76         : fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong) {
77     U_ASSERT(1 ==
78              SimpleFormatter::getArgumentLimit(fCompiledPattern.getBuffer(), fCompiledPattern.length()));
79     if (fCompiledPattern.charAt(1) != 0) {
80         fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
81         fSuffixOffset = 3 + fPrefixLength;
82     } else {
83         fPrefixLength = 0;
84         fSuffixOffset = 2;
85     }
86     if (3 + fPrefixLength < fCompiledPattern.length()) {
87         fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT;
88     } else {
89         fSuffixLength = 0;
90     }
91 }
92 
SimpleModifier()93 SimpleModifier::SimpleModifier()
94         : fField(UNUM_FIELD_COUNT), fStrong(false), fPrefixLength(0), fSuffixLength(0) {
95 }
96 
apply(NumberStringBuilder & output,int leftIndex,int rightIndex,UErrorCode & status) const97 int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
98                               UErrorCode &status) const {
99     return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
100 }
101 
getPrefixLength(UErrorCode & status) const102 int32_t SimpleModifier::getPrefixLength(UErrorCode &status) const {
103     (void)status;
104     return fPrefixLength;
105 }
106 
getCodePointCount(UErrorCode & status) const107 int32_t SimpleModifier::getCodePointCount(UErrorCode &status) const {
108     (void)status;
109     int32_t count = 0;
110     if (fPrefixLength > 0) {
111         count += fCompiledPattern.countChar32(2, fPrefixLength);
112     }
113     if (fSuffixLength > 0) {
114         count += fCompiledPattern.countChar32(1 + fSuffixOffset, fSuffixLength);
115     }
116     return count;
117 }
118 
isStrong() const119 bool SimpleModifier::isStrong() const {
120     return fStrong;
121 }
122 
123 int32_t
formatAsPrefixSuffix(NumberStringBuilder & result,int32_t startIndex,int32_t endIndex,Field field,UErrorCode & status) const124 SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
125                                      Field field, UErrorCode &status) const {
126     if (fPrefixLength > 0) {
127         result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
128     }
129     if (fSuffixLength > 0) {
130         result.insert(
131                 endIndex + fPrefixLength,
132                 fCompiledPattern,
133                 1 + fSuffixOffset,
134                 1 + fSuffixOffset + fSuffixLength,
135                 field,
136                 status);
137     }
138     return fPrefixLength + fSuffixLength;
139 }
140 
apply(NumberStringBuilder & output,int leftIndex,int rightIndex,UErrorCode & status) const141 int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
142                                           UErrorCode &status) const {
143     // Insert the suffix first since inserting the prefix will change the rightIndex
144     int32_t length = output.insert(rightIndex, fSuffix, status);
145     length += output.insert(leftIndex, fPrefix, status);
146     return length;
147 }
148 
getPrefixLength(UErrorCode & status) const149 int32_t ConstantMultiFieldModifier::getPrefixLength(UErrorCode &status) const {
150     (void)status;
151     return fPrefix.length();
152 }
153 
getCodePointCount(UErrorCode & status) const154 int32_t ConstantMultiFieldModifier::getCodePointCount(UErrorCode &status) const {
155     (void)status;
156     return fPrefix.codePointCount() + fSuffix.codePointCount();
157 }
158 
isStrong() const159 bool ConstantMultiFieldModifier::isStrong() const {
160     return fStrong;
161 }
162 
CurrencySpacingEnabledModifier(const NumberStringBuilder & prefix,const NumberStringBuilder & suffix,bool strong,const DecimalFormatSymbols & symbols,UErrorCode & status)163 CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
164                                                                const NumberStringBuilder &suffix,
165                                                                bool strong,
166                                                                const DecimalFormatSymbols &symbols,
167                                                                UErrorCode &status)
168         : ConstantMultiFieldModifier(prefix, suffix, strong) {
169     // Check for currency spacing. Do not build the UnicodeSets unless there is
170     // a currency code point at a boundary.
171     if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == UNUM_CURRENCY_FIELD) {
172         int prefixCp = prefix.getLastCodePoint();
173         UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX, status);
174         if (prefixUnicodeSet.contains(prefixCp)) {
175             fAfterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX, status);
176             fAfterPrefixUnicodeSet.freeze();
177             fAfterPrefixInsert = getInsertString(symbols, PREFIX, status);
178         } else {
179             fAfterPrefixUnicodeSet.setToBogus();
180             fAfterPrefixInsert.setToBogus();
181         }
182     } else {
183         fAfterPrefixUnicodeSet.setToBogus();
184         fAfterPrefixInsert.setToBogus();
185     }
186     if (suffix.length() > 0 && suffix.fieldAt(0) == UNUM_CURRENCY_FIELD) {
187         int suffixCp = suffix.getLastCodePoint();
188         UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX, status);
189         if (suffixUnicodeSet.contains(suffixCp)) {
190             fBeforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX, status);
191             fBeforeSuffixUnicodeSet.freeze();
192             fBeforeSuffixInsert = getInsertString(symbols, SUFFIX, status);
193         } else {
194             fBeforeSuffixUnicodeSet.setToBogus();
195             fBeforeSuffixInsert.setToBogus();
196         }
197     } else {
198         fBeforeSuffixUnicodeSet.setToBogus();
199         fBeforeSuffixInsert.setToBogus();
200     }
201 }
202 
apply(NumberStringBuilder & output,int leftIndex,int rightIndex,UErrorCode & status) const203 int32_t CurrencySpacingEnabledModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
204                                               UErrorCode &status) const {
205     // Currency spacing logic
206     int length = 0;
207     if (rightIndex - leftIndex > 0 && !fAfterPrefixUnicodeSet.isBogus() &&
208         fAfterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
209         // TODO: Should we use the CURRENCY field here?
210         length += output.insert(leftIndex, fAfterPrefixInsert, UNUM_FIELD_COUNT, status);
211     }
212     if (rightIndex - leftIndex > 0 && !fBeforeSuffixUnicodeSet.isBogus() &&
213         fBeforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
214         // TODO: Should we use the CURRENCY field here?
215         length += output.insert(rightIndex + length, fBeforeSuffixInsert, UNUM_FIELD_COUNT, status);
216     }
217 
218     // Call super for the remaining logic
219     length += ConstantMultiFieldModifier::apply(output, leftIndex, rightIndex + length, status);
220     return length;
221 }
222 
223 int32_t
applyCurrencySpacing(NumberStringBuilder & output,int32_t prefixStart,int32_t prefixLen,int32_t suffixStart,int32_t suffixLen,const DecimalFormatSymbols & symbols,UErrorCode & status)224 CurrencySpacingEnabledModifier::applyCurrencySpacing(NumberStringBuilder &output, int32_t prefixStart,
225                                                      int32_t prefixLen, int32_t suffixStart,
226                                                      int32_t suffixLen,
227                                                      const DecimalFormatSymbols &symbols,
228                                                      UErrorCode &status) {
229     int length = 0;
230     bool hasPrefix = (prefixLen > 0);
231     bool hasSuffix = (suffixLen > 0);
232     bool hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
233     if (hasPrefix && hasNumber) {
234         length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols, status);
235     }
236     if (hasSuffix && hasNumber) {
237         length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols, status);
238     }
239     return length;
240 }
241 
242 int32_t
applyCurrencySpacingAffix(NumberStringBuilder & output,int32_t index,EAffix affix,const DecimalFormatSymbols & symbols,UErrorCode & status)243 CurrencySpacingEnabledModifier::applyCurrencySpacingAffix(NumberStringBuilder &output, int32_t index,
244                                                           EAffix affix,
245                                                           const DecimalFormatSymbols &symbols,
246                                                           UErrorCode &status) {
247     // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
248     // This works even if the last code point in the prefix is 2 code units because the
249     // field value gets populated to both indices in the field array.
250     Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
251     if (affixField != UNUM_CURRENCY_FIELD) {
252         return 0;
253     }
254     int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index);
255     UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix, status);
256     if (!affixUniset.contains(affixCp)) {
257         return 0;
258     }
259     int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index);
260     UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix, status);
261     if (!numberUniset.contains(numberCp)) {
262         return 0;
263     }
264     UnicodeString spacingString = getInsertString(symbols, affix, status);
265 
266     // NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
267     // It would be more efficient if this could be done before affixes were attached,
268     // so that it could be prepended/appended instead of inserted.
269     // However, the build code path is more efficient, and this is the most natural
270     // place to put currency spacing in the non-build code path.
271     // TODO: Should we use the CURRENCY field here?
272     return output.insert(index, spacingString, UNUM_FIELD_COUNT, status);
273 }
274 
275 UnicodeSet
getUnicodeSet(const DecimalFormatSymbols & symbols,EPosition position,EAffix affix,UErrorCode & status)276 CurrencySpacingEnabledModifier::getUnicodeSet(const DecimalFormatSymbols &symbols, EPosition position,
277                                               EAffix affix, UErrorCode &status) {
278     // Ensure the static defaults are initialized:
279     umtx_initOnce(gDefaultCurrencySpacingInitOnce, &initDefaultCurrencySpacing, status);
280     if (U_FAILURE(status)) {
281         return UnicodeSet();
282     }
283 
284     const UnicodeString& pattern = symbols.getPatternForCurrencySpacing(
285             position == IN_CURRENCY ? UNUM_CURRENCY_MATCH : UNUM_CURRENCY_SURROUNDING_MATCH,
286             affix == SUFFIX,
287             status);
288     if (pattern.compare(u"[:digit:]", -1) == 0) {
289         return *UNISET_DIGIT;
290     } else if (pattern.compare(u"[:^S:]", -1) == 0) {
291         return *UNISET_NOTS;
292     } else {
293         return UnicodeSet(pattern, status);
294     }
295 }
296 
297 UnicodeString
getInsertString(const DecimalFormatSymbols & symbols,EAffix affix,UErrorCode & status)298 CurrencySpacingEnabledModifier::getInsertString(const DecimalFormatSymbols &symbols, EAffix affix,
299                                                 UErrorCode &status) {
300     return symbols.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, affix == SUFFIX, status);
301 }
302 
303 #endif /* #if !UCONFIG_NO_FORMATTING */
304