Coverage Report

Created: 2025-12-07 06:36

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
386k
                                       UErrorCode& status) {
27
386k
    patternInfo.consumePattern(patternString, status);
28
386k
}
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
285k
                                         IgnoreRounding ignoreRounding, UErrorCode& status) {
46
285k
    parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status);
47
285k
}
48
49
50
8.04k
char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const {
51
8.04k
    const Endpoints& endpoints = getEndpoints(flags);
52
8.04k
    if (index < 0 || index >= endpoints.end - endpoints.start) {
53
0
        UPRV_UNREACHABLE_EXIT;
54
0
    }
55
8.04k
    return pattern.charAt(endpoints.start + index);
56
8.04k
}
57
58
725k
int32_t ParsedPatternInfo::length(int32_t flags) const {
59
725k
    return getLengthFromEndpoints(getEndpoints(flags));
60
725k
}
61
62
725k
int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints& endpoints) {
63
725k
    return endpoints.end - endpoints.start;
64
725k
}
65
66
558k
UnicodeString ParsedPatternInfo::getString(int32_t flags) const {
67
558k
    const Endpoints& endpoints = getEndpoints(flags);
68
558k
    if (endpoints.start == endpoints.end) {
69
481k
        return {};
70
481k
    }
71
    // Create a new UnicodeString
72
76.8k
    return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start);
73
558k
}
74
75
1.29M
const Endpoints& ParsedPatternInfo::getEndpoints(int32_t flags) const {
76
1.29M
    bool prefix = (flags & AFFIX_PREFIX) != 0;
77
1.29M
    bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
78
1.29M
    bool padding = (flags & AFFIX_PADDING) != 0;
79
1.29M
    if (isNegative && padding) {
80
0
        return negative.paddingEndpoints;
81
1.29M
    } else if (padding) {
82
3.72k
        return positive.paddingEndpoints;
83
1.28M
    } else if (prefix && isNegative) {
84
2.21k
        return negative.prefixEndpoints;
85
1.28M
    } else if (prefix) {
86
638k
        return positive.prefixEndpoints;
87
648k
    } else if (isNegative) {
88
2.15k
        return negative.suffixEndpoints;
89
645k
    } else {
90
645k
        return positive.suffixEndpoints;
91
645k
    }
92
1.29M
}
93
94
177k
bool ParsedPatternInfo::positiveHasPlusSign() const {
95
177k
    return positive.hasPlusSign;
96
177k
}
97
98
725k
bool ParsedPatternInfo::hasNegativeSubpattern() const {
99
725k
    return fHasNegativeSubpattern;
100
725k
}
101
102
16
bool ParsedPatternInfo::negativeHasMinusSign() const {
103
16
    return negative.hasMinusSign;
104
16
}
105
106
354k
bool ParsedPatternInfo::hasCurrencySign() const {
107
354k
    return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign);
108
354k
}
109
110
15.0k
bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
111
15.0k
    return AffixUtils::containsType(pattern, type, status);
