Coverage Report

Created: 2025-06-24 06:43

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