Coverage Report

Created: 2025-11-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/number_patternstring.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
// Allow implicit conversion from char16_t* to UnicodeString for this file:
9
// Helpful in toString methods and elsewhere.
10
#define UNISTR_FROM_STRING_EXPLICIT
11
#define UNISTR_FROM_CHAR_EXPLICIT
12
13
#include "uassert.h"
14
#include "number_patternstring.h"
15
#include "unicode/utf16.h"
16
#include "number_utils.h"
17
#include "number_roundingutils.h"
18
#include "number_mapper.h"
19
20
using namespace icu;
21
using namespace icu::number;
22
using namespace icu::number::impl;
23
24
25
void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo,
26
329k
                                       UErrorCode& status) {
27
329k
    patternInfo.consumePattern(patternString, status);
28
329k
}
29
30
DecimalFormatProperties
31
PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding,
32
0
                                 UErrorCode& status) {
33
0
    DecimalFormatProperties properties;
34
0
    parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status);
35
0
    return properties;
36
0
}
37
38
DecimalFormatProperties PatternParser::parseToProperties(const UnicodeString& pattern,
39
0
                                                         UErrorCode& status) {
40
0
    return parseToProperties(pattern, IGNORE_ROUNDING_NEVER, status);
41
0
}
42
43
void
44
PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties,
45
221k
                                         IgnoreRounding ignoreRounding, UErrorCode& status) {
46
221k
    parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status);
47
221k
}
48
49
50
7.77k
char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const {
51
7.77k
    const Endpoints& endpoints = getEndpoints(flags);
52
7.77k
    if (index < 0 || index >= endpoints.end - endpoints.start) {
53
0
        UPRV_UNREACHABLE_EXIT;
54
0
    }
55
7.77k
    return pattern.charAt(endpoints.start + index);
56
7.77k
}
57
58
772k
int32_t ParsedPatternInfo::length(int32_t flags) const {
59
772k
    return getLengthFromEndpoints(getEndpoints(flags));
60
772k
}
61
62
772k
int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints& endpoints) {
63
772k
    return endpoints.end - endpoints.start;
64
772k
}
65
66
437k
UnicodeString ParsedPatternInfo::getString(int32_t flags) const {
67
437k
    const Endpoints& endpoints = getEndpoints(flags);
68
437k
    if (endpoints.start == endpoints.end) {
69
388k
        return {};
70
388k
    }
71
    // Create a new UnicodeString
72
48.0k
    return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start);
73
437k
}
74
75
1.21M
const Endpoints& ParsedPatternInfo::getEndpoints(int32_t flags) const {
76
1.21M
    bool prefix = (flags & AFFIX_PREFIX) != 0;
77
1.21M
    bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
78
1.21M
    bool padding = (flags & AFFIX_PADDING) != 0;
79
1.21M
    if (isNegative && padding) {
80
0
        return negative.paddingEndpoints;
81
1.21M
    } else if (padding) {
82
1.51k
        return positive.paddingEndpoints;
83
1.21M
    } else if (prefix && isNegative) {
84
1.92k
        return negative.prefixEndpoints;
85
1.21M
    } else if (prefix) {
86
602k
        return positive.prefixEndpoints;
87
611k
    } else if (isNegative) {
88
1.84k
        return negative.suffixEndpoints;
89
609k
    } else {
90
609k
        return positive.suffixEndpoints;
91
609k
    }
92
1.21M
}
93
94
188k
bool ParsedPatternInfo::positiveHasPlusSign() const {
95
188k
    return positive.hasPlusSign;
96
188k
}
97
98
772k
bool ParsedPatternInfo::hasNegativeSubpattern() const {
99
772k
    return fHasNegativeSubpattern;
100
772k
}
101
102
24
bool ParsedPatternInfo::negativeHasMinusSign() const {
103
24
    return negative.hasMinusSign;
104
24
}
105
106
377k
bool ParsedPatternInfo::hasCurrencySign() const {
107
377k
    return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign);
108
377k
}
109
110
15.8k
bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
111
15.8k
    return AffixUtils::containsType(pattern, type, status);
112
15.8k
}
113
114
386k
bool ParsedPatternInfo::hasBody() const {
115
386k
    return positive.integerTotal > 0;
116
386k
}
117
118
9.05k
bool ParsedPatternInfo::currencyAsDecimal() const {
119
9.05k
    return positive.hasCurrencyDecimal;
120
9.05k
}
121
122
/////////////////////////////////////////////////////
123
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
124
/////////////////////////////////////////////////////
125
126
165M
UChar32 ParsedPatternInfo::ParserState::peek() {
127
165M
    if (offset == pattern.length()) {
128
2.39M
        return -1;
129
162M
    } else {
130
162M
        return pattern.char32At(offset);
131
162M
    }
132
165M
}
133
134
4.10k
UChar32 ParsedPatternInfo::ParserState::peek2() {
135
4.10k
    if (offset == pattern.length()) {
136
0
        return -1;
137
0
    }
138
4.10k
    int32_t cp1 = pattern.char32At(offset);
139
4.10k
    int32_t offset2 = offset + U16_LENGTH(cp1);
140
4.10k
    if (offset2 == pattern.length()) {
141
549
        return -1;
142
549
    }
143
3.55k
    return pattern.char32At(offset2);
144
4.10k
}
145
146
42.1M
UChar32 ParsedPatternInfo::ParserState::next() {
147
42.1M
    int32_t codePoint = peek();
148
42.1M
    offset += U16_LENGTH(codePoint);
149
42.1M
    return codePoint;
150
42.1M
}
151
152
329k
void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode& status) {
153
329k
    if (U_FAILURE(status)) { return; }
154
329k
    this->pattern = patternString;
155
156
    // This class is not intended for writing twice!
157
    // Use move assignment to overwrite instead.
158
329k
    U_ASSERT(state.offset == 0);
159
160
    // pattern := subpattern (';' subpattern)?
161
329k
    currentSubpattern = &positive;
162
329k
    consumeSubpattern(status);
163
329k
    if (U_FAILURE(status)) { return; }
164
328k
    if (state.peek() == u';') {
165
3.92k
        state.next(); // consume the ';'
166
        // Don't consume the negative subpattern if it is empty (trailing ';')
167
3.92k
        if (state.peek() != -1) {
168
3.90k
            fHasNegativeSubpattern = true;
169
3.90k
            currentSubpattern = &negative;
170
3.90k
            consumeSubpattern(status);
171
3.90k
            if (U_FAILURE(status)) { return; }
172
3.90k
        }
173
3.92k
    }
174
328k
    if (state.peek() != -1) {
175
4.79k
        state.toParseException(u"Found unquoted special character");
176
4.79k
        status = U_UNQUOTED_SPECIAL;
177
4.79k
    }
178
328k
}
179
180
333k
void ParsedPatternInfo::consumeSubpattern(UErrorCode& status) {
181
    // subpattern := literals? number exponent? literals?
182
333k
    consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status);