112
15.0k
}
113
114
362k
bool ParsedPatternInfo::hasBody() const {
115
362k
    return positive.integerTotal > 0;
116
362k
}
117
118
8.59k
bool ParsedPatternInfo::currencyAsDecimal() const {
119
8.59k
    return positive.hasCurrencyDecimal;
120
8.59k
}
121
122
/////////////////////////////////////////////////////
123
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
124
/////////////////////////////////////////////////////
125
126
186M
UChar32 ParsedPatternInfo::ParserState::peek() {
127
186M
    if (offset == pattern.length()) {
128
2.73M
        return -1;
129
183M
    } else {
130
183M
        return pattern.char32At(offset);
131
183M
    }
132
186M
}
133
134
7.44k
UChar32 ParsedPatternInfo::ParserState::peek2() {
135
7.44k
    if (offset == pattern.length()) {
136
0
        return -1;
137
0
    }
138
7.44k
    int32_t cp1 = pattern.char32At(offset);
139
7.44k
    int32_t offset2 = offset + U16_LENGTH(cp1);
140
7.44k
    if (offset2 == pattern.length()) {
141
572
        return -1;
142
572
    }
143
6.87k
    return pattern.char32At(offset2);
144
7.44k
}
145
146
47.3M
UChar32 ParsedPatternInfo::ParserState::next() {
147
47.3M
    int32_t codePoint = peek();
148
47.3M
    offset += U16_LENGTH(codePoint);
149
47.3M
    return codePoint;
150
47.3M
}
151
152
386k
void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode& status) {
153
386k
    if (U_FAILURE(status)) { return; }
154
386k
    this->pattern = patternString;
155
156
    // This class is not intended for writing twice!
157
    // Use move assignment to overwrite instead.
158
386k
    U_ASSERT(state.offset == 0);
159
160
    // pattern := subpattern (';' subpattern)?
161
386k
    currentSubpattern = &positive;
162
386k
    consumeSubpattern(status);
163
386k
    if (U_FAILURE(status)) { return; }
164
385k
    if (state.peek() == u';') {
165
4.78k
        state.next(); // consume the ';'
166
        // Don't consume the negative subpattern if it is empty (trailing ';')
167
4.78k
        if (state.peek() != -1) {
168
4.76k
            fHasNegativeSubpattern = true;
169
4.76k
            currentSubpattern = &negative;
170
4.76k
            consumeSubpattern(status);
171
4.76k
            if (U_FAILURE(status)) { return; }
172
4.76k
        }
173
4.78k
    }
174
385k
    if (state.peek() != -1) {
175
8.22k
        state.toParseException(u"Found unquoted special character");
176
8.22k
        status = U_UNQUOTED_SPECIAL;
177
8.22k
    }
178
385k
}
179
180
391k
void ParsedPatternInfo::consumeSubpattern(UErrorCode& status) {
181
    // subpattern := literals? number exponent? literals?
182
391k
    consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status);
183
391k
    if (U_FAILURE(status)) { return; }
184
391k
    consumeAffix(currentSubpattern->prefixEndpoints, status);
185
391k
    if (U_FAILURE(status)) { return; }
186
391k
    consumePadding(PadPosition::UNUM_PAD_AFTER_PREFIX, status);
187
391k
    if (U_FAILURE(status)) { return; }
188
391k
    consumeFormat(status);
189
391k
    if (U_FAILURE(status)) { return; }
190
390k
    consumeExponent(status);
191
390k
    if (U_FAILURE(status)) { return; }
192
390k
    consumePadding(PadPosition::UNUM_PAD_BEFORE_SUFFIX, status);
193
390k
    if (U_FAILURE(status)) { return; }
194
390k
    consumeAffix(currentSubpattern->suffixEndpoints, status);
195
390k
    if (U_FAILURE(status)) { return; }
196
389k
    consumePadding(PadPosition::UNUM_PAD_AFTER_SUFFIX, status);
197
389k
    if (U_FAILURE(status)) { return; }
