Coverage Report

Created: 2026-02-05 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/number_patternmodifier.cpp
Line
Count
Source
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 "cstring.h"
9
#include "number_patternmodifier.h"
10
#include "unicode/dcfmtsym.h"
11
#include "unicode/ucurr.h"
12
#include "unicode/unistr.h"
13
#include "number_microprops.h"
14
15
using namespace icu;
16
using namespace icu::number;
17
using namespace icu::number::impl;
18
19
20
5.57M
AffixPatternProvider::~AffixPatternProvider() = default;
21
22
23
MutablePatternModifier::MutablePatternModifier(bool isStrong)
24
120k
        : fStrong(isStrong) {}
25
26
122k
void MutablePatternModifier::setPatternInfo(const AffixPatternProvider* patternInfo, Field field) {
27
122k
    fPatternInfo = patternInfo;
28
122k
    fField = field;
29
122k
}
30
31
void MutablePatternModifier::setPatternAttributes(
32
        UNumberSignDisplay signDisplay,
33
        bool perMille,
34
120k
        bool approximately) {
35
120k
    fSignDisplay = signDisplay;
36
120k
    fPerMilleReplacesPercent = perMille;
37
120k
    fApproximately = approximately;
38
120k
}
39
40
void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols,
41
                                        const CurrencyUnit& currency,
42
                                        const UNumberUnitWidth unitWidth,
43
                                        const PluralRules* rules,
44
120k
                                        UErrorCode& status) {
45
120k
    U_ASSERT((rules != nullptr) == needsPlurals());
46
120k
    fSymbols = symbols;
47
120k
    fCurrencySymbols = {currency, symbols->getLocale(), *symbols, status};
48
120k
    fUnitWidth = unitWidth;
49
120k
    fRules = rules;
50
120k
}
51
52
448k
void MutablePatternModifier::setNumberProperties(Signum signum, StandardPlural::Form plural) {
53
448k
    fSignum = signum;
54
448k
    fPlural = plural;
55
448k
}
56
57
20.4k
bool MutablePatternModifier::needsPlurals() const {
58
20.4k
    UErrorCode statusLocal = U_ZERO_ERROR;
59
20.4k
    return fPatternInfo->containsSymbolType(AffixPatternType::TYPE_CURRENCY_TRIPLE, statusLocal);
60
    // Silently ignore any error codes.
61
20.4k
}
62
63
109k
AdoptingSignumModifierStore MutablePatternModifier::createImmutableForPlural(StandardPlural::Form plural, UErrorCode& status) {
64
109k
    AdoptingSignumModifierStore pm;
65
66
109k
    setNumberProperties(SIGNUM_POS, plural);
67
109k
    pm.adoptModifier(SIGNUM_POS, createConstantModifier(status));
68
109k
    setNumberProperties(SIGNUM_NEG_ZERO, plural);
69
109k
    pm.adoptModifier(SIGNUM_NEG_ZERO, createConstantModifier(status));
70
109k
    setNumberProperties(SIGNUM_POS_ZERO, plural);
71
109k
    pm.adoptModifier(SIGNUM_POS_ZERO, createConstantModifier(status));
72
109k
    setNumberProperties(SIGNUM_NEG, plural);
73
109k
    pm.adoptModifier(SIGNUM_NEG, createConstantModifier(status));
74
75
109k
    return pm;
76
109k
}
77
78
539
ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) {
79
    // TODO: Move StandardPlural VALUES to standardplural.h
80
539
    static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = {
81
539
            StandardPlural::Form::ZERO,
82
539
            StandardPlural::Form::ONE,
83
539
            StandardPlural::Form::TWO,
84
539
            StandardPlural::Form::FEW,
85
539
            StandardPlural::Form::MANY,
86
539
            StandardPlural::Form::OTHER};
87
88
539
    auto* pm = new AdoptingModifierStore();
89
539
    if (pm == nullptr) {
90
0
        status = U_MEMORY_ALLOCATION_ERROR;
91
0
        return nullptr;
92
0
    }
93
94
539
    if (needsPlurals()) {
95
        // Slower path when we require the plural keyword.
96
0
        for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
97
0
            pm->adoptSignumModifierStore(plural, createImmutableForPlural(plural, status));
98
0
        }
99
0
        if (U_FAILURE(status)) {
100
0
            delete pm;
101
0
            return nullptr;
102
0
        }
103
0
        return new ImmutablePatternModifier(pm, fRules);  // adopts pm
104
539
    } else {
105
        // Faster path when plural keyword is not needed.
106
539
        pm->adoptSignumModifierStoreNoPlural(createImmutableForPlural(StandardPlural::Form::COUNT, status));
107
539
        if (U_FAILURE(status)) {
108
0
            delete pm;
109
0
            return nullptr;
110
0
        }
111
539
        return new ImmutablePatternModifier(pm, nullptr);  // adopts pm
112
539
    }