183
333k
    if (U_FAILURE(status)) { return; }
184
333k
    consumeAffix(currentSubpattern->prefixEndpoints, status);
185
333k
    if (U_FAILURE(status)) { return; }
186
333k
    consumePadding(PadPosition::UNUM_PAD_AFTER_PREFIX, status);
187
333k
    if (U_FAILURE(status)) { return; }
188
333k
    consumeFormat(status);
189
333k
    if (U_FAILURE(status)) { return; }
190
332k
    consumeExponent(status);
191
332k
    if (U_FAILURE(status)) { return; }
192
332k
    consumePadding(PadPosition::UNUM_PAD_BEFORE_SUFFIX, status);
193
332k
    if (U_FAILURE(status)) { return; }
194
332k
    consumeAffix(currentSubpattern->suffixEndpoints, status);
195
332k
    if (U_FAILURE(status)) { return; }
196
332k
    consumePadding(PadPosition::UNUM_PAD_AFTER_SUFFIX, status);
197
332k
    if (U_FAILURE(status)) { return; }
198
332k
}
199
200
1.33M
void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode& status) {
201
1.33M
    if (state.peek() != u'*') {
202
1.33M
        return;
203
1.33M
    }
204
1.64k
    if (currentSubpattern->hasPadding) {
205
16
        state.toParseException(u"Cannot have multiple pad specifiers");
206
16
        status = U_MULTIPLE_PAD_SPECIFIERS;
207
16
        return;
208
16
    }
209
1.63k
    currentSubpattern->paddingLocation = paddingLocation;
210
1.63k
    currentSubpattern->hasPadding = true;
211
1.63k
    state.next(); // consume the '*'
212
1.63k
    currentSubpattern->paddingEndpoints.start = state.offset;
213
1.63k
    consumeLiteral(status);
214
1.63k
    currentSubpattern->paddingEndpoints.end = state.offset;
215
1.63k
}
216
217
666k
void ParsedPatternInfo::consumeAffix(Endpoints& endpoints, UErrorCode& status) {
218
    // literals := { literal }
219
666k
    endpoints.start = state.offset;
220
36.7M
    while (true) {
221
36.7M
        switch (state.peek()) {
222
180k
            case u'#':
223
180k
            case u'@':
224
188k
            case u';':
225
190k
            case u'*':
226
191k
            case u'.':
227
191k
            case u',':
228
336k
            case u'0':
229
337k
            case u'1':
230
338k
            case u'2':
231
339k
            case u'3':
232
339k
            case u'4':
233
339k
            case u'5':
234
340k
            case u'6':
235
341k
            case u'7':
236
341k
            case u'8':
237
341k
            case u'9':
238
666k
            case -1:
239
                // Characters that cannot appear unquoted in a literal
240
                // break outer;
241
666k
                goto after_outer;
242
243
12.3k
            case u'%':
244
12.3k
                currentSubpattern->hasPercentSign = true;
245
12.3k
                break;
246
247
33.6k
            case u'‰':
248
33.6k
                currentSubpattern->hasPerMilleSign = true;
249
33.6k
                break;
250
251
43.6k
            case u'¤':
252
43.6k
                currentSubpattern->hasCurrencySign = true;
253
43.6k
                break;
254
255
5.29k
            case u'-':
256
5.29k
                currentSubpattern->hasMinusSign = true;
257
5.29k
                break;
258
259
3.83k
            case u'+':
260
3.83k
                currentSubpattern->hasPlusSign = true;
261
3.83k
                break;
262
263
35.9M
            default:
264
35.9M
                break;
265
36.7M
        }
266
36.0M
        consumeLiteral(status);
267
36.0M
        if (U_FAILURE(status)) { return; }
268
36.0M
    }
269
666k
    after_outer:
270
666k
    endpoints.end = state.offset;
271
666k
}
272
273
36.0M
void ParsedPatternInfo::consumeLiteral(UErrorCode& status) {
274
36.0M
    if (state.peek() == -1) {
275
18
        state.toParseException(u"Expected unquoted literal but found EOL");
276
18
        status = U_PATTERN_SYNTAX_ERROR;
277
18
        return;
278
36.0M
    } else if (state.peek() == u'\'') {
279
441k
        state.next(); // consume the starting quote
280
3.29M
        while (state.peek() != u'\'') {
281
2.85M
            if (state.peek() == -1) {
282
154
                state.toParseException(u"Expected quoted literal but found EOL");
283
154
                status = U_PATTERN_SYNTAX_ERROR;
284
154
                return;
285
2.85M
            } else {
286
2.85M
                state.next(); // consume a quoted character
287
2.85M
            }
288
2.85M
        }
289
441k
        state.next(); // consume the ending quote
290
35.6M
    } else {
291
        // consume a non-quoted literal character
292
35.6M
        state.next();
293
35.6M
    }
294
36.0M
}
295
296
333k
void ParsedPatternInfo::consumeFormat(UErrorCode& status) {
297
333k
    consumeIntegerFormat(status);
298
333k
    if (U_FAILURE(status)) { return; }
299
332k
    if (state.peek() == u'.') {
300
145k
        state.next(); // consume the decimal point
301
145k
        currentSubpattern->hasDecimal = true;
302
145k
        currentSubpattern->widthExceptAffixes += 1;
303
145k
        consumeFractionFormat(status);
304
145k
        if (U_FAILURE(status)) { return; }
305
187k
    } else if (state.peek() == u'¤') {
306
        // Check if currency is a decimal separator
307
4.10k
        switch (state.peek2()) {
308
12
            case u'#':
309
31
            case u'0':
310
1.08k
            case u'1':
311
1.10k
            case u'2':
312
1.17k
            case u'3':
313
1.20k
            case u'4':
314
1.22k
            case u'5':
315
2.93k
            case u'6':
316
2.94k
            case u'7':
317
2.96k
            case u'8':
318
2.98k
            case u'9':
319
2.98k
                break;
320
1.12k
            default:
321
                // Currency symbol followed by a non-numeric character;
322
                // treat as a normal affix.
323
1.12k
                return;
324
4.10k
        }
325
        // Currency symbol is followed by a numeric character;
326
        // treat as a decimal separator.
327
2.98k
        currentSubpattern->hasCurrencySign = true;
328
2.98k
        currentSubpattern->hasCurrencyDecimal = true;
329
2.98k
        currentSubpattern->hasDecimal = true;
330
2.98k
        currentSubpattern->widthExceptAffixes += 1;
331
2.98k
        state.next(); // consume the symbol
332
2.98k
        consumeFractionFormat(status);
333
2.98k
        if (U_FAILURE(status)) { return; }
334
2.98k
    }
335
332k
}
336
337
333k
void ParsedPatternInfo::consumeIntegerFormat(UErrorCode& status) {
338
    // Convenience reference:
339
333k
    ParsedSubpatternInfo& result = *currentSubpattern;
340
341
1.70M
    while (true) {
342
1.70M
        switch (state.peek()) {
343
137k
            case u',':
344
137k
                result.widthExceptAffixes += 1;
345
137k
                result.groupingSizes <<= 16;
346
137k
                break;
347
348
451k
            case u'#':
349
451k
                if (result.integerNumerals > 0) {
350
554
                    state.toParseException(u"# cannot follow 0 before decimal point");
351
554
                    status = U_UNEXPECTED_TOKEN;
352
554
                    return;
353
554
                }
354
450k
                result.widthExceptAffixes += 1;
355
450k
                result.groupingSizes += 1;
356
450k
                if (result.integerAtSigns > 0) {
357
281
                    result.integerTrailingHashSigns += 1;
358
450k
                } else {
359
450k
                    result.integerLeadingHashSigns += 1;
360
450k
                }
361
450k
                result.integerTotal += 1;
362
450k
                break;
363
364
11.9k
            case u'@':
365
11.9k
                if (result.integerNumerals > 0) {
366
3
                    state.toParseException(u"Cannot mix 0 and @");
367
3
                    status = U_UNEXPECTED_TOKEN;
368
3
                    return;
369
3
                }
370
11.9k
                if (result.integerTrailingHashSigns > 0) {
371
3
                    state.toParseException(u"Cannot nest # inside of a run of @");
372
3
                    status = U_UNEXPECTED_TOKEN;
373
3
                    return;
374
3
                }
375
11.9k
                result.widthExceptAffixes += 1;
376
11.9k
                result.groupingSizes += 1;
377
11.9k
                result.integerAtSigns += 1;
378
11.9k
                result.integerTotal += 1;
379
11.9k
                break;
380
381
438k
            case u'0':
382
467k
            case u'1':
383
545k
            case u'2':
384
569k
            case u'3':
385
663k
            case u'4':
386
666k
            case u'5':
387
674k
            case u'6':
388
696k
            case u'7':
389
704k
            case u'8':
390
769k
            case u'9':
391
769k
                if (result.integerAtSigns > 0) {
392
4
                    state.toParseException(u"Cannot mix @ and 0");
393
4
                    status = U_UNEXPECTED_TOKEN;
394
4
                    return;
395
4
                }
396
769k
                result.widthExceptAffixes += 1;
397
769k
                result.groupingSizes += 1;
398
769k
                result.integerNumerals += 1;
399
769k
                result.integerTotal += 1;
400
769k
                if (!result.rounding.isZeroish() || state.peek() != u'0') {
401
483k
                    result.rounding.appendDigit(static_cast<int8_t>(state.peek() - u'0'), 0, true);
402
483k
                }
403
769k
                break;
404
405
332k
            default:
406
332k
                goto after_outer;
407
1.70M
        }
408
1.36M
        state.next(); // consume the symbol
409
1.36M
    }
410
411
332k
    after_outer:
412
    // Disallow patterns with a trailing ',' or with two ',' next to each other
413
332k
    auto grouping1 = static_cast<int16_t> (result.groupingSizes & 0xffff);
414
332k
    auto grouping2 = static_cast<int16_t> ((result.groupingSizes >> 16) & 0xffff);
415
332k
    auto grouping3 = static_cast<int16_t> ((result.groupingSizes >> 32) & 0xffff);
416
332k
    if (grouping1 == 0 && grouping2 != -1) {
417
101
        state.toParseException(u"Trailing grouping separator is invalid");
418
101
        status = U_UNEXPECTED_TOKEN;
419
101
        return;
420
101
    }
421
332k
    if (grouping2 == 0 && grouping3 != -1) {
422
15
        state.toParseException(u"Grouping width of zero is invalid");
423
15
        status = U_PATTERN_SYNTAX_ERROR;
424
15
        return;
425
15
    }
426
332k
}
427
428
148k
void ParsedPatternInfo::consumeFractionFormat(UErrorCode& status) {
429
    // Convenience reference:
430
148k
    ParsedSubpatternInfo& result = *currentSubpattern;
431
432
148k
    int32_t zeroCounter = 0;
433
1.34M
    while (true) {
434
1.34M
        switch (state.peek()) {
435
547k
            case u'#':
436
547k
                result.widthExceptAffixes += 1;
437
547k
                result.fractionHashSigns += 1;
438
547k
                result.fractionTotal += 1;
439
547k
                zeroCounter++;
440
547k
                break;
441
442
191k
            case u'0':
443
226k
            case u'1':
444
315k
            case u'2':
445
316k
            case u'3':
446
408k
            case u'4':
447
408k
            case u'5':
448
440k
            case u'6':
449
532k
            case u'7':
450
533k
            case u'8':
451
648k
            case u'9':
452
648k
                if (result.fractionHashSigns > 0) {
453
9
                    state.toParseException(u"0 cannot follow # after decimal point");
454
9
                    status = U_UNEXPECTED_TOKEN;
455
9
                    return;
456
9
                }
457
648k
                result.widthExceptAffixes += 1;
458
648k
                result.fractionNumerals += 1;
459
648k
                result.fractionTotal += 1;
460
648k
                if (state.peek() == u'0') {
461
191k
                    zeroCounter++;
462
456k
                } else {
463
456k
                    result.rounding
464
456k
                            .appendDigit(static_cast<int8_t>(state.peek() - u'0'), zeroCounter, false);
465
456k
                    zeroCounter = 0;
466
456k
                }
467
648k
                break;
468
469
148k
            default:
470
148k
                return;
471
1.34M
        }
472
1.19M
        state.next(); // consume the symbol
473
1.19M
    }
474
148k
}
475
476
332k
void ParsedPatternInfo::consumeExponent(UErrorCode& status) {
477
    // Convenience reference:
478
332k
    ParsedSubpatternInfo& result = *currentSubpattern;
479
480
332k
    if (state.peek() != u'E') {
481
314k
        return;
482
314k
    }
483
18.2k
    if ((result.groupingSizes & 0xffff0000L) != 0xffff0000L) {
484
7
        state.toParseException(u"Cannot have grouping separator in scientific notation");
485
7
        status = U_MALFORMED_EXPONENTIAL_PATTERN;
486
7
        return;
487
7
    }
488
18.2k
    state.next(); // consume the E
489
18.2k
    result.widthExceptAffixes++;
490
18.2k
    if (state.peek() == u'+') {
491
75
        state.next(); // consume the +
492
75
        result.exponentHasPlusSign = true;
493
75
        result.widthExceptAffixes++;
494
75
    }
495
38.1k
    while (state.peek() == u'0') {
496
19.8k
        state.next(); // consume the 0
497
19.8k
        result.exponentZeros += 1;
498
19.8k
        result.widthExceptAffixes++;
499
19.8k
    }
500
18.2k
}
501
502
///////////////////////////////////////////////////
503
/// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
504
///////////////////////////////////////////////////
505
506
void PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern,
507
                                                  DecimalFormatProperties& properties,
