blob: 872b97010d74b7f4fa0847b10ebbedfce0f3fb87 [file] [log] [blame]
Jungshik Shinb3189662017-11-07 11:18:34 -08001// © 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
13using namespace icu;
14using namespace icu::number;
15using namespace icu::number::impl;
16
17namespace {
18
19// TODO: This is copied from simpleformatter.cpp
20const 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.
25icu::UInitOnce gDefaultCurrencySpacingInitOnce = U_INITONCE_INITIALIZER;
26
27UnicodeSet *UNISET_DIGIT = nullptr;
28UnicodeSet *UNISET_NOTS = nullptr;
29
30UBool U_CALLCONV cleanupDefaultCurrencySpacing() {
31 delete UNISET_DIGIT;
32 UNISET_DIGIT = nullptr;
33 delete UNISET_NOTS;
34 UNISET_NOTS = nullptr;
35 return TRUE;
36}
37
38void 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
53int32_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
61int32_t ConstantAffixModifier::getPrefixLength(UErrorCode &status) const {
62 (void)status;
63 return fPrefix.length();
64}
65
66int32_t ConstantAffixModifier::getCodePointCount(UErrorCode &status) const {
67 (void)status;
68 return fPrefix.countChar32() + fSuffix.countChar32();
69}
70
71bool ConstantAffixModifier::isStrong() const {
72 return fStrong;
73}
74
75SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong)
76 : fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong) {
Jungshik Shinf61e46d2018-05-04 13:00:45 -070077 int32_t argLimit = SimpleFormatter::getArgumentLimit(
78 fCompiledPattern.getBuffer(), fCompiledPattern.length());
79 if (argLimit == 0) {
80 // No arguments in compiled pattern
Jungshik Shinb3189662017-11-07 11:18:34 -080081 fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
Jungshik Shinf61e46d2018-05-04 13:00:45 -070082 U_ASSERT(2 + fPrefixLength == fCompiledPattern.length());
83 // Set suffixOffset = -1 to indicate no arguments in compiled pattern.
84 fSuffixOffset = -1;
Jungshik Shinb3189662017-11-07 11:18:34 -080085 fSuffixLength = 0;
Jungshik Shinf61e46d2018-05-04 13:00:45 -070086 } else {
87 U_ASSERT(argLimit == 1);
88 if (fCompiledPattern.charAt(1) != 0) {
89 fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
90 fSuffixOffset = 3 + fPrefixLength;
91 } else {
92 fPrefixLength = 0;
93 fSuffixOffset = 2;
94 }
95 if (3 + fPrefixLength < fCompiledPattern.length()) {
96 fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT;
97 } else {
98 fSuffixLength = 0;
99 }
Jungshik Shinb3189662017-11-07 11:18:34 -0800100 }
101}
102
103SimpleModifier::SimpleModifier()
104 : fField(UNUM_FIELD_COUNT), fStrong(false), fPrefixLength(0), fSuffixLength(0) {
105}
106
107int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
108 UErrorCode &status) const {
109 return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
110}
111
112int32_t SimpleModifier::getPrefixLength(UErrorCode &status) const {
113 (void)status;
114 return fPrefixLength;
115}
116
117int32_t SimpleModifier::getCodePointCount(UErrorCode &status) const {
118 (void)status;
119 int32_t count = 0;
120 if (fPrefixLength > 0) {
121 count += fCompiledPattern.countChar32(2, fPrefixLength);
122 }
123 if (fSuffixLength > 0) {
124 count += fCompiledPattern.countChar32(1 + fSuffixOffset, fSuffixLength);
125 }
126 return count;
127}
128
129bool SimpleModifier::isStrong() const {
130 return fStrong;
131}
132
133int32_t
134SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
135 Field field, UErrorCode &status) const {
Jungshik Shinf61e46d2018-05-04 13:00:45 -0700136 if (fSuffixOffset == -1) {
137 // There is no argument for the inner number; overwrite the entire segment with our string.
138 return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
139 } else {
140 if (fPrefixLength > 0) {
141 result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
142 }
143 if (fSuffixLength > 0) {
144 result.insert(
145 endIndex + fPrefixLength,
146 fCompiledPattern,
147 1 + fSuffixOffset,
148 1 + fSuffixOffset + fSuffixLength,
149 field,
150 status);
151 }
152 return fPrefixLength + fSuffixLength;
Jungshik Shinb3189662017-11-07 11:18:34 -0800153 }
Jungshik Shinb3189662017-11-07 11:18:34 -0800154}
155
156int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
157 UErrorCode &status) const {
Jungshik Shinf61e46d2018-05-04 13:00:45 -0700158 int32_t length = output.insert(leftIndex, fPrefix, status);
159 if (fOverwrite) {
160 length += output.splice(
161 leftIndex + length,
162 rightIndex + length,
163 UnicodeString(), 0, 0,
164 UNUM_FIELD_COUNT, status);
165 }
166 length += output.insert(rightIndex + length, fSuffix, status);
Jungshik Shinb3189662017-11-07 11:18:34 -0800167 return length;
168}
169
170int32_t ConstantMultiFieldModifier::getPrefixLength(UErrorCode &status) const {
171 (void)status;
172 return fPrefix.length();
173}
174
175int32_t ConstantMultiFieldModifier::getCodePointCount(UErrorCode &status) const {
176 (void)status;
177 return fPrefix.codePointCount() + fSuffix.codePointCount();
178}
179
180bool ConstantMultiFieldModifier::isStrong() const {
181 return fStrong;
182}
183
184CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
185 const NumberStringBuilder &suffix,
Jungshik Shinf61e46d2018-05-04 13:00:45 -0700186 bool overwrite,
Jungshik Shinb3189662017-11-07 11:18:34 -0800187 bool strong,
188 const DecimalFormatSymbols &symbols,
189 UErrorCode &status)
Jungshik Shinf61e46d2018-05-04 13:00:45 -0700190 : ConstantMultiFieldModifier(prefix, suffix, overwrite, strong) {
Jungshik Shinb3189662017-11-07 11:18:34 -0800191 // Check for currency spacing. Do not build the UnicodeSets unless there is
192 // a currency code point at a boundary.
193 if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == UNUM_CURRENCY_FIELD) {
194 int prefixCp = prefix.getLastCodePoint();
195 UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX, status);
196 if (prefixUnicodeSet.contains(prefixCp)) {
197 fAfterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX, status);
198 fAfterPrefixUnicodeSet.freeze();
199 fAfterPrefixInsert = getInsertString(symbols, PREFIX, status);
200 } else {
201 fAfterPrefixUnicodeSet.setToBogus();
202 fAfterPrefixInsert.setToBogus();
203 }
204 } else {
205 fAfterPrefixUnicodeSet.setToBogus();
206 fAfterPrefixInsert.setToBogus();
207 }
208 if (suffix.length() > 0 && suffix.fieldAt(0) == UNUM_CURRENCY_FIELD) {
209 int suffixCp = suffix.getLastCodePoint();
210 UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX, status);
211 if (suffixUnicodeSet.contains(suffixCp)) {
212 fBeforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX, status);
213 fBeforeSuffixUnicodeSet.freeze();
214 fBeforeSuffixInsert = getInsertString(symbols, SUFFIX, status);
215 } else {
216 fBeforeSuffixUnicodeSet.setToBogus();
217 fBeforeSuffixInsert.setToBogus();
218 }
219 } else {
220 fBeforeSuffixUnicodeSet.setToBogus();
221 fBeforeSuffixInsert.setToBogus();
222 }
223}
224
225int32_t CurrencySpacingEnabledModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
226 UErrorCode &status) const {
227 // Currency spacing logic
228 int length = 0;
229 if (rightIndex - leftIndex > 0 && !fAfterPrefixUnicodeSet.isBogus() &&
230 fAfterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
231 // TODO: Should we use the CURRENCY field here?
232 length += output.insert(leftIndex, fAfterPrefixInsert, UNUM_FIELD_COUNT, status);
233 }
234 if (rightIndex - leftIndex > 0 && !fBeforeSuffixUnicodeSet.isBogus() &&
235 fBeforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
236 // TODO: Should we use the CURRENCY field here?
237 length += output.insert(rightIndex + length, fBeforeSuffixInsert, UNUM_FIELD_COUNT, status);
238 }
239
240 // Call super for the remaining logic
241 length += ConstantMultiFieldModifier::apply(output, leftIndex, rightIndex + length, status);
242 return length;
243}
244
245int32_t
246CurrencySpacingEnabledModifier::applyCurrencySpacing(NumberStringBuilder &output, int32_t prefixStart,
247 int32_t prefixLen, int32_t suffixStart,
248 int32_t suffixLen,
249 const DecimalFormatSymbols &symbols,
250 UErrorCode &status) {
251 int length = 0;
252 bool hasPrefix = (prefixLen > 0);
253 bool hasSuffix = (suffixLen > 0);
254 bool hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
255 if (hasPrefix && hasNumber) {
256 length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols, status);
257 }
258 if (hasSuffix && hasNumber) {
259 length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols, status);
260 }
261 return length;
262}
263
264int32_t
265CurrencySpacingEnabledModifier::applyCurrencySpacingAffix(NumberStringBuilder &output, int32_t index,
266 EAffix affix,
267 const DecimalFormatSymbols &symbols,
268 UErrorCode &status) {
269 // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
270 // This works even if the last code point in the prefix is 2 code units because the
271 // field value gets populated to both indices in the field array.
272 Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
273 if (affixField != UNUM_CURRENCY_FIELD) {
274 return 0;
275 }
276 int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index);
277 UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix, status);
278 if (!affixUniset.contains(affixCp)) {
279 return 0;
280 }
281 int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index);
282 UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix, status);
283 if (!numberUniset.contains(numberCp)) {
284 return 0;
285 }
286 UnicodeString spacingString = getInsertString(symbols, affix, status);
287
288 // NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
289 // It would be more efficient if this could be done before affixes were attached,
290 // so that it could be prepended/appended instead of inserted.
291 // However, the build code path is more efficient, and this is the most natural
292 // place to put currency spacing in the non-build code path.
293 // TODO: Should we use the CURRENCY field here?
294 return output.insert(index, spacingString, UNUM_FIELD_COUNT, status);
295}
296
297UnicodeSet
298CurrencySpacingEnabledModifier::getUnicodeSet(const DecimalFormatSymbols &symbols, EPosition position,
299 EAffix affix, UErrorCode &status) {
300 // Ensure the static defaults are initialized:
301 umtx_initOnce(gDefaultCurrencySpacingInitOnce, &initDefaultCurrencySpacing, status);
302 if (U_FAILURE(status)) {
303 return UnicodeSet();
304 }
305
306 const UnicodeString& pattern = symbols.getPatternForCurrencySpacing(
307 position == IN_CURRENCY ? UNUM_CURRENCY_MATCH : UNUM_CURRENCY_SURROUNDING_MATCH,
308 affix == SUFFIX,
309 status);
310 if (pattern.compare(u"[:digit:]", -1) == 0) {
311 return *UNISET_DIGIT;
312 } else if (pattern.compare(u"[:^S:]", -1) == 0) {
313 return *UNISET_NOTS;
314 } else {
315 return UnicodeSet(pattern, status);
316 }
317}
318
319UnicodeString
320CurrencySpacingEnabledModifier::getInsertString(const DecimalFormatSymbols &symbols, EAffix affix,
321 UErrorCode &status) {
322 return symbols.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, affix == SUFFIX, status);
323}
324
325#endif /* #if !UCONFIG_NO_FORMATTING */