Coverage Report

Created: 2026-05-06 06:16

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
6.21M
AffixPatternProvider::~AffixPatternProvider() = default;
21
22
23
MutablePatternModifier::MutablePatternModifier(bool isStrong)
24
140k
        : fStrong(isStrong) {}
25
26
142k
void MutablePatternModifier::setPatternInfo(const AffixPatternProvider* patternInfo, Field field) {
27
142k
    fPatternInfo = patternInfo;
28
142k
    fField = field;
29
142k
}
30
31
void MutablePatternModifier::setPatternAttributes(
32
        UNumberSignDisplay signDisplay,
33
        bool perMille,
34
140k
        bool approximately) {
35
140k
    fSignDisplay = signDisplay;
36
140k
    fPerMilleReplacesPercent = perMille;
37
140k
    fApproximately = approximately;
38
140k
}
39
40
void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols,
41
                                        const CurrencyUnit& currency,
42
                                        const UNumberUnitWidth unitWidth,
43
                                        const PluralRules* rules,
44
140k
                                        UErrorCode& status) {
45
140k
    U_ASSERT((rules != nullptr) == needsPlurals());
46
140k
    fSymbols = symbols;
47
140k
    fCurrencySymbols = {currency, symbols->getLocale(), *symbols, status};
48
140k
    fUnitWidth = unitWidth;
49
140k
    fRules = rules;
50
140k
}
51
52
529k
void MutablePatternModifier::setNumberProperties(Signum signum, StandardPlural::Form plural) {
53
529k
    fSignum = signum;
54
529k
    fPlural = plural;
55
529k
}
56
57
20.2k
bool MutablePatternModifier::needsPlurals() const {
58
20.2k
    UErrorCode statusLocal = U_ZERO_ERROR;
59
20.2k
    return fPatternInfo->containsSymbolType(AffixPatternType::TYPE_CURRENCY_TRIPLE, statusLocal);
60
    // Silently ignore any error codes.
61
20.2k
}
62
63
129k
AdoptingSignumModifierStore MutablePatternModifier::createImmutableForPlural(StandardPlural::Form plural, UErrorCode& status) {
64
129k
    AdoptingSignumModifierStore pm;
65
66
129k
    setNumberProperties(SIGNUM_POS, plural);
67
129k
    pm.adoptModifier(SIGNUM_POS, createConstantModifier(status));
68
129k
    setNumberProperties(SIGNUM_NEG_ZERO, plural);
69
129k
    pm.adoptModifier(SIGNUM_NEG_ZERO, createConstantModifier(status));
70
129k
    setNumberProperties(SIGNUM_POS_ZERO, plural);
71
129k
    pm.adoptModifier(SIGNUM_POS_ZERO, createConstantModifier(status));
72
129k
    setNumberProperties(SIGNUM_NEG, plural);
73
129k
    pm.adoptModifier(SIGNUM_NEG, createConstantModifier(status));
74
75
129k
    return pm;
76
129k
}
77
78
565
ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) {
79
    // TODO: Move StandardPlural VALUES to standardplural.h
80
565
    static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = {
81
565
            StandardPlural::Form::ZERO,
82
565
            StandardPlural::Form::ONE,
83
565
            StandardPlural::Form::TWO,
84
565
            StandardPlural::Form::FEW,
85
565
            StandardPlural::Form::MANY,
86
565
            StandardPlural::Form::OTHER};
87
88
565
    auto* pm = new AdoptingModifierStore();
89
565
    if (pm == nullptr) {
90
0
        status = U_MEMORY_ALLOCATION_ERROR;
91
0
        return nullptr;
92
0
    }
93
94
565
    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
565
    } else {
105
        // Faster path when plural keyword is not needed.
106
565
        pm->adoptSignumModifierStoreNoPlural(createImmutableForPlural(StandardPlural::Form::COUNT, status));
107
565
        if (U_FAILURE(status)) {
108
0
            delete pm;
109
0
            return nullptr;
110
0
        }
111
565
        return new ImmutablePatternModifier(pm, nullptr);  // adopts pm
112
565
    }
113
565
}
114
115
518k
ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErrorCode& status) {
116
518k
    FormattedStringBuilder a;
117
518k
    FormattedStringBuilder b;
118
518k
    insertPrefix(a, 0, status);
119
518k
    insertSuffix(b, 0, status);
120
518k
    if (fPatternInfo->hasCurrencySign()) {
121
0
        return new CurrencySpacingEnabledModifier(
122
0
                a, b, !fPatternInfo->hasBody(), fStrong, *fSymbols, status);
123
518k
    } else {
124
518k
        return new ConstantMultiFieldModifier(a, b, !fPatternInfo->hasBody(), fStrong);
125
518k
    }
126
518k
}
127
128
ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules)
129
565
        : pm(pm), rules(rules), parent(nullptr) {}