508
221k
                                                  IgnoreRounding ignoreRounding, UErrorCode& status) {
509
221k
    if (pattern.length() == 0) {
510
        // Backwards compatibility requires that we reset to the default values.
511
        // TODO: Only overwrite the properties that "saveToProperties" normally touches?
512
181
        properties.clear();
513
181
        return;
514
181
    }
515
516
221k
    ParsedPatternInfo patternInfo;
517
221k
    parseToPatternInfo(pattern, patternInfo, status);
518
221k
    if (U_FAILURE(status)) { return; }
519
215k
    patternInfoToProperties(properties, patternInfo, ignoreRounding, status);
520
215k
}
521
522
void
523
PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, ParsedPatternInfo& patternInfo,
524
215k
                                       IgnoreRounding _ignoreRounding, UErrorCode& status) {
525
    // Translate from PatternParseResult to Properties.
526
    // Note that most data from "negative" is ignored per the specification of DecimalFormat.
527
528
215k
    const ParsedSubpatternInfo& positive = patternInfo.positive;
529
530
215k
    bool ignoreRounding;
531
215k
    if (_ignoreRounding == IGNORE_ROUNDING_NEVER) {
532
0
        ignoreRounding = false;
533
215k
    } else if (_ignoreRounding == IGNORE_ROUNDING_IF_CURRENCY) {
534
206k
        ignoreRounding = positive.hasCurrencySign;
535
206k
    } else {
536
9.83k
        U_ASSERT(_ignoreRounding == IGNORE_ROUNDING_ALWAYS);
537
9.83k
        ignoreRounding = true;
538
9.83k
    }
539
540
    // Grouping settings
541
215k
    auto grouping1 = static_cast<int16_t> (positive.groupingSizes & 0xffff);
542
215k
    auto grouping2 = static_cast<int16_t> ((positive.groupingSizes >> 16) & 0xffff);
543
215k
    auto grouping3 = static_cast<int16_t> ((positive.groupingSizes >> 32) & 0xffff);
544
215k
    if (grouping2 != -1) {
545
86.2k
        properties.groupingSize = grouping1;
546
86.2k
        properties.groupingUsed = true;
547
129k
    } else {
548
129k
        properties.groupingSize = -1;
549
129k
        properties.groupingUsed = false;
550
129k
    }
551
215k
    if (grouping3 != -1) {
552
2.60k
        properties.secondaryGroupingSize = grouping2;
553
213k
    } else {
554
213k
        properties.secondaryGroupingSize = -1;
555
213k
    }
556
557
    // For backwards compatibility, require that the pattern emit at least one min digit.
558
215k
    int minInt, minFrac;
559
215k
    if (positive.integerTotal == 0 && positive.fractionTotal > 0) {
560
        // patterns like ".##"
561
277
        minInt = 0;
562
277
        minFrac = uprv_max(1, positive.fractionNumerals);
563
215k
    } else if (positive.integerNumerals == 0 && positive.fractionNumerals == 0) {
564
        // patterns like "#.##"
565
49.0k
        minInt = 1;
566
49.0k
        minFrac = 0;
567
166k
    } else {
568
166k
        minInt = positive.integerNumerals;
569
166k
        minFrac = positive.fractionNumerals;
570
166k
    }
571
572
    // Rounding settings
573
    // Don't set basic rounding when there is a currency sign; defer to CurrencyUsage
574
215k
    if (positive.integerAtSigns > 0) {
575
898
        properties.minimumFractionDigits = -1;
576
898
        properties.maximumFractionDigits = -1;
577
898
        properties.roundingIncrement = 0.0;
578
898
        properties.minimumSignificantDigits = positive.integerAtSigns;
579
898
        properties.maximumSignificantDigits = positive.integerAtSigns + positive.integerTrailingHashSigns;
580
215k
    } else if (!positive.rounding.isZeroish()) {
581
13.4k
        if (!ignoreRounding) {
582
10.5k
            properties.minimumFractionDigits = minFrac;
583
10.5k
            properties.maximumFractionDigits = positive.fractionTotal;
584
10.5k
            properties.roundingIncrement = positive.rounding.toDouble();
585
10.5k
        } else {
586
2.92k
            properties.minimumFractionDigits = -1;
587
2.92k
            properties.maximumFractionDigits = -1;
588
2.92k
            properties.roundingIncrement = 0.0;
589
2.92k
        }
590
13.4k
        properties.minimumSignificantDigits = -1;
591
13.4k
        properties.maximumSignificantDigits = -1;
592
201k
    } else {
593
201k
        if (!ignoreRounding) {
594
184k
            properties.minimumFractionDigits = minFrac;
595
184k
            properties.maximumFractionDigits = positive.fractionTotal;
596
184k
            properties.roundingIncrement = 0.0;
597
184k
        } else {
598
16.6k
            properties.minimumFractionDigits = -1;
599
16.6k
            properties.maximumFractionDigits = -1;
600
16.6k
            properties.roundingIncrement = 0.0;
601
16.6k
        }
602
201k
        properties.minimumSignificantDigits = -1;
603
201k
        properties.maximumSignificantDigits = -1;
604
201k
    }
605
606
    // If the pattern ends with a '.' then force the decimal point.
607
215k
    if (positive.hasDecimal && positive.fractionTotal == 0) {
608
465
        properties.decimalSeparatorAlwaysShown = true;
609
215k
    } else {
610
215k
        properties.decimalSeparatorAlwaysShown = false;
611
215k
    }
612
613
    // Persist the currency as decimal separator
614
215k
    properties.currencyAsDecimal = positive.hasCurrencyDecimal;
615
616
    // Scientific notation settings
617
215k
    if (positive.exponentZeros > 0) {
618
17.0k
        properties.exponentSignAlwaysShown = positive.exponentHasPlusSign;
619
17.0k
        properties.minimumExponentDigits = positive.exponentZeros;
620
17.0k
        if (positive.integerAtSigns == 0) {
621
            // patterns without '@' can define max integer digits, used for engineering notation
622
17.0k
            properties.minimumIntegerDigits = positive.integerNumerals;
623
17.0k
            properties.maximumIntegerDigits = positive.integerTotal;
624
17.0k
        } else {
625
            // patterns with '@' cannot define max integer digits
626
18
            properties.minimumIntegerDigits = 1;
627
18
            properties.maximumIntegerDigits = -1;
628
18
        }
629
198k
    } else {
630
198k
        properties.exponentSignAlwaysShown = false;
631
198k
        properties.minimumExponentDigits = -1;
632
198k
        properties.minimumIntegerDigits = minInt;
633
198k
        properties.maximumIntegerDigits = -1;
634
198k
    }
635
636
    // Compute the affix patterns (required for both padding and affixes)
637
215k
    UnicodeString posPrefix = patternInfo.getString(AffixPatternProvider::AFFIX_PREFIX);
638
215k
    UnicodeString posSuffix = patternInfo.getString(0);
639
640
    // Padding settings
641
215k
    if (positive.hasPadding) {
642
        // The width of the positive prefix and suffix templates are included in the padding
643
1.51k
        int paddingWidth = positive.widthExceptAffixes +
644
1.51k
                           AffixUtils::estimateLength(posPrefix, status) +
645
1.51k
                           AffixUtils::estimateLength(posSuffix, status);
646
1.51k
        properties.formatWidth = paddingWidth;
647
1.51k
        UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING);
648
1.51k
        if (rawPaddingString.length() == 1) {
649
1.47k
            properties.padString = rawPaddingString;
650
1.47k
        } else if (rawPaddingString.length() == 2) {
651
18
            if (rawPaddingString.charAt(0) == u'\'') {
652
15
                properties.padString.setTo(u"'", -1);
653
15
            } else {
654
3
                properties.padString = rawPaddingString;
655
3
            }
656
18
        } else {
657
18
            properties.padString = UnicodeString(rawPaddingString, 1, rawPaddingString.length() - 2);
658
18
        }
659
1.51k
        properties.padPosition = positive.paddingLocation;
660
214k
    } else {
661
214k
        properties.formatWidth = -1;
662
214k
        properties.padString.setToBogus();
663
214k
        properties.padPosition.nullify();
664
214k
    }