113
539
}
114
115
437k
ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErrorCode& status) {
116
437k
    FormattedStringBuilder a;
117
437k
    FormattedStringBuilder b;
118
437k
    insertPrefix(a, 0, status);
119
437k
    insertSuffix(b, 0, status);
120
437k
    if (fPatternInfo->hasCurrencySign()) {
121
0
        return new CurrencySpacingEnabledModifier(
122
0
                a, b, !fPatternInfo->hasBody(), fStrong, *fSymbols, status);
123
437k
    } else {
124
437k
        return new ConstantMultiFieldModifier(a, b, !fPatternInfo->hasBody(), fStrong);
125
437k
    }
126
437k
}
127
128
ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules)
129
539
        : pm(pm), rules(rules), parent(nullptr) {}
130
131
void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroProps& micros,
132
1.67M
                                               UErrorCode& status) const {
133
1.67M
    parent->processQuantity(quantity, micros, status);
134
1.67M
    micros.rounder.apply(quantity, status);
135
1.67M
    if (micros.modMiddle != nullptr) {
136
0
        return;
137
0
    }
138
1.67M
    applyToMicros(micros, quantity, status);
139
1.67M
}
140
141
void ImmutablePatternModifier::applyToMicros(
142
1.67M
        MicroProps& micros, const DecimalQuantity& quantity, UErrorCode& status) const {
143
1.67M
    if (rules == nullptr) {
144
1.67M
        micros.modMiddle = pm->getModifierWithoutPlural(quantity.signum());
145
1.67M
    } else {
146
0
        StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, rules, quantity, status);
147
0
        micros.modMiddle = pm->getModifier(quantity.signum(), pluralForm);
148
0
    }
149
1.67M
}
150
151
0
const Modifier* ImmutablePatternModifier::getModifier(Signum signum, StandardPlural::Form plural) const {
152
0
    if (rules == nullptr) {
153
0
        return pm->getModifierWithoutPlural(signum);
154
0
    } else {
155
0
        return pm->getModifier(signum, plural);
156
0
    }
157
0
}
158
159
539
void ImmutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
160
539
    this->parent = parent;
161
539
}
162
163
164
/** Used by the unsafe code path. */
165
10.8k
MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
166
10.8k
    fParent = parent;
167
10.8k
    return *this;
168
10.8k
}
169
170
void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& micros,
171
10.8k
                                             UErrorCode& status) const {
172
10.8k
    fParent->processQuantity(fq, micros, status);
173
10.8k
    micros.rounder.apply(fq, status);
174
10.8k
    if (micros.modMiddle != nullptr) {
175
2.43k
        return;
176
2.43k
    }
177
    // The unsafe code path performs self-mutation, so we need a const_cast.
178
    // This method needs to be const because it overrides a const method in the parent class.
179
8.45k
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
180
8.45k
    if (needsPlurals()) {
181
0
        StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, fRules, fq, status);
182
0
        nonConstThis->setNumberProperties(fq.signum(), pluralForm);
183
8.45k
    } else {
184
8.45k
        nonConstThis->setNumberProperties(fq.signum(), StandardPlural::Form::COUNT);
185
8.45k
    }