130
131
void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroProps& micros,
132
2.03M
                                               UErrorCode& status) const {
133
2.03M
    parent->processQuantity(quantity, micros, status);
134
2.03M
    micros.rounder.apply(quantity, status);
135
2.03M
    if (micros.modMiddle != nullptr) {
136
0
        return;
137
0
    }
138
2.03M
    applyToMicros(micros, quantity, status);
139
2.03M
}
140
141
void ImmutablePatternModifier::applyToMicros(
142
2.03M
        MicroProps& micros, const DecimalQuantity& quantity, UErrorCode& status) const {
143
2.03M
    if (rules == nullptr) {
144
2.03M
        micros.modMiddle = pm->getModifierWithoutPlural(quantity.signum());
145
2.03M
    } 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
2.03M
}
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
565
void ImmutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
160
565
    this->parent = parent;
161
565
}
162
163
164
/** Used by the unsafe code path. */
165
10.7k
MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
166
10.7k
    fParent = parent;
167
10.7k
    return *this;
168
10.7k
}
169
170
void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& micros,
171
10.7k
                                             UErrorCode& status) const {
172
10.7k
    fParent->processQuantity(fq, micros, status);
173
10.7k
    micros.rounder.apply(fq, status);
174
10.7k
    if (micros.modMiddle != nullptr) {
175
2.35k
        return;
176
2.35k
    }
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.40k
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
180
8.40k
    if (needsPlurals()) {
181
0
        StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, fRules, fq, status);
182
0
        nonConstThis->setNumberProperties(fq.signum(), pluralForm);
183
8.40k
    } else {
184
8.40k
        nonConstThis->setNumberProperties(fq.signum(), StandardPlural::Form::COUNT);
185
8.40k
    }
186
8.40k
    micros.modMiddle = this;
187
8.40k
}
188
189
int32_t MutablePatternModifier::apply(FormattedStringBuilder& output, int32_t leftIndex, int32_t rightIndex,
190
10.7k
                                      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.7k
    auto* nonConstThis = const_cast<MutablePatternModifier*>(this);
194
10.7k
    int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status);
195
10.7k
    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.7k
    int32_t overwriteLen = 0;
198
10.7k
    if (!fPatternInfo->hasBody()) {
199
21
        overwriteLen = output.splice(
200
21
                leftIndex + prefixLen,
201
21
                rightIndex + prefixLen,
202
21
                UnicodeString(),
203
21
                0,
204
21
                0,
205
21
                kUndefinedField,
206
21
                status);
207
21
    }
208
10.7k
    CurrencySpacingEnabledModifier::applyCurrencySpacing(
209
10.7k
            output,
210
10.7k
            leftIndex,
211
10.7k
            prefixLen,
212
10.7k
            rightIndex + overwriteLen + prefixLen,
213
10.7k
            suffixLen,
214
10.7k
            *fSymbols,
215
10.7k
            status);
216
10.7k
    return prefixLen + overwriteLen + suffixLen;
217
10.7k
}
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
529k
int32_t MutablePatternModifier::insertPrefix(FormattedStringBuilder& sb, int position, UErrorCode& status) {
268
529k
    prepareAffix(true);
269
529k
    int32_t length = AffixUtils::unescape(currentAffix, sb, position, *this, fField, status);
270
529k
    return length;
271
529k
}
272
273
529k
int32_t MutablePatternModifier::insertSuffix(FormattedStringBuilder& sb, int position, UErrorCode& status) {
274
529k
    prepareAffix(false);
275
529k
    int32_t length = AffixUtils::unescape(currentAffix, sb, position, *this, fField, status);
276
529k
    return length;
277
529k
}
278
279
/** This method contains the heart of the logic for rendering LDML affix strings. */
280
1.05M
void MutablePatternModifier::prepareAffix(bool isPrefix) {
281
1.05M
    PatternStringUtils::patternInfoToStringBuilder(
282
1.05M
            *fPatternInfo,
283
1.05M
            isPrefix,
284
1.05M
            PatternStringUtils::resolveSignDisplay(fSignDisplay, fSignum),
285
1.05M
            fApproximately,
286
1.05M
            fPlural,
287
1.05M
            fPerMilleReplacesPercent,
288
1.05M
            false, // dropCurrencySymbols
289
1.05M
            currentAffix);
290
1.05M
}
291
292
262k
UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
293
262k
    UErrorCode localStatus = U_ZERO_ERROR;
294
262k
    switch (type) {
295
133k
        case AffixPatternType::TYPE_MINUS_SIGN:
296
133k
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol);
297
129k
        case AffixPatternType::TYPE_PLUS_SIGN:
298
129k
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
299
0
        case AffixPatternType::TYPE_APPROXIMATELY_SIGN:
300
0
            return fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kApproximatelySignSymbol);
301
56
        case AffixPatternType::TYPE_PERCENT:
302
56
            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
262k
    }
322
262k
}
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 */