665
666
    // Set the affixes
667
    // Always call the setter, even if the prefixes are empty, especially in the case of the
668
    // negative prefix pattern, to prevent default values from overriding the pattern.
669
215k
    properties.positivePrefixPattern = posPrefix;
670
215k
    properties.positiveSuffixPattern = posSuffix;
671
215k
    if (patternInfo.fHasNegativeSubpattern) {
672
1.82k
        properties.negativePrefixPattern = patternInfo.getString(
673
1.82k
                AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN | AffixPatternProvider::AFFIX_PREFIX);
674
1.82k
        properties.negativeSuffixPattern = patternInfo.getString(
675
1.82k
                AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN);
676
214k
    } else {
677
214k
        properties.negativePrefixPattern.setToBogus();
678
214k
        properties.negativeSuffixPattern.setToBogus();
679
214k
    }
680
681
    // Set the magnitude multiplier
682
215k
    if (positive.hasPercentSign) {
683
10.7k
        properties.magnitudeMultiplier = 2;
684
205k
    } else if (positive.hasPerMilleSign) {
685
385
        properties.magnitudeMultiplier = 3;
686
204k
    } else {
687
204k
        properties.magnitudeMultiplier = 0;
688
204k
    }
689
215k
}
690
691
///////////////////////////////////////////////////////////////////
692
/// End PatternStringParser.java; begin PatternStringUtils.java ///
693
///////////////////////////////////////////////////////////////////
694
695
// Determine whether a given roundingIncrement should be ignored for formatting
696
// based on the current maxFrac value (maximum fraction digits). For example a
697
// roundingIncrement of 0.01 should be ignored if maxFrac is 1, but not if maxFrac
698
// is 2 or more. Note that roundingIncrements are rounded in significance, so
699
// a roundingIncrement of 0.006 is treated like 0.01 for this determination, i.e.
700
// it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
701
// 0.005 is treated like 0.001 for significance). This is the reason for the
702
// initial doubling below.
703
// roundIncr must be non-zero.
704
10.5k
bool PatternStringUtils::ignoreRoundingIncrement(double roundIncr, int32_t maxFrac) {
705
10.5k
    if (maxFrac < 0) {
706
0
        return false;
707
0
    }
708
10.5k
    int32_t frac = 0;
709
10.5k
    roundIncr *= 2.0;
710
21.6k
    for (frac = 0; frac <= maxFrac && roundIncr <= 1.0; frac++, roundIncr *= 10.0);
711
10.5k
    return (frac > maxFrac);
712
10.5k
}
713
714
UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties& properties,
715
0
                                                            UErrorCode& status) {
716
0
    UnicodeString sb;
717
718
    // Convenience references
719
    // The uprv_min() calls prevent DoS
720
0
    int32_t dosMax = 100;
721
0
    int32_t grouping1 = uprv_max(0, uprv_min(properties.groupingSize, dosMax));
722
0
    int32_t grouping2 = uprv_max(0, uprv_min(properties.secondaryGroupingSize, dosMax));
723
0
    bool useGrouping = properties.groupingUsed;
724
0
    int32_t paddingWidth = uprv_min(properties.formatWidth, dosMax);
725
0
    NullableValue<PadPosition> paddingLocation = properties.padPosition;
726
0
    UnicodeString paddingString = properties.padString;
727
0
    int32_t minInt = uprv_max(0, uprv_min(properties.minimumIntegerDigits, dosMax));
728
0
    int32_t maxInt = uprv_min(properties.maximumIntegerDigits, dosMax);
729
0
    int32_t minFrac = uprv_max(0, uprv_min(properties.minimumFractionDigits, dosMax));
730
0
    int32_t maxFrac = uprv_min(properties.maximumFractionDigits, dosMax);
731
0
    int32_t minSig = uprv_min(properties.minimumSignificantDigits, dosMax);
732
0
    int32_t maxSig = uprv_min(properties.maximumSignificantDigits, dosMax);
733
0
    bool alwaysShowDecimal = properties.decimalSeparatorAlwaysShown;
734
0
    int32_t exponentDigits = uprv_min(properties.minimumExponentDigits, dosMax);
735
0
    bool exponentShowPlusSign = properties.exponentSignAlwaysShown;
736
737
0
    AutoAffixPatternProvider affixProvider(properties, status);
738
739
    // Prefixes
740
0
    sb.append(affixProvider.get().getString(AffixPatternProvider::AFFIX_POS_PREFIX));
741
0
    int32_t afterPrefixPos = sb.length();
742
743
    // Figure out the grouping sizes.
744
0
    if (!useGrouping) {
745
0
        grouping1 = 0;
746
0
        grouping2 = 0;
747
0
    } else if (grouping1 == grouping2) {
748
0
        grouping1 = 0;
749
0
    }
750
0
    int32_t groupingLength = grouping1 + grouping2 + 1;
751
752
    // Figure out the digits we need to put in the pattern.
753
0
    double increment = properties.roundingIncrement;
754
0
    UnicodeString digitsString;
755
0
    int32_t digitsStringScale = 0;
756
0
    if (maxSig != uprv_min(dosMax, -1)) {
757
        // Significant Digits.
758
0
        while (digitsString.length() < minSig) {
759
0
            digitsString.append(u'@');
760
0
        }
761
0
        while (digitsString.length() < maxSig) {
762
0
            digitsString.append(u'#');
763
0
        }
764
0
    } else if (increment != 0.0 && !ignoreRoundingIncrement(increment,maxFrac)) {
765
        // Rounding Increment.
766
0
        DecimalQuantity incrementQuantity;
767
0
        incrementQuantity.setToDouble(increment);
768
0
        incrementQuantity.roundToInfinity();
769
0
        digitsStringScale = incrementQuantity.getLowerDisplayMagnitude();
770
0
        incrementQuantity.adjustMagnitude(-digitsStringScale);
771
0
        incrementQuantity.increaseMinIntegerTo(minInt - digitsStringScale);
772
0
        UnicodeString str = incrementQuantity.toPlainString();
773
0
        if (str.charAt(0) == u'-') {
774
            // TODO: Unsupported operation exception or fail silently?
775
0
            digitsString.append(str, 1, str.length() - 1);
776
0
        } else {
777
0
            digitsString.append(str);
778
0
        }
779
0
    }
780
0
    while (digitsString.length() + digitsStringScale < minInt) {
781
0
        digitsString.insert(0, u'0');
782
0
    }
783
0
    while (-digitsStringScale < minFrac) {
784
0
        digitsString.append(u'0');
785
0
        digitsStringScale--;
786
0
    }
787
788
    // Write the digits to the string builder
789
0
    int32_t m0 = uprv_max(groupingLength, digitsString.length() + digitsStringScale);
790
0
    m0 = (maxInt != dosMax) ? uprv_max(maxInt, m0) - 1 : m0 - 1;
791
0
    int32_t mN = (maxFrac != dosMax) ? uprv_min(-maxFrac, digitsStringScale) : digitsStringScale;
792
0
    for (int32_t magnitude = m0; magnitude >= mN; magnitude--) {
793
0
        int32_t di = digitsString.length() + digitsStringScale - magnitude - 1;
794
0
        if (di < 0 || di >= digitsString.length()) {
795
0
            sb.append(u'#');
796
0
        } else {
797
0
            sb.append(digitsString.charAt(di));
798
0
        }
799
        // Decimal separator
800
0
        if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
801
0
            if (properties.currencyAsDecimal) {
802
0
                sb.append(u'¤');
803
0
            } else {
804
0
                sb.append(u'.');
805
0
            }
806
0
        }
807
0
        if (!useGrouping) {
808
0
            continue;
809
0
        }
810
        // Least-significant grouping separator
811
0
        if (magnitude > 0 && magnitude == grouping1) {
812
0
            sb.append(u',');
813
0
        }
814
        // All other grouping separators
815
0
        if (magnitude > grouping1 && grouping2 > 0 && (magnitude - grouping1) % grouping2 == 0) {
816
0
            sb.append(u',');
817
0
        }
818
0
    }