186
8.45k
    micros.modMiddle = this;
187
8.45k
}
188
189
int32_t MutablePatternModifier::apply(FormattedStringBuilder& output, int32_t leftIndex, int32_t rightIndex,
190
10.8k
                                      UErrorCode& status) const {
191
    // The unsafe code path performs self-mutation, so we need a const_cast.
192
    // This method needs to be const because it overrides a const method in the parent class.
193
10.8k
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
194
10.8k
    int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status);
195
10.8k
    int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status);
196
    // If the pattern had no decimal stem body (like #,##0.00), overwrite the value.
197
10.8k
    int32_t overwriteLen = 0;
198
10.8k
    if (!fPatternInfo->hasBody()) {
199
18
        overwriteLen = output.splice(
200
18
                leftIndex + prefixLen,
201
18
                rightIndex + prefixLen,
202
18
                UnicodeString(),
203
18
                0,
204
18
                0,
205
18
                kUndefinedField,
206
18
                status);
207
18
    }
208
10.8k
    CurrencySpacingEnabledModifier::applyCurrencySpacing(
209
10.8k
            output,
210
10.8k
            leftIndex,
211
10.8k
            prefixLen,
212
10.8k
            rightIndex + overwriteLen + prefixLen,
213
10.8k
            suffixLen,
214
10.8k
            *fSymbols,
215
10.8k
            status);
216
10.8k
    return prefixLen + overwriteLen + suffixLen;
217
10.8k
}
218
219
0
int32_t MutablePatternModifier::getPrefixLength() const {
220
    // The unsafe code path performs self-mutation, so we need a const_cast.
221
    // This method needs to be const because it overrides a const method in the parent class.
222
0
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
223
224
    // Enter and exit CharSequence Mode to get the length.
225
0
    UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
226
0
    nonConstThis->prepareAffix(true);
227
0
    int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // prefix length
228
0
    return result;
229
0
}
230
231
0
int32_t MutablePatternModifier::getCodePointCount() const {
232
    // The unsafe code path performs self-mutation, so we need a const_cast.
233
    // This method needs to be const because it overrides a const method in the parent class.
234
0
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
235
236
    // Render the affixes to get the length
237
0
    UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
238
0
    nonConstThis->prepareAffix(true);
239
0
    int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // prefix length
240
0
    nonConstThis->prepareAffix(false);
241
0
    result += AffixUtils::unescapedCodePointCount(currentAffix, *this, status);  // suffix length
242
0
    return result;
243
0
}
244
245
0
bool MutablePatternModifier::isStrong() const {
246
0
    return fStrong;
247
0
}
248
249
0
bool MutablePatternModifier::containsField(Field field) const {
250
0
    (void)field;
251
    // This method is not currently used.
252
0
    UPRV_UNREACHABLE_EXIT;
253
0
}
254
255
0
void MutablePatternModifier::getParameters(Parameters& output) const {
256
0
    (void)output;
257
    // This method is not currently used.
258
0
    UPRV_UNREACHABLE_EXIT;
259
0
}
260
261
0
bool MutablePatternModifier::strictEquals(const Modifier& other) const {
262
0
    (void)other;
263
    // This method is not currently used.
264
0
    UPRV_UNREACHABLE_EXIT;
265
0
}
266
267
448k
int32_t MutablePatternModifier::insertPrefix(FormattedStringBuilder& sb, int position, UErrorCode& status) {
268
448k
    prepareAffix(true);
269
448k
    int32_t length = AffixUtils::unescape(currentAffix, sb, position, *this, fField, status);
270
448k
    return length;
271
448k
}
272
273
448k
int32_t MutablePatternModifier::insertSuffix(FormattedStringBuilder& sb, int position, UErrorCode& status) {
274
448k
    prepareAffix(false);
275
448k
    int32_t length = AffixUtils::unescape(currentAffix, sb, position, *this, fField, status);
276
448k
    return length;
277
448k
}
278
279
/** This method contains the heart of the logic for rendering LDML affix strings. */
280
896k
void MutablePatternModifier::prepareAffix(bool isPrefix) {
281
896k
    PatternStringUtils::patternInfoToStringBuilder(
282
896k
            *fPatternInfo,
283
896k
            isPrefix,
284
896k
            PatternStringUtils::resolveSignDisplay(fSignDisplay, fSignum),
285
896k
            fApproximately,
286
896k
            fPlural,
287
896k
            fPerMilleReplacesPercent,
288
896k
            false, // dropCurrencySymbols
289
896k
            currentAffix);
290
896k
}
291
292
221k
UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
293
221k
    UErrorCode localStatus = U_ZERO_ERROR;
