Coverage Report

Created: 2018-09-25 14:53

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