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