819
820
    // Exponential notation
821
0
    if (exponentDigits != uprv_min(dosMax, -1)) {
822
0
        sb.append(u'E');
823
0
        if (exponentShowPlusSign) {
824
0
            sb.append(u'+');
825
0
        }
826
0
        for (int32_t i = 0; i < exponentDigits; i++) {
827
0
            sb.append(u'0');
828
0
        }
829
0
    }
830
831
    // Suffixes
832
0
    int32_t beforeSuffixPos = sb.length();
833
0
    sb.append(affixProvider.get().getString(AffixPatternProvider::AFFIX_POS_SUFFIX));
834
835
    // Resolve Padding
836
0
    if (paddingWidth > 0 && !paddingLocation.isNull()) {
837
0
        while (paddingWidth - sb.length() > 0) {
838
0
            sb.insert(afterPrefixPos, u'#');
839
0
            beforeSuffixPos++;
840
0
        }
841
0
        int32_t addedLength;
842
0
        switch (paddingLocation.get(status)) {
843
0
            case PadPosition::UNUM_PAD_BEFORE_PREFIX:
844
0
                addedLength = escapePaddingString(paddingString, sb, 0, status);
845
0
                sb.insert(0, u'*');
846
0
                afterPrefixPos += addedLength + 1;
847
0
                beforeSuffixPos += addedLength + 1;
848
0
                break;
849
0
            case PadPosition::UNUM_PAD_AFTER_PREFIX:
850
0
                addedLength = escapePaddingString(paddingString, sb, afterPrefixPos, status);
851
0
                sb.insert(afterPrefixPos, u'*');
852
0
                afterPrefixPos += addedLength + 1;
853
0
                beforeSuffixPos += addedLength + 1;
854
0
                break;
855
0
            case PadPosition::UNUM_PAD_BEFORE_SUFFIX:
856
0
                escapePaddingString(paddingString, sb, beforeSuffixPos, status);
857
0
                sb.insert(beforeSuffixPos, u'*');
858
0
                break;
859
0
            case PadPosition::UNUM_PAD_AFTER_SUFFIX:
860
0
                sb.append(u'*');
861
0
                escapePaddingString(paddingString, sb, sb.length(), status);
862
0
                break;
863
0
        }
864
0
        if (U_FAILURE(status)) { return sb; }
865
0
    }