198
389k
}
199
200
1.56M
void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode& status) {
201
1.56M
    if (state.peek() != u'*') {
202
1.55M
        return;
203
1.55M
    }
204
4.33k
    if (currentSubpattern->hasPadding) {
205
15
        state.toParseException(u"Cannot have multiple pad specifiers");
206
15
        status = U_MULTIPLE_PAD_SPECIFIERS;
207
15
        return;
208
15
    }
209
4.31k
    currentSubpattern->paddingLocation = paddingLocation;
210
4.31k
    currentSubpattern->hasPadding = true;
211
4.31k
    state.next(); // consume the '*'
212
4.31k
    currentSubpattern->paddingEndpoints.start = state.offset;
213
4.31k
    consumeLiteral(status);
214
4.31k
    currentSubpattern->paddingEndpoints.end = state.offset;
215
4.31k
}
216
217
781k
void ParsedPatternInfo::consumeAffix(Endpoints& endpoints, UErrorCode& status) {
218
    // literals := { literal }
219
781k
    endpoints.start = state.offset;
220
41.8M
    while (true) {
221
41.8M
        switch (state.peek()) {
222
232k
            case u'#':
223
232k
            case u'@':
224
242k
            case u';':
225
246k
            case u'*':
226
247k
            case u'.':
227
248k
            case u',':
228
398k
            case u'0':
229
400k
            case u'1':
230
401k
            case u'2':
231
402k
            case u'3':
232
402k
            case u'4':
233
403k
            case u'5':
234
404k
            case u'6':
235
405k
            case u'7':
236
405k
            case u'8':
237
406k
            case u'9':
238
781k
            case -1:
239
                // Characters that cannot appear unquoted in a literal
240
                // break outer;
241
781k
                goto after_outer;
242
243
15.8k
            case u'%':
244
15.8k
                currentSubpattern->hasPercentSign = true;
245
15.8k
                break;
246
247
38.2k
            case u'‰':
248
38.2k
                currentSubpattern->hasPerMilleSign = true;
249
38.2k
                break;
250
251
49.0k
            case u'¤':
252
49.0k
                currentSubpattern->hasCurrencySign = true;
253
49.0k
                break;
254
255
5.20k
            case u'-':
256
5.20k
                currentSubpattern->hasMinusSign = true;
257
5.20k
                break;
258
259
3.69k
            case u'+':
260
3.69k
                currentSubpattern->hasPlusSign = true;
261
3.69k
                break;
262
263
40.9M
            default:
264
40.9M
                break;
265
41.8M
        }
266
41.0M
        consumeLiteral(status);
267
41.0M
        if (U_FAILURE(status)) { return; }
268
41.0M
    }
269
781k
    after_outer:
270
781k
    endpoints.end = state.offset;
271
781k
}
272
273
41.0M
void ParsedPatternInfo::consumeLiteral(UErrorCode& status) {
274
41.0M
    if (state.peek() == -1) {
275
450
        state.toParseException(u"Expected unquoted literal but found EOL");
276
450
        status = U_PATTERN_SYNTAX_ERROR;
277
450
        return;
278
41.0M
    } else if (state.peek() == u'\'') {
279
381k
        state.next(); // consume the starting quote
280
3.42M
        while (state.peek() != u'\'') {
281
3.04M
            if (state.peek() == -1) {
282
247
                state.toParseException(u"Expected quoted literal but found EOL");
283
247
                status = U_PATTERN_SYNTAX_ERROR;
284
247
                return;
285
3.04M
            } else {
286
3.04M
                state.next(); // consume a quoted character
287
3.04M
            }
288
3.04M
        }
289
381k
        state.next(); // consume the ending quote
290
40.6M
    } else {
291
        // consume a non-quoted literal character
292
40.6M
        state.next();
293
40.6M
    }
294
41.0M
}
295
296
391k
void ParsedPatternInfo::consumeFormat(UErrorCode& status) {
297
391k
    consumeIntegerFormat(status);
298
391k
    if (U_FAILURE(status)) { return; }
299
390k
    if (state.peek() == u'.') {
300
139k
        state.next(); // consume the decimal point
301
139k
        currentSubpattern->hasDecimal = true;
302
139k
        currentSubpattern->widthExceptAffixes += 1;
303
139k
        consumeFractionFormat(status);
304
139k
        if (U_FAILURE(status)) { return; }
305
251k
    } else if (state.peek() == u'¤') {
306
        // Check if currency is a decimal separator
307
7.44k
        switch (state.peek2()) {
308
18
            case u'#':
309
35
            case u'0':
310
870
            case u'1':
311
953
            case u'2':
312
1.10k
            case u'3':
313
1.24k
            case u'4':
314
1.52k
            case u'5':
315
6.10k
            case u'6':
316
6.12k
            case u'7':
317
6.15k
            case u'8':
318
6.23k
            case u'9':
319
6.23k
                break;
320
1.20k
            default:
321
                // Currency symbol followed by a non-numeric character;
322
                // treat as a normal affix.
323
1.20k
                return;
324
7.44k
        }
325
        // Currency symbol is followed by a numeric character;
326
        // treat as a decimal separator.
327
6.23k
        currentSubpattern->hasCurrencySign = true;
328
6.23k
        currentSubpattern->hasCurrencyDecimal = true;
329
6.23k
        currentSubpattern->hasDecimal = true;
330
6.23k
        currentSubpattern->widthExceptAffixes += 1;
331
6.23k
        state.next(); // consume the symbol
332
6.23k
        consumeFractionFormat(status);
333
6.23k
        if (U_FAILURE(status)) { return; }
334
6.23k
    }
335
390k
}
336
337
391k
void ParsedPatternInfo::consumeIntegerFormat(UErrorCode& status) {
338
    // Convenience reference:
339
391k
    ParsedSubpatternInfo& result = *currentSubpattern;
340
341
1.95M
    while (true) {
342
1.95M
        switch (state.peek()) {
343
132k
            case u',':
344
132k
                result.widthExceptAffixes += 1;
345
132k
                result.groupingSizes <<= 16;
346
132k
                break;
347
348
493k
            case u'#':
349
493k
                if (result.integerNumerals > 0) {
350
629
                    state.toParseException(u"# cannot follow 0 before decimal point");
351
629
                    status = U_UNEXPECTED_TOKEN;
352
629
                    return;
353
629
                }
354
493k
                result.widthExceptAffixes += 1;
355
493k
                result.groupingSizes += 1;
356
493k
                if (result.integerAtSigns > 0) {
357
256
                    result.integerTrailingHashSigns += 1;
358
492k
                } else {
359
492k
                    result.integerLeadingHashSigns += 1;
360
492k
                }
361
493k
                result.integerTotal += 1;
362
493k
                break;
363
364
9.60k
            case u'@':
365
9.60k
                if (result.integerNumerals > 0) {
366
4
                    state.toParseException(u"Cannot mix 0 and @");
367
4
                    status = U_UNEXPECTED_TOKEN;
368
4
                    return;
369
4
                }
370
9.60k
                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
9.60k
                result.widthExceptAffixes += 1;
376
9.60k
                result.groupingSizes += 1;
377
9.60k
                result.integerAtSigns += 1;
378
9.60k
                result.integerTotal += 1;
379
9.60k
                break;
380
381
475k
            case u'0':
382
522k
            case u'1':
383
618k
            case u'2':
384
648k
            case u'3':
385
765k
            case u'4':
386
768k
            case u'5':
387
788k
            case u'6':
388
842k
            case u'7':
389
851k
            case u'8':
390
928k
            case u'9':
391
928k
                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
927k
                result.widthExceptAffixes += 1;
397
927k
                result.groupingSizes += 1;
398
927k
                result.integerNumerals += 1;
399
927k
                result.integerTotal += 1;
400
927k
                if (!result.rounding.isZeroish() || state.peek() != u'0') {
401
633k
                    result.rounding.appendDigit(static_cast<int8_t>(state.peek() - u'0'), 0, true);
402
633k
                }
403
927k
                break;
404
405
390k
            default:
406
390k
                goto after_outer;
407
1.95M
        }
408
1.56M
        state.next(); // consume the symbol
409
1.56M
    }
410
411
390k
    after_outer:
412
    // Disallow patterns with a trailing ',' or with two ',' next to each other
413
390k
    auto grouping1 = static_cast<int16_t> (result.groupingSizes & 0xffff);
414
390k
    auto grouping2 = static_cast<int16_t> ((result.groupingSizes >> 16) & 0xffff);
415
390k
    auto grouping3 = static_cast<int16_t> ((result.groupingSizes >> 32) & 0xffff);
416
390k
    if (grouping1 == 0 && grouping2 != -1) {
417
123
        state.toParseException(u"Trailing grouping separator is invalid");
418
123
        status = U_UNEXPECTED_TOKEN;
419
123
        return;
420
123
    }
421
390k
    if (grouping2 == 0 && grouping3 != -1) {
422
13
        state.toParseException(u"Grouping width of zero is invalid");
423
13
        status = U_PATTERN_SYNTAX_ERROR;
424
13
        return;
425
13
    }
426
390k
}
427
428
145k
void ParsedPatternInfo::consumeFractionFormat(UErrorCode& status) {
429
    // Convenience reference:
430
145k
    ParsedSubpatternInfo& result = *currentSubpattern;
431
432
145k
    int32_t zeroCounter = 0;
433
1.28M
    while (true) {
434
1.28M
        switch (state.peek()) {
435
512k
            case u'#':
436
512k
                result.widthExceptAffixes += 1;
437
512k
                result.fractionHashSigns += 1;
438
512k
                result.fractionTotal += 1;
439
512k
                zeroCounter++;
440
512k
                break;
441
442
193k
            case u'0':
443
221k
            case u'1':
444
315k
            case u'2':
445
318k
            case u'3':
446
415k
            case u'4':
447
416k
            case u'5':
448
444k
            case u'6':
449
516k
            case u'7':
450
517k
            case u'8':
451
630k
            case u'9':
452
630k
                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
630k
                result.widthExceptAffixes += 1;
458
630k
                result.fractionNumerals += 1;
459
630k
                result.fractionTotal += 1;
460
630k
                if (state.peek() == u'0') {
461
193k
                    zeroCounter++;
462
436k
                } else {
463
436k
                    result.rounding
464
436k
                            .appendDigit(static_cast<int8_t>(state.peek() - u'0'), zeroCounter, false);
465
436k
                    zeroCounter = 0;
466
436k
                }
467
630k
                break;
468
469
145k
            default:
470
145k
                return;
471
1.28M
        }
472
1.14M
        state.next(); // consume the symbol
473
1.14M
    }
474
145k
}
475
476
390k
void ParsedPatternInfo::consumeExponent(UErrorCode& status) {
477
    // Convenience reference:
478
390k
    ParsedSubpatternInfo& result = *currentSubpattern;
479
480
390k
    if (state.peek() != u'E') {
481
363k
        return;
482
363k
    }
483
26.5k
    if ((result.groupingSizes & 0xffff0000L) != 0xffff0000L) {
484
3
        state.toParseException(u"Cannot have grouping separator in scientific notation");
485
3
        status = U_MALFORMED_EXPONENTIAL_PATTERN;
486
3
        return;
487
3
    }
488
26.5k
    state.next(); // consume the E
489
26.5k
    result.widthExceptAffixes++;
490
26.5k
    if (state.peek() == u'+') {
491
102
        state.next(); // consume the +
492
102
        result.exponentHasPlusSign = true;
493
102
        result.widthExceptAffixes++;
494
102
    }
495
52.1k
    while (state.peek() == u'0') {
496
25.6k
        state.next(); // consume the 0
497
25.6k
        result.exponentZeros += 1;
498
25.6k
        result.widthExceptAffixes++;
499
25.6k
    }
500
26.5k
}
501
502
///////////////////////////////////////////////////
503
/// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
504
///////////////////////////////////////////////////
505
506
void PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern,
507
                                                  DecimalFormatProperties& properties,