294
221k
    switch (type) {
295
112k
        case AffixPatternType::TYPE_MINUS_SIGN:
296
112k
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol);
297
108k
        case AffixPatternType::TYPE_PLUS_SIGN:
298
108k
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
299
0
        case AffixPatternType::TYPE_APPROXIMATELY_SIGN:
300
0
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kApproximatelySignSymbol);
301
50
        case AffixPatternType::TYPE_PERCENT:
302
50
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
303
0
        case AffixPatternType::TYPE_PERMILLE:
304
0
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
305
0
        case AffixPatternType::TYPE_CURRENCY_SINGLE:
306
0
            return getCurrencySymbolForUnitWidth(localStatus);
307
0
        case AffixPatternType::TYPE_CURRENCY_DOUBLE:
308
0
            return fCurrencySymbols.getIntlCurrencySymbol(localStatus);
309
0
        case AffixPatternType::TYPE_CURRENCY_TRIPLE:
310
            // NOTE: This is the code path only for patterns containing "¤¤¤".
311
            // Plural currencies set via the API are formatted in LongNameHandler.
312
            // This code path is used by DecimalFormat via CurrencyPluralInfo.
313
0
            U_ASSERT(fPlural != StandardPlural::Form::COUNT);
314
0
            return fCurrencySymbols.getPluralName(fPlural, localStatus);
315
0
        case AffixPatternType::TYPE_CURRENCY_QUAD:
316
0
            return UnicodeString(u"\uFFFD");
317
0
        case AffixPatternType::TYPE_CURRENCY_QUINT:
318
0
            return UnicodeString(u"\uFFFD");
319
0
        default:
320
0
            UPRV_UNREACHABLE_EXIT;
321
221k
    }
322
221k
}
323
324
0
UnicodeString MutablePatternModifier::getCurrencySymbolForUnitWidth(UErrorCode& status) const {
325
0
    switch (fUnitWidth) {
326
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW:
327
0
        return fCurrencySymbols.getNarrowCurrencySymbol(status);
328
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT:
329
0
        return fCurrencySymbols.getCurrencySymbol(status);
330
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE:
331
0
        return fCurrencySymbols.getIntlCurrencySymbol(status);
332
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_FORMAL:
333
0
        return fCurrencySymbols.getFormalCurrencySymbol(status);
334
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_VARIANT:
335
0
        return fCurrencySymbols.getVariantCurrencySymbol(status);
336
0
    case UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN:
337
0
        return {};
338
0
    default:
339
0
        return fCurrencySymbols.getCurrencySymbol(status);
340
0
    }
341
0
}
342
343
0
UnicodeString MutablePatternModifier::toUnicodeString() const {
344
    // Never called by AffixUtils
345
0
    UPRV_UNREACHABLE_EXIT;
346
0
}
347
348
#endif /* #if !UCONFIG_NO_FORMATTING */