866
867
    // Negative affixes
868
    // Ignore if the negative prefix pattern is "-" and the negative suffix is empty
869
0
    if (affixProvider.get().hasNegativeSubpattern()) {
870
0
        sb.append(u';');
871
0
        sb.append(affixProvider.get().getString(AffixPatternProvider::AFFIX_NEG_PREFIX));
872
        // Copy the positive digit format into the negative.
873
        // This is optional; the pattern is the same as if '#' were appended here instead.
874
        // NOTE: It is not safe to append the UnicodeString to itself, so we need to copy.
875
        // See https://unicode-org.atlassian.net/browse/ICU-13707
876
0
        UnicodeString copy(sb);
877
0
        sb.append(copy, afterPrefixPos, beforeSuffixPos - afterPrefixPos);
878
0
        sb.append(affixProvider.get().getString(AffixPatternProvider::AFFIX_NEG_SUFFIX));
879
0
    }
880
881
0
    return sb;
882
0
}
883
884
int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex,
885
0
                                            UErrorCode& status) {
886
0
    (void) status;
887
0
    if (input.length() == 0) {
888
0
        input.setTo(kFallbackPaddingString, -1);
889
0
    }
890
0
    int startLength = output.length();
891
0
    if (input.length() == 1) {
892
0
        if (input.compare(u"'", -1) == 0) {
893
0
            output.insert(startIndex, u"''", -1);
894
0
        } else {
895
0
            output.insert(startIndex, input);
896
0
        }
897
0
    } else {
898
0
        output.insert(startIndex, u'\'');
899
0
        int offset = 1;
900
0
        for (int i = 0; i < input.length(); i++) {
901
            // it's okay to deal in chars here because the quote mark is the only interesting thing.
902
0
            char16_t ch = input.charAt(i);
903
0
            if (ch == u'\'') {
904
0
                output.insert(startIndex + offset, u"''", -1);
905
0
                offset += 2;
906
0
            } else {
907
0
                output.insert(startIndex + offset, ch);
908
0
                offset += 1;
909
0
            }
910
0
        }
911
0
        output.insert(startIndex + offset, u'\'');
912
0
    }
913
0
    return output.length() - startLength;
914
0
}
915
916
UnicodeString
917
PatternStringUtils::convertLocalized(const UnicodeString& input, const DecimalFormatSymbols& symbols,
918
0
                                     bool toLocalized, UErrorCode& status) {
919
    // Construct a table of strings to be converted between localized and standard.
920
0
    static constexpr int32_t LEN = 21;
921
0
    UnicodeString table[LEN][2];
922
0
    int standIdx = toLocalized ? 0 : 1;
923
0
    int localIdx = toLocalized ? 1 : 0;
924
    // TODO: Add approximately sign here?
925
0
    table[0][standIdx] = u"%";
926
0
    table[0][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPercentSymbol);
927
0
    table[1][standIdx] = u"‰";
928
0
    table[1][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol);
929
0
    table[2][standIdx] = u".";
930
0
    table[2][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol);
931
0
    table[3][standIdx] = u",";
932
0
    table[3][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol);
933
0
    table[4][standIdx] = u"-";
934
0
    table[4][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol);
935
0
    table[5][standIdx] = u"+";
936
0
    table[5][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol);
937
0
    table[6][standIdx] = u";";
938
0
    table[6][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol);
939
0
    table[7][standIdx] = u"@";
940
0
    table[7][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kSignificantDigitSymbol);
941
0
    table[8][standIdx] = u"E";
942
0
    table[8][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol);
943
0
    table[9][standIdx] = u"*";
944
0
    table[9][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPadEscapeSymbol);
945
0
    table[10][standIdx] = u"#";
946
0
    table[10][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDigitSymbol);
947
0
    for (int i = 0; i < 10; i++) {
948
0
        table[11 + i][standIdx] = u'0' + i;
949
0
        table[11 + i][localIdx] = symbols.getConstDigitSymbol(i);
950
0
    }
951
952
    // Special case: quotes are NOT allowed to be in any localIdx strings.
953
    // Substitute them with '’' instead.
954
0
    for (int32_t i = 0; i < LEN; i++) {
955
0
        table[i][localIdx].findAndReplace(u'\'', u'’');
956
0
    }
957
958
    // Iterate through the string and convert.
959
    // State table:
960
    // 0 => base state
961
    // 1 => first char inside a quoted sequence in input and output string
962
    // 2 => inside a quoted sequence in input and output string
963
    // 3 => first char after a close quote in input string;
964
    // close quote still needs to be written to output string
965
    // 4 => base state in input string; inside quoted sequence in output string
966
    // 5 => first char inside a quoted sequence in input string;
967
    // inside quoted sequence in output string
968
0
    UnicodeString result;
969
0
    int state = 0;
970
0
    for (int offset = 0; offset < input.length(); offset++) {
971
0
        char16_t ch = input.charAt(offset);
972
973
        // Handle a quote character (state shift)
974
0
        if (ch == u'\'') {
975
0
            if (state == 0) {
976
0
                result.append(u'\'');
977
0
                state = 1;
978
0
                continue;
979
0
            } else if (state == 1) {
980
0
                result.append(u'\'');
981
0
                state = 0;
982
0
                continue;
983
0
            } else if (state == 2) {
984
0
                state = 3;
985
0
                continue;
986
0
            } else if (state == 3) {
987
0
                result.append(u'\'');
988
0
                result.append(u'\'');
989
0
                state = 1;
990
0
                continue;
991
0
            } else if (state == 4) {
992
0
                state = 5;
993
0
                continue;
994
0
            } else {
995
0
                U_ASSERT(state == 5);
996
0
                result.append(u'\'');
997
0
                result.append(u'\'');
998
0
                state = 4;
999
0
                continue;
1000
0
            }
1001
0
        }
1002
1003
0
        if (state == 0 || state == 3 || state == 4) {
1004
0
            for (auto& pair : table) {
1005
                // Perform a greedy match on this symbol string
1006
0
                UnicodeString temp = input.tempSubString(offset, pair[0].length());
1007
0
                if (temp == pair[0]) {
1008
                    // Skip ahead past this region for the next iteration
1009
0
                    offset += pair[0].length() - 1;
1010
0
                    if (state == 3 || state == 4) {
1011
0
                        result.append(u'\'');
1012
0
                        state = 0;
1013
0
                    }
1014
0
                    result.append(pair[1]);
1015
0
                    goto continue_outer;
1016
0
                }
1017
0
            }
1018
            // No replacement found. Check if a special quote is necessary
1019
0
            for (auto& pair : table) {
1020
0
                UnicodeString temp = input.tempSubString(offset, pair[1].length());
1021
0
                if (temp == pair[1]) {
1022
0
                    if (state == 0) {
1023
0
                        result.append(u'\'');
1024
0
                        state = 4;
1025
0
                    }
1026
0
                    result.append(ch);
1027
0
                    goto continue_outer;
1028
0
                }
1029
0
            }
1030
            // Still nothing. Copy the char verbatim. (Add a close quote if necessary)
1031
0
            if (state == 3 || state == 4) {
1032
0
                result.append(u'\'');
1033
0
                state = 0;
1034
0
            }
1035
0
            result.append(ch);
1036
0
        } else {
1037
0
            U_ASSERT(state == 1 || state == 2 || state == 5);
1038
0
            result.append(ch);
1039
0
            state = 2;
1040
0
        }
1041
0
        continue_outer:;
1042
0
    }
1043
    // Resolve final quotes
1044
0
    if (state == 3 || state == 4) {
1045
0
        result.append(u'\'');
1046
0
        state = 0;
1047
0
    }
1048
0
    if (state != 0) {
1049
        // Malformed localized pattern: unterminated quote
1050
0
        status = U_PATTERN_SYNTAX_ERROR;
1051
0
    }
1052
0
    return result;
1053
0
}
1054
1055
void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix,
1056
                                                    PatternSignType patternSignType,