508
285k
                                                  IgnoreRounding ignoreRounding, UErrorCode& status) {
509
285k
    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
176
        properties.clear();
513
176
        return;
514
176
    }
515
516
285k
    ParsedPatternInfo patternInfo;
517
285k
    parseToPatternInfo(pattern, patternInfo, status);
518
285k
    if (U_FAILURE(status)) { return; }
519
275k
    patternInfoToProperties(properties, patternInfo, ignoreRounding, status);
520
275k
}
521
522
void
523
PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, ParsedPatternInfo& patternInfo,
524
275k
                                       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
275k
    const ParsedSubpatternInfo& positive = patternInfo.positive;
529
530
275k
    bool ignoreRounding;
531
275k
    if (_ignoreRounding == IGNORE_ROUNDING_NEVER) {
532
0
        ignoreRounding = false;
533
275k
    } else if (_ignoreRounding == IGNORE_ROUNDING_IF_CURRENCY) {
534
264k
        ignoreRounding = positive.hasCurrencySign;
535
264k
    } else {
536
11.2k
        U_ASSERT(_ignoreRounding == IGNORE_ROUNDING_ALWAYS);
537
11.2k
        ignoreRounding = true;
538
11.2k
    }
539
540
    // Grouping settings
541
275k
    auto grouping1 = static_cast<int16_t> (positive.groupingSizes & 0xffff);
