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