1057
                                                    bool approximately,
1058
                                                    StandardPlural::Form plural,
1059
                                                    bool perMilleReplacesPercent,
1060
                                                    bool dropCurrencySymbols,
1061
1.11M
                                                    UnicodeString& output) {
1062
1063
    // Should the output render '+' where '-' would normally appear in the pattern?
1064
1.11M
    bool plusReplacesMinusSign = (patternSignType == PATTERN_SIGN_TYPE_POS_SIGN)
1065
188k
        && !patternInfo.positiveHasPlusSign();
1066
1067
    // Should we use the affix from the negative subpattern?
1068
    // (If not, we will use the positive subpattern.)
1069
1.11M
    bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
1070
7.09k
        && (patternSignType == PATTERN_SIGN_TYPE_NEG
1071
3.54k
            || (patternInfo.negativeHasMinusSign() && (plusReplacesMinusSign || approximately)));
1072
1073
    // Resolve the flags for the affix pattern.
1074
1.11M
    int flags = 0;
1075
1.11M
    if (useNegativeAffixPattern) {
1076
3.55k
        flags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN;
1077
3.55k
    }
1078
1.11M
    if (isPrefix) {
1079
557k
        flags |= AffixPatternProvider::AFFIX_PREFIX;
1080
557k
    }
1081
1.11M
    if (plural != StandardPlural::Form::COUNT) {
1082
334k
        U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural));