542
275k
    auto grouping2 = static_cast<int16_t> ((positive.groupingSizes >> 16) & 0xffff);
543
275k
    auto grouping3 = static_cast<int16_t> ((positive.groupingSizes >> 32) & 0xffff);
544
275k
    if (grouping2 != -1) {
545
83.2k
        properties.groupingSize = grouping1;
546
83.2k
        properties.groupingUsed = true;
547
192k
    } else {
548
192k
        properties.groupingSize = -1;
549
192k
        properties.groupingUsed = false;
550
192k
    }
551
275k
    if (grouping3 != -1) {
552
2.54k
        properties.secondaryGroupingSize = grouping2;
553
272k
    } else {
554
272k
        properties.secondaryGroupingSize = -1;
555
272k
    }
556
557
    // For backwards compatibility, require that the pattern emit at least one min digit.
558
275k
    int minInt, minFrac;
559
275k
    if (positive.integerTotal == 0 && positive.fractionTotal > 0) {
560
        // patterns like ".##"
561
307
        minInt = 0;
562
307
        minFrac = uprv_max(1, positive.fractionNumerals);
563
275k
    } else if (positive.integerNumerals == 0 && positive.fractionNumerals == 0) {
564
        // patterns like "#.##"
565
99.4k
        minInt = 1;
566
99.4k
        minFrac = 0;
567
175k
    } else {
568
175k
        minInt = positive.integerNumerals;
569
175k
        minFrac = positive.fractionNumerals;
570
175k
    }
571
572
    // Rounding settings
