Coverage Report

Created: 2018-09-25 14:53

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