1083
334k
        flags |= plural;
1084
334k
    }
1085
1086
    // Should we prepend a sign to the pattern?
1087
1.11M
    bool prependSign;
1088
1.11M
    if (!isPrefix || useNegativeAffixPattern) {
1089
559k
        prependSign = false;
1090
559k
    } else if (patternSignType == PATTERN_SIGN_TYPE_NEG) {
1091
180k
        prependSign = true;
1092
375k
    } else {
1093
375k
        prependSign = plusReplacesMinusSign || approximately;
1094
375k
    }
1095
1096
    // What symbols should take the place of the sign placeholder?
1097
1.11M
    const char16_t* signSymbols = u"-";
1098
1.11M
    if (approximately) {
1099
0
        if (plusReplacesMinusSign) {
1100
0
            signSymbols = u"~+";
1101
0
        } else if (patternSignType == PATTERN_SIGN_TYPE_NEG) {
1102
0
            signSymbols = u"~-";
1103
0
        } else {
1104
0
            signSymbols = u"~";
1105
0
        }
1106
1.11M
    } else if (plusReplacesMinusSign) {
1107
188k
        signSymbols = u"+";
1108
188k
    }
1109
1110
    // Compute the number of tokens in the affix pattern (signSymbols is considered one token).
1111
1.11M
    int length = patternInfo.length(flags) + (prependSign ? 1 : 0);
1112
1113
    // Finally, set the result into the StringBuilder.
1114
1.11M
    output.remove();
1115
58.0M
    for (int index = 0; index < length; index++) {
1116
56.9M
        char16_t candidate;
1117
56.9M
        if (prependSign && index == 0) {
1118
274k
            candidate = u'-';
1119
56.6M
        } else if (prependSign) {
1120
20.1M
            candidate = patternInfo.charAt(flags, index - 1);
1121
36.4M
        } else {
1122
36.4M
            candidate = patternInfo.charAt(flags, index);
1123
36.4M
        }
1124
56.9M
        if (candidate == u'-') {
1125
280k
            if (u_strlen(signSymbols) == 1) {
1126
280k
                candidate = signSymbols[0];
1127
280k
            } else {
1128
0
                output.append(signSymbols[0]);
1129
0
                candidate = signSymbols[1];
1130
0
            }
1131
280k
        }
1132
56.9M
        if (perMilleReplacesPercent && candidate == u'%') {
1133
0
            candidate = u'‰';
1134
0
        }
1135
56.9M
        if (dropCurrencySymbols && candidate == u'\u00A4') {
1136
0
            continue;
1137
0
        }
1138
56.9M
        output.append(candidate);
1139
56.9M
    }
1140
1.11M
}
1141
1142
780k
PatternSignType PatternStringUtils::resolveSignDisplay(UNumberSignDisplay signDisplay, Signum signum) {
1143
780k
    switch (signDisplay) {
1144
26.1k
        case UNUM_SIGN_AUTO:
1145
26.1k
        case UNUM_SIGN_ACCOUNTING:
1146
26.1k
            switch (signum) {
1147
6.28k
                case SIGNUM_NEG:
1148
8.06k
                case SIGNUM_NEG_ZERO:
1149
8.06k
                    return PATTERN_SIGN_TYPE_NEG;
1150
5.34k
                case SIGNUM_POS_ZERO:
1151
18.0k
                case SIGNUM_POS:
1152
18.0k
                    return PATTERN_SIGN_TYPE_POS;
1153
0
                default:
1154
0
                    break;
1155
26.1k
            }
1156
0
            break;
1157
1158
12
        case UNUM_SIGN_ALWAYS:
1159
16
        case UNUM_SIGN_ACCOUNTING_ALWAYS:
1160
16
            switch (signum) {
1161
4
                case SIGNUM_NEG:
1162
4
                case SIGNUM_NEG_ZERO:
1163
4
                    return PATTERN_SIGN_TYPE_NEG;
1164
6
                case SIGNUM_POS_ZERO:
1165
12
                case SIGNUM_POS:
1166
12
                    return PATTERN_SIGN_TYPE_POS_SIGN;
1167
0
                default:
1168
0
                    break;
1169
16
            }
1170
0
            break;
1171
1172
754k
        case UNUM_SIGN_EXCEPT_ZERO:
1173
754k
        case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
1174
754k
            switch (signum) {
1175
188k
                case SIGNUM_NEG:
1176
188k
                    return PATTERN_SIGN_TYPE_NEG;
1177
188k
                case SIGNUM_NEG_ZERO:
1178
377k
                case SIGNUM_POS_ZERO:
1179
377k
                    return PATTERN_SIGN_TYPE_POS;
1180
188k
                case SIGNUM_POS:
1181
188k
                    return PATTERN_SIGN_TYPE_POS_SIGN;
1182
0
                default:
1183
0
                    break;
1184
754k
            }
1185
0
            break;
1186
1187
8
        case UNUM_SIGN_NEGATIVE:
1188
12
        case UNUM_SIGN_ACCOUNTING_NEGATIVE:
1189
12
            switch (signum) {
1190
2
                case SIGNUM_NEG:
1191
2
                    return PATTERN_SIGN_TYPE_NEG;
1192
0
                case SIGNUM_NEG_ZERO:
1193
6
                case SIGNUM_POS_ZERO:
1194
10
                case SIGNUM_POS:
1195
10
                    return PATTERN_SIGN_TYPE_POS;
1196
0
                default:
1197
0
                    break;
1198
12
            }
1199
0
            break;
1200
1201
8
        case UNUM_SIGN_NEVER:
1202
8
            return PATTERN_SIGN_TYPE_POS;
1203
1204
0
        default:
1205
0
            break;
1206
780k
    }
1207
1208
780k
    UPRV_UNREACHABLE_EXIT;
1209
0
    return PATTERN_SIGN_TYPE_POS;
1210
780k
}
1211
1212
#endif /* #if !UCONFIG_NO_FORMATTING */