573
    // Don't set basic rounding when there is a currency sign; defer to CurrencyUsage
574
275k
    if (positive.integerAtSigns > 0) {
575
1.23k
        properties.minimumFractionDigits = -1;
576
1.23k
        properties.maximumFractionDigits = -1;
577
1.23k
        properties.roundingIncrement = 0.0;
578
1.23k
        properties.minimumSignificantDigits = positive.integerAtSigns;
579
1.23k
        properties.maximumSignificantDigits = positive.integerAtSigns + positive.integerTrailingHashSigns;
580
274k
    } else if (!positive.rounding.isZeroish()) {
581
19.2k
        if (!ignoreRounding) {
582
13.4k
            properties.minimumFractionDigits = minFrac;
583
13.4k
            properties.maximumFractionDigits = positive.fractionTotal;
584
13.4k
            properties.roundingIncrement = positive.rounding.toDouble();
585
13.4k
        } else {
586
5.76k
            properties.minimumFractionDigits = -1;
587
5.76k
            properties.maximumFractionDigits = -1;
588
5.76k
            properties.roundingIncrement = 0.0;
589
5.76k
        }
590
19.2k
        properties.minimumSignificantDigits = -1;
591
19.2k
        properties.maximumSignificantDigits = -1;
592
254k
    } else {
593
254k
        if (!ignoreRounding) {
594
233k
            properties.minimumFractionDigits = minFrac;
595
233k
            properties.maximumFractionDigits = positive.fractionTotal;
596
233k
            properties.roundingIncrement = 0.0;
597
233k
        } else {
598
21.7k
            properties.minimumFractionDigits = -1;
599
21.7k
            properties.maximumFractionDigits = -1;
600
21.7k
            properties.roundingIncrement = 0.0;
601
21.7k
        }
602
254k
        properties.minimumSignificantDigits = -1;
603
254k
        properties.maximumSignificantDigits = -1;
604
254k
    }
605
606
    // If the pattern ends with a '.' then force the decimal point.
607
275k
    if (positive.hasDecimal && positive.fractionTotal == 0) {
608
720
        properties.decimalSeparatorAlwaysShown = true;
609
274k
    } else {
610
274k
        properties.decimalSeparatorAlwaysShown = false;
611
274k
    }
612
613
    // Persist the currency as decimal separator
614
275k
    properties.currencyAsDecimal = positive.hasCurrencyDecimal;
615
616
    // Scientific notation settings
617
275k
    if (positive.exponentZeros > 0) {
618
24.3k
        properties.exponentSignAlwaysShown = positive.exponentHasPlusSign;
619
24.3k
        properties.minimumExponentDigits = positive.exponentZeros;
620
24.3k
        if (positive.integerAtSigns == 0) {
621
            // patterns without '@' can define max integer digits, used for engineering notation
622
24.2k
            properties.minimumIntegerDigits = positive.integerNumerals;
623
24.2k
            properties.maximumIntegerDigits = positive.integerTotal;
624
24.2k
        } else {
625
            // patterns with '@' cannot define max integer digits
626
34
            properties.minimumIntegerDigits = 1;
627
34
            properties.maximumIntegerDigits = -1;
628
34
        }
629
251k
    } else {
630
251k
        properties.exponentSignAlwaysShown = false;
631
251k
        properties.minimumExponentDigits = -1;
632
251k
        properties.minimumIntegerDigits = minInt;
633
251k
        properties.maximumIntegerDigits = -1;
634
251k
    }
635
636
    // Compute the affix patterns (required for both padding and affixes)
637
275k
    UnicodeString posPrefix = patternInfo.getString(AffixPatternProvider::AFFIX_PREFIX);
638
275k
    UnicodeString posSuffix = patternInfo.getString(0);
639
640
    // Padding settings
641
275k
    if (positive.hasPadding) {
642
        // The width of the positive prefix and suffix templates are included in the padding
643
3.72k
        int paddingWidth = positive.widthExceptAffixes +
644
3.72k
                           AffixUtils::estimateLength(posPrefix, status) +
645
3.72k
                           AffixUtils::estimateLength(posSuffix, status);
646
3.72k
        properties.formatWidth = paddingWidth;
647
3.72k
        UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING);
648
3.72k
        if (rawPaddingString.length() == 1) {
649
3.67k
            properties.padString = rawPaddingString;
650
3.67k
        } else if (rawPaddingString.length() == 2) {
651
24
            if (rawPaddingString.charAt(0) == u'\'') {
652
20
                properties.padString.setTo(u"'", -1);
653
20
            } else {
654
4
                properties.padString = rawPaddingString;
655
4
            }
656
30
        } else {
657
30
            properties.padString = UnicodeString(rawPaddingString, 1, rawPaddingString.length() - 2);
658
30
        }
659
3.72k
        properties.padPosition = positive.paddingLocation;
660
271k
    } else {
661
271k
        properties.formatWidth = -1;
662
271k
        properties.padString.setToBogus();
663
271k
        properties.padPosition.nullify();
664
271k
    }
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
275k
    properties.positivePrefixPattern = posPrefix;
670
275k
    properties.positiveSuffixPattern = posSuffix;
671
275k
    if (patternInfo.fHasNegativeSubpattern) {
672
2.13k
        properties.negativePrefixPattern = patternInfo.getString(
673
2.13k
                AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN | AffixPatternProvider::AFFIX_PREFIX);
674
2.13k
        properties.negativeSuffixPattern = patternInfo.getString(
675
2.13k
                AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN);
676
273k
    } else {
677
273k
        properties.negativePrefixPattern.setToBogus();
678
273k
        properties.negativeSuffixPattern.setToBogus();
679
273k
    }
680
681
    // Set the magnitude multiplier
682
275k
    if (positive.hasPercentSign) {
683
13.2k
        properties.magnitudeMultiplier = 2;
684
262k
    } else if (positive.hasPerMilleSign) {
685
459
        properties.magnitudeMultiplier = 3;
686
261k
    } else {
687
261k
        properties.magnitudeMultiplier = 0;
688
261k
    }
689
275k
}
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
13.4k
bool PatternStringUtils::ignoreRoundingIncrement(double roundIncr, int32_t maxFrac) {
705
13.4k
    if (maxFrac < 0) {
706
0
        return false;
707
0
    }
708
13.4k
    int32_t frac = 0;
709
13.4k
    roundIncr *= 2.0;
710
26.3k
    for (frac = 0; frac <= maxFrac && roundIncr <= 1.0; frac++, roundIncr *= 10.0);
711
13.4k
    return (frac > maxFrac);
712
13.4k
}
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.12M
                                                    UnicodeString& output) {
1062
1063
    // Should the output render '+' where '-' would normally appear in the pattern?
1064
1.12M
    bool plusReplacesMinusSign = (patternSignType == PATTERN_SIGN_TYPE_POS_SIGN)
1065
177k
        && !patternInfo.positiveHasPlusSign();
1066
1067
    // Should we use the affix from the negative subpattern?
1068
    // (If not, we will use the positive subpattern.)
1069
1.12M
    bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
1070
8.29k
        && (patternSignType == PATTERN_SIGN_TYPE_NEG
1071
4.14k
            || (patternInfo.negativeHasMinusSign() && (plusReplacesMinusSign || approximately)));
1072
1073
    // Resolve the flags for the affix pattern.
1074
1.12M
    int flags = 0;
1075
1.12M
    if (useNegativeAffixPattern) {
1076
4.15k
        flags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN;
1077
4.15k
    }
1078
1.12M
    if (isPrefix) {
1079
564k
        flags |= AffixPatternProvider::AFFIX_PREFIX;
1080
564k
    }
1081
1.12M
    if (plural != StandardPlural::Form::COUNT) {
1082
395k
        U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural));
1083
395k
        flags |= plural;
1084
395k
    }
1085
1086
    // Should we prepend a sign to the pattern?
1087
1.12M
    bool prependSign;
1088
1.12M
    if (!isPrefix || useNegativeAffixPattern) {
1089
566k
        prependSign = false;
1090
566k
    } else if (patternSignType == PATTERN_SIGN_TYPE_NEG) {
1091
189k
        prependSign = true;
1092
373k
    } else {
1093
373k
        prependSign = plusReplacesMinusSign || approximately;
1094
373k
    }
1095
1096
    // What symbols should take the place of the sign placeholder?
1097
1.12M
    const char16_t* signSymbols = u"-";
1098
1.12M
    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.12M
    } else if (plusReplacesMinusSign) {
1107
177k
        signSymbols = u"+";
1108
177k
    }
1109
1110
    // Compute the number of tokens in the affix pattern (signSymbols is considered one token).
1111
1.12M
    int length = patternInfo.length(flags) + (prependSign ? 1 : 0);
1112
1113
    // Finally, set the result into the StringBuilder.
1114
1.12M
    output.remove();
1115
67.5M
    for (int index = 0; index < length; index++) {
1116
66.3M
        char16_t candidate;
1117
66.3M
        if (prependSign && index == 0) {
1118
277k
            candidate = u'-';
1119
66.1M
        } else if (prependSign) {
1120
22.0M
            candidate = patternInfo.charAt(flags, index - 1);
1121
44.0M
        } else {
1122
44.0M
            candidate = patternInfo.charAt(flags, index);
1123
44.0M
        }
1124
66.3M
        if (candidate == u'-') {
1125
283k
            if (u_strlen(signSymbols) == 1) {
1126
283k
                candidate = signSymbols[0];
1127
283k
            } else {
1128
0
                output.append(signSymbols[0]);
1129
0
                candidate = signSymbols[1];
1130
0
            }
1131
283k
        }
1132
66.3M
        if (perMilleReplacesPercent && candidate == u'%') {
1133
0
            candidate = u'‰';
1134
0
        }
1135
66.3M
        if (dropCurrencySymbols && candidate == u'\u00A4') {
1136
0
            continue;
1137
0
        }
1138
66.3M
        output.append(candidate);
1139
66.3M
    }
1140
1.12M
}
1141
1142
732k
PatternSignType PatternStringUtils::resolveSignDisplay(UNumberSignDisplay signDisplay, Signum signum) {
1143
732k
    switch (signDisplay) {
1144
24.2k
        case UNUM_SIGN_AUTO:
1145
24.2k
        case UNUM_SIGN_ACCOUNTING:
1146
24.2k
            switch (signum) {
1147
5.81k
                case SIGNUM_NEG:
1148
7.39k
                case SIGNUM_NEG_ZERO:
1149
7.39k
                    return PATTERN_SIGN_TYPE_NEG;
1150
4.97k
                case SIGNUM_POS_ZERO:
1151
16.8k
                case SIGNUM_POS:
1152
16.8k
                    return PATTERN_SIGN_TYPE_POS;
1153
0
                default:
1154
0
                    break;
1155
24.2k
            }
1156
0
            break;
1157
1158
16
        case UNUM_SIGN_ALWAYS:
1159
20
        case UNUM_SIGN_ACCOUNTING_ALWAYS:
1160
20
            switch (signum) {
1161
6
                case SIGNUM_NEG:
1162
6
                case SIGNUM_NEG_ZERO:
1163
6
                    return PATTERN_SIGN_TYPE_NEG;
1164
4
                case SIGNUM_POS_ZERO:
1165
14
                case SIGNUM_POS:
1166
14
                    return PATTERN_SIGN_TYPE_POS_SIGN;
1167
0
                default:
1168
0
                    break;
1169
20
            }
1170
0
            break;
1171
1172
708k
        case UNUM_SIGN_EXCEPT_ZERO:
1173
708k
        case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
1174
708k
            switch (signum) {
1175
177k
                case SIGNUM_NEG:
1176
177k
                    return PATTERN_SIGN_TYPE_NEG;
1177
177k
                case SIGNUM_NEG_ZERO:
1178
354k
                case SIGNUM_POS_ZERO:
1179
354k
                    return PATTERN_SIGN_TYPE_POS;
1180
177k
                case SIGNUM_POS:
1181
177k
                    return PATTERN_SIGN_TYPE_POS_SIGN;
1182
0
                default:
1183
0
                    break;
1184
708k
            }
1185
0
            break;
1186
1187
4
        case UNUM_SIGN_NEGATIVE:
1188
8
        case UNUM_SIGN_ACCOUNTING_NEGATIVE:
1189
8
            switch (signum) {
1190
4
                case SIGNUM_NEG:
1191
4
                    return PATTERN_SIGN_TYPE_NEG;
1192
0
                case SIGNUM_NEG_ZERO:
1193
2
                case SIGNUM_POS_ZERO:
1194
4
                case SIGNUM_POS:
1195
4
                    return PATTERN_SIGN_TYPE_POS;
1196
0
                default:
1197
0
                    break;
1198
8
            }
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
732k
    }
1207
1208
732k
    UPRV_UNREACHABLE_EXIT;
1209
0
    return PATTERN_SIGN_TYPE_POS;
1210
732k
}
1211
1212
#endif /* #if !UCONFIG_NO_FORMATTING */