| // © 2017 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT |
| |
| #include "uassert.h" |
| #include "number_patternstring.h" |
| #include "unicode/utf16.h" |
| #include "number_utils.h" |
| |
| using namespace icu; |
| using namespace icu::number; |
| using namespace icu::number::impl; |
| |
| void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode &status) { |
| patternInfo.consumePattern(patternString, status); |
| } |
| |
| DecimalFormatProperties |
| PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, |
| UErrorCode &status) { |
| DecimalFormatProperties properties; |
| parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); |
| return properties; |
| } |
| |
| void PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, |
| IgnoreRounding ignoreRounding, UErrorCode &status) { |
| parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); |
| } |
| |
| char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const { |
| const Endpoints &endpoints = getEndpoints(flags); |
| if (index < 0 || index >= endpoints.end - endpoints.start) { |
| U_ASSERT(false); |
| } |
| return pattern.charAt(endpoints.start + index); |
| } |
| |
| int32_t ParsedPatternInfo::length(int32_t flags) const { |
| return getLengthFromEndpoints(getEndpoints(flags)); |
| } |
| |
| int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints &endpoints) { |
| return endpoints.end - endpoints.start; |
| } |
| |
| UnicodeString ParsedPatternInfo::getString(int32_t flags) const { |
| const Endpoints &endpoints = getEndpoints(flags); |
| if (endpoints.start == endpoints.end) { |
| return UnicodeString(); |
| } |
| // Create a new UnicodeString |
| return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start); |
| } |
| |
| const Endpoints &ParsedPatternInfo::getEndpoints(int32_t flags) const { |
| bool prefix = (flags & AFFIX_PREFIX) != 0; |
| bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; |
| bool padding = (flags & AFFIX_PADDING) != 0; |
| if (isNegative && padding) { |
| return negative.paddingEndpoints; |
| } else if (padding) { |
| return positive.paddingEndpoints; |
| } else if (prefix && isNegative) { |
| return negative.prefixEndpoints; |
| } else if (prefix) { |
| return positive.prefixEndpoints; |
| } else if (isNegative) { |
| return negative.suffixEndpoints; |
| } else { |
| return positive.suffixEndpoints; |
| } |
| } |
| |
| bool ParsedPatternInfo::positiveHasPlusSign() const { |
| return positive.hasPlusSign; |
| } |
| |
| bool ParsedPatternInfo::hasNegativeSubpattern() const { |
| return fHasNegativeSubpattern; |
| } |
| |
| bool ParsedPatternInfo::negativeHasMinusSign() const { |
| return negative.hasMinusSign; |
| } |
| |
| bool ParsedPatternInfo::hasCurrencySign() const { |
| return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign); |
| } |
| |
| bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &status) const { |
| return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status); |
| } |
| |
| bool ParsedPatternInfo::hasBody() const { |
| return positive.integerTotal > 0; |
| } |
| |
| ///////////////////////////////////////////////////// |
| /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION /// |
| ///////////////////////////////////////////////////// |
| |
| UChar32 ParsedPatternInfo::ParserState::peek() { |
| if (offset == pattern.length()) { |
| return -1; |
| } else { |
| return pattern.char32At(offset); |
| } |
| } |
| |
| UChar32 ParsedPatternInfo::ParserState::next() { |
| int codePoint = peek(); |
| offset += U16_LENGTH(codePoint); |
| return codePoint; |
| } |
| |
| void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode &status) { |
| if (U_FAILURE(status)) { return; } |
| this->pattern = patternString; |
| |
| // pattern := subpattern (';' subpattern)? |
| currentSubpattern = &positive; |
| consumeSubpattern(status); |
| if (U_FAILURE(status)) { return; } |
| if (state.peek() == u';') { |
| state.next(); // consume the ';' |
| // Don't consume the negative subpattern if it is empty (trailing ';') |
| if (state.peek() != -1) { |
| fHasNegativeSubpattern = true; |
| currentSubpattern = &negative; |
| consumeSubpattern(status); |
| if (U_FAILURE(status)) { return; } |
| } |
| } |
| if (state.peek() != -1) { |
| state.toParseException(u"Found unquoted special character"); |
| status = U_UNQUOTED_SPECIAL; |
| } |
| } |
| |
| void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) { |
| // subpattern := literals? number exponent? literals? |
| consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status); |
| if (U_FAILURE(status)) { return; } |
| consumeAffix(currentSubpattern->prefixEndpoints, status); |
| if (U_FAILURE(status)) { return; } |
| consumePadding(PadPosition::UNUM_PAD_AFTER_PREFIX, status); |
| if (U_FAILURE(status)) { return; } |
| consumeFormat(status); |
| if (U_FAILURE(status)) { return; } |
| consumeExponent(status); |
| if (U_FAILURE(status)) { return; } |
| consumePadding(PadPosition::UNUM_PAD_BEFORE_SUFFIX, status); |
| if (U_FAILURE(status)) { return; } |
| consumeAffix(currentSubpattern->suffixEndpoints, status); |
| if (U_FAILURE(status)) { return; } |
| consumePadding(PadPosition::UNUM_PAD_AFTER_SUFFIX, status); |
| if (U_FAILURE(status)) { return; } |
| } |
| |
| void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode &status) { |
| if (state.peek() != u'*') { |
| return; |
| } |
| if (!currentSubpattern->paddingLocation.isNull()) { |
| state.toParseException(u"Cannot have multiple pad specifiers"); |
| status = U_MULTIPLE_PAD_SPECIFIERS; |
| return; |
| } |
| currentSubpattern->paddingLocation = paddingLocation; |
| state.next(); // consume the '*' |
| currentSubpattern->paddingEndpoints.start = state.offset; |
| consumeLiteral(status); |
| currentSubpattern->paddingEndpoints.end = state.offset; |
| } |
| |
| void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) { |
| // literals := { literal } |
| endpoints.start = state.offset; |
| while (true) { |
| switch (state.peek()) { |
| case u'#': |
| case u'@': |
| case u';': |
| case u'*': |
| case u'.': |
| case u',': |
| case u'0': |
| case u'1': |
| case u'2': |
| case u'3': |
| case u'4': |
| case u'5': |
| case u'6': |
| case u'7': |
| case u'8': |
| case u'9': |
| case -1: |
| // Characters that cannot appear unquoted in a literal |
| // break outer; |
| goto after_outer; |
| |
| case u'%': |
| currentSubpattern->hasPercentSign = true; |
| break; |
| |
| case u'‰': |
| currentSubpattern->hasPerMilleSign = true; |
| break; |
| |
| case u'¤': |
| currentSubpattern->hasCurrencySign = true; |
| break; |
| |
| case u'-': |
| currentSubpattern->hasMinusSign = true; |
| break; |
| |
| case u'+': |
| currentSubpattern->hasPlusSign = true; |
| break; |
| |
| default: |
| break; |
| } |
| consumeLiteral(status); |
| if (U_FAILURE(status)) { return; } |
| } |
| after_outer: |
| endpoints.end = state.offset; |
| } |
| |
| void ParsedPatternInfo::consumeLiteral(UErrorCode &status) { |
| if (state.peek() == -1) { |
| state.toParseException(u"Expected unquoted literal but found EOL"); |
| status = U_PATTERN_SYNTAX_ERROR; |
| return; |
| } else if (state.peek() == u'\'') { |
| state.next(); // consume the starting quote |
| while (state.peek() != u'\'') { |
| if (state.peek() == -1) { |
| state.toParseException(u"Expected quoted literal but found EOL"); |
| status = U_PATTERN_SYNTAX_ERROR; |
| return; |
| } else { |
| state.next(); // consume a quoted character |
| } |
| } |
| state.next(); // consume the ending quote |
| } else { |
| // consume a non-quoted literal character |
| state.next(); |
| } |
| } |
| |
| void ParsedPatternInfo::consumeFormat(UErrorCode &status) { |
| consumeIntegerFormat(status); |
| if (U_FAILURE(status)) { return; } |
| if (state.peek() == u'.') { |
| state.next(); // consume the decimal point |
| currentSubpattern->hasDecimal = true; |
| currentSubpattern->widthExceptAffixes += 1; |
| consumeFractionFormat(status); |
| if (U_FAILURE(status)) { return; } |
| } |
| } |
| |
| void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) { |
| // Convenience reference: |
| ParsedSubpatternInfo &result = *currentSubpattern; |
| |
| while (true) { |
| switch (state.peek()) { |
| case u',': |
| result.widthExceptAffixes += 1; |
| result.groupingSizes <<= 16; |
| break; |
| |
| case u'#': |
| if (result.integerNumerals > 0) { |
| state.toParseException(u"# cannot follow 0 before decimal point"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| result.widthExceptAffixes += 1; |
| result.groupingSizes += 1; |
| if (result.integerAtSigns > 0) { |
| result.integerTrailingHashSigns += 1; |
| } else { |
| result.integerLeadingHashSigns += 1; |
| } |
| result.integerTotal += 1; |
| break; |
| |
| case u'@': |
| if (result.integerNumerals > 0) { |
| state.toParseException(u"Cannot mix 0 and @"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| if (result.integerTrailingHashSigns > 0) { |
| state.toParseException(u"Cannot nest # inside of a run of @"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| result.widthExceptAffixes += 1; |
| result.groupingSizes += 1; |
| result.integerAtSigns += 1; |
| result.integerTotal += 1; |
| break; |
| |
| case u'0': |
| case u'1': |
| case u'2': |
| case u'3': |
| case u'4': |
| case u'5': |
| case u'6': |
| case u'7': |
| case u'8': |
| case u'9': |
| if (result.integerAtSigns > 0) { |
| state.toParseException(u"Cannot mix @ and 0"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| result.widthExceptAffixes += 1; |
| result.groupingSizes += 1; |
| result.integerNumerals += 1; |
| result.integerTotal += 1; |
| if (!result.rounding.isZero() || state.peek() != u'0') { |
| result.rounding.appendDigit(static_cast<int8_t>(state.peek() - u'0'), 0, true); |
| } |
| break; |
| |
| default: |
| goto after_outer; |
| } |
| state.next(); // consume the symbol |
| } |
| |
| after_outer: |
| // Disallow patterns with a trailing ',' or with two ',' next to each other |
| auto grouping1 = static_cast<int16_t> (result.groupingSizes & 0xffff); |
| auto grouping2 = static_cast<int16_t> ((result.groupingSizes >> 16) & 0xffff); |
| auto grouping3 = static_cast<int16_t> ((result.groupingSizes >> 32) & 0xffff); |
| if (grouping1 == 0 && grouping2 != -1) { |
| state.toParseException(u"Trailing grouping separator is invalid"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| if (grouping2 == 0 && grouping3 != -1) { |
| state.toParseException(u"Grouping width of zero is invalid"); |
| status = U_PATTERN_SYNTAX_ERROR; |
| return; |
| } |
| } |
| |
| void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) { |
| // Convenience reference: |
| ParsedSubpatternInfo &result = *currentSubpattern; |
| |
| int32_t zeroCounter = 0; |
| while (true) { |
| switch (state.peek()) { |
| case u'#': |
| result.widthExceptAffixes += 1; |
| result.fractionHashSigns += 1; |
| result.fractionTotal += 1; |
| zeroCounter++; |
| break; |
| |
| case u'0': |
| case u'1': |
| case u'2': |
| case u'3': |
| case u'4': |
| case u'5': |
| case u'6': |
| case u'7': |
| case u'8': |
| case u'9': |
| if (result.fractionHashSigns > 0) { |
| state.toParseException(u"0 cannot follow # after decimal point"); |
| status = U_UNEXPECTED_TOKEN; |
| return; |
| } |
| result.widthExceptAffixes += 1; |
| result.fractionNumerals += 1; |
| result.fractionTotal += 1; |
| if (state.peek() == u'0') { |
| zeroCounter++; |
| } else { |
| result.rounding |
| .appendDigit(static_cast<int8_t>(state.peek() - u'0'), zeroCounter, false); |
| zeroCounter = 0; |
| } |
| break; |
| |
| default: |
| return; |
| } |
| state.next(); // consume the symbol |
| } |
| } |
| |
| void ParsedPatternInfo::consumeExponent(UErrorCode &status) { |
| // Convenience reference: |
| ParsedSubpatternInfo &result = *currentSubpattern; |
| |
| if (state.peek() != u'E') { |
| return; |
| } |
| if ((result.groupingSizes & 0xffff0000L) != 0xffff0000L) { |
| state.toParseException(u"Cannot have grouping separator in scientific notation"); |
| status = U_MALFORMED_EXPONENTIAL_PATTERN; |
| return; |
| } |
| state.next(); // consume the E |
| result.widthExceptAffixes++; |
| if (state.peek() == u'+') { |
| state.next(); // consume the + |
| result.exponentHasPlusSign = true; |
| result.widthExceptAffixes++; |
| } |
| while (state.peek() == u'0') { |
| state.next(); // consume the 0 |
| result.exponentZeros += 1; |
| result.widthExceptAffixes++; |
| } |
| } |
| |
| /////////////////////////////////////////////////// |
| /// END RECURSIVE DESCENT PARSER IMPLEMENTATION /// |
| /////////////////////////////////////////////////// |
| |
| void |
| PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties, |
| IgnoreRounding ignoreRounding, UErrorCode &status) { |
| if (pattern.length() == 0) { |
| // Backwards compatibility requires that we reset to the default values. |
| // TODO: Only overwrite the properties that "saveToProperties" normally touches? |
| properties.clear(); |
| return; |
| } |
| |
| ParsedPatternInfo patternInfo; |
| parseToPatternInfo(pattern, patternInfo, status); |
| if (U_FAILURE(status)) { return; } |
| patternInfoToProperties(properties, patternInfo, ignoreRounding, status); |
| } |
| |
| void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, |
| ParsedPatternInfo& patternInfo, |
| IgnoreRounding _ignoreRounding, UErrorCode &status) { |
| // Translate from PatternParseResult to Properties. |
| // Note that most data from "negative" is ignored per the specification of DecimalFormat. |
| |
| const ParsedSubpatternInfo &positive = patternInfo.positive; |
| |
| bool ignoreRounding; |
| if (_ignoreRounding == IGNORE_ROUNDING_NEVER) { |
| ignoreRounding = false; |
| } else if (_ignoreRounding == IGNORE_ROUNDING_IF_CURRENCY) { |
| ignoreRounding = positive.hasCurrencySign; |
| } else { |
| U_ASSERT(_ignoreRounding == IGNORE_ROUNDING_ALWAYS); |
| ignoreRounding = true; |
| } |
| |
| // Grouping settings |
| auto grouping1 = static_cast<int16_t> (positive.groupingSizes & 0xffff); |
| auto grouping2 = static_cast<int16_t> ((positive.groupingSizes >> 16) & 0xffff); |
| auto grouping3 = static_cast<int16_t> ((positive.groupingSizes >> 32) & 0xffff); |
| if (grouping2 != -1) { |
| properties.groupingSize = grouping1; |
| } else { |
| properties.groupingSize = -1; |
| } |
| if (grouping3 != -1) { |
| properties.secondaryGroupingSize = grouping2; |
| } else { |
| properties.secondaryGroupingSize = -1; |
| } |
| |
| // For backwards compatibility, require that the pattern emit at least one min digit. |
| int minInt, minFrac; |
| if (positive.integerTotal == 0 && positive.fractionTotal > 0) { |
| // patterns like ".##" |
| minInt = 0; |
| minFrac = uprv_max(1, positive.fractionNumerals); |
| } else if (positive.integerNumerals == 0 && positive.fractionNumerals == 0) { |
| // patterns like "#.##" |
| minInt = 1; |
| minFrac = 0; |
| } else { |
| minInt = positive.integerNumerals; |
| minFrac = positive.fractionNumerals; |
| } |
| |
| // Rounding settings |
| // Don't set basic rounding when there is a currency sign; defer to CurrencyUsage |
| if (positive.integerAtSigns > 0) { |
| properties.minimumFractionDigits = -1; |
| properties.maximumFractionDigits = -1; |
| properties.roundingIncrement = 0.0; |
| properties.minimumSignificantDigits = positive.integerAtSigns; |
| properties.maximumSignificantDigits = |
| positive.integerAtSigns + positive.integerTrailingHashSigns; |
| } else if (!positive.rounding.isZero()) { |
| if (!ignoreRounding) { |
| properties.minimumFractionDigits = minFrac; |
| properties.maximumFractionDigits = positive.fractionTotal; |
| properties.roundingIncrement = positive.rounding.toDouble(); |
| } else { |
| properties.minimumFractionDigits = -1; |
| properties.maximumFractionDigits = -1; |
| properties.roundingIncrement = 0.0; |
| } |
| properties.minimumSignificantDigits = -1; |
| properties.maximumSignificantDigits = -1; |
| } else { |
| if (!ignoreRounding) { |
| properties.minimumFractionDigits = minFrac; |
| properties.maximumFractionDigits = positive.fractionTotal; |
| properties.roundingIncrement = 0.0; |
| } else { |
| properties.minimumFractionDigits = -1; |
| properties.maximumFractionDigits = -1; |
| properties.roundingIncrement = 0.0; |
| } |
| properties.minimumSignificantDigits = -1; |
| properties.maximumSignificantDigits = -1; |
| } |
| |
| // If the pattern ends with a '.' then force the decimal point. |
| if (positive.hasDecimal && positive.fractionTotal == 0) { |
| properties.decimalSeparatorAlwaysShown = true; |
| } else { |
| properties.decimalSeparatorAlwaysShown = false; |
| } |
| |
| // Scientific notation settings |
| if (positive.exponentZeros > 0) { |
| properties.exponentSignAlwaysShown = positive.exponentHasPlusSign; |
| properties.minimumExponentDigits = positive.exponentZeros; |
| if (positive.integerAtSigns == 0) { |
| // patterns without '@' can define max integer digits, used for engineering notation |
| properties.minimumIntegerDigits = positive.integerNumerals; |
| properties.maximumIntegerDigits = positive.integerTotal; |
| } else { |
| // patterns with '@' cannot define max integer digits |
| properties.minimumIntegerDigits = 1; |
| properties.maximumIntegerDigits = -1; |
| } |
| } else { |
| properties.exponentSignAlwaysShown = false; |
| properties.minimumExponentDigits = -1; |
| properties.minimumIntegerDigits = minInt; |
| properties.maximumIntegerDigits = -1; |
| } |
| |
| // Compute the affix patterns (required for both padding and affixes) |
| UnicodeString posPrefix = patternInfo.getString(AffixPatternProvider::AFFIX_PREFIX); |
| UnicodeString posSuffix = patternInfo.getString(0); |
| |
| // Padding settings |
| if (!positive.paddingLocation.isNull()) { |
| // The width of the positive prefix and suffix templates are included in the padding |
| int paddingWidth = |
| positive.widthExceptAffixes + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) + |
| AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status); |
| properties.formatWidth = paddingWidth; |
| UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING); |
| if (rawPaddingString.length() == 1) { |
| properties.padString = rawPaddingString; |
| } else if (rawPaddingString.length() == 2) { |
| if (rawPaddingString.charAt(0) == u'\'') { |
| properties.padString.setTo(u"'", -1); |
| } else { |
| properties.padString = rawPaddingString; |
| } |
| } else { |
| properties.padString = UnicodeString(rawPaddingString, 1, rawPaddingString.length() - 2); |
| } |
| properties.padPosition = positive.paddingLocation; |
| } else { |
| properties.formatWidth = -1; |
| properties.padString.setToBogus(); |
| properties.padPosition.nullify(); |
| } |
| |
| // Set the affixes |
| // Always call the setter, even if the prefixes are empty, especially in the case of the |
| // negative prefix pattern, to prevent default values from overriding the pattern. |
| properties.positivePrefixPattern = posPrefix; |
| properties.positiveSuffixPattern = posSuffix; |
| if (patternInfo.fHasNegativeSubpattern) { |
| properties.negativePrefixPattern = patternInfo.getString( |
| AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN | AffixPatternProvider::AFFIX_PREFIX); |
| properties.negativeSuffixPattern = patternInfo.getString( |
| AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN); |
| } else { |
| properties.negativePrefixPattern.setToBogus(); |
| properties.negativeSuffixPattern.setToBogus(); |
| } |
| |
| // Set the magnitude multiplier |
| if (positive.hasPercentSign) { |
| properties.magnitudeMultiplier = 2; |
| } else if (positive.hasPerMilleSign) { |
| properties.magnitudeMultiplier = 3; |
| } else { |
| properties.magnitudeMultiplier = 0; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| /// End PatternStringParser.java; begin PatternStringUtils.java /// |
| /////////////////////////////////////////////////////////////////// |
| |
| UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties &properties, |
| UErrorCode &status) { |
| UnicodeString sb; |
| |
| // Convenience references |
| // The uprv_min() calls prevent DoS |
| int dosMax = 100; |
| int groupingSize = uprv_min(properties.secondaryGroupingSize, dosMax); |
| int firstGroupingSize = uprv_min(properties.groupingSize, dosMax); |
| int paddingWidth = uprv_min(properties.formatWidth, dosMax); |
| NullableValue<PadPosition> paddingLocation = properties.padPosition; |
| UnicodeString paddingString = properties.padString; |
| int minInt = uprv_max(uprv_min(properties.minimumIntegerDigits, dosMax), 0); |
| int maxInt = uprv_min(properties.maximumIntegerDigits, dosMax); |
| int minFrac = uprv_max(uprv_min(properties.minimumFractionDigits, dosMax), 0); |
| int maxFrac = uprv_min(properties.maximumFractionDigits, dosMax); |
| int minSig = uprv_min(properties.minimumSignificantDigits, dosMax); |
| int maxSig = uprv_min(properties.maximumSignificantDigits, dosMax); |
| bool alwaysShowDecimal = properties.decimalSeparatorAlwaysShown; |
| int exponentDigits = uprv_min(properties.minimumExponentDigits, dosMax); |
| bool exponentShowPlusSign = properties.exponentSignAlwaysShown; |
| UnicodeString pp = properties.positivePrefix; |
| UnicodeString ppp = properties.positivePrefixPattern; |
| UnicodeString ps = properties.positiveSuffix; |
| UnicodeString psp = properties.positiveSuffixPattern; |
| UnicodeString np = properties.negativePrefix; |
| UnicodeString npp = properties.negativePrefixPattern; |
| UnicodeString ns = properties.negativeSuffix; |
| UnicodeString nsp = properties.negativeSuffixPattern; |
| |
| // Prefixes |
| if (!ppp.isBogus()) { |
| sb.append(ppp); |
| } |
| sb.append(AffixUtils::escape(UnicodeStringCharSequence(pp))); |
| int afterPrefixPos = sb.length(); |
| |
| // Figure out the grouping sizes. |
| int grouping1, grouping2, grouping; |
| if (groupingSize != uprv_min(dosMax, -1) && firstGroupingSize != uprv_min(dosMax, -1) && |
| groupingSize != firstGroupingSize) { |
| grouping = groupingSize; |
| grouping1 = groupingSize; |
| grouping2 = firstGroupingSize; |
| } else if (groupingSize != uprv_min(dosMax, -1)) { |
| grouping = groupingSize; |
| grouping1 = 0; |
| grouping2 = groupingSize; |
| } else if (firstGroupingSize != uprv_min(dosMax, -1)) { |
| grouping = groupingSize; |
| grouping1 = 0; |
| grouping2 = firstGroupingSize; |
| } else { |
| grouping = 0; |
| grouping1 = 0; |
| grouping2 = 0; |
| } |
| int groupingLength = grouping1 + grouping2 + 1; |
| |
| // Figure out the digits we need to put in the pattern. |
| double roundingInterval = properties.roundingIncrement; |
| UnicodeString digitsString; |
| int digitsStringScale = 0; |
| if (maxSig != uprv_min(dosMax, -1)) { |
| // Significant Digits. |
| while (digitsString.length() < minSig) { |
| digitsString.append(u'@'); |
| } |
| while (digitsString.length() < maxSig) { |
| digitsString.append(u'#'); |
| } |
| } else if (roundingInterval != 0.0) { |
| // Rounding Interval. |
| digitsStringScale = minFrac; |
| // TODO: Check for DoS here? |
| DecimalQuantity incrementQuantity; |
| incrementQuantity.setToDouble(roundingInterval); |
| incrementQuantity.adjustMagnitude(minFrac); |
| incrementQuantity.roundToMagnitude(0, kDefaultMode, status); |
| UnicodeString str = incrementQuantity.toPlainString(); |
| if (str.charAt(0) == u'-') { |
| // TODO: Unsupported operation exception or fail silently? |
| digitsString.append(str, 1, str.length() - 1); |
| } else { |
| digitsString.append(str); |
| } |
| } |
| while (digitsString.length() + digitsStringScale < minInt) { |
| digitsString.insert(0, u'0'); |
| } |
| while (-digitsStringScale < minFrac) { |
| digitsString.append(u'0'); |
| digitsStringScale--; |
| } |
| |
| // Write the digits to the string builder |
| int m0 = uprv_max(groupingLength, digitsString.length() + digitsStringScale); |
| m0 = (maxInt != dosMax) ? uprv_max(maxInt, m0) - 1 : m0 - 1; |
| int mN = (maxFrac != dosMax) ? uprv_min(-maxFrac, digitsStringScale) : digitsStringScale; |
| for (int magnitude = m0; magnitude >= mN; magnitude--) { |
| int di = digitsString.length() + digitsStringScale - magnitude - 1; |
| if (di < 0 || di >= digitsString.length()) { |
| sb.append(u'#'); |
| } else { |
| sb.append(digitsString.charAt(di)); |
| } |
| if (magnitude > grouping2 && grouping > 0 && (magnitude - grouping2) % grouping == 0) { |
| sb.append(u','); |
| } else if (magnitude > 0 && magnitude == grouping2) { |
| sb.append(u','); |
| } else if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) { |
| sb.append(u'.'); |
| } |
| } |
| |
| // Exponential notation |
| if (exponentDigits != uprv_min(dosMax, -1)) { |
| sb.append(u'E'); |
| if (exponentShowPlusSign) { |
| sb.append(u'+'); |
| } |
| for (int i = 0; i < exponentDigits; i++) { |
| sb.append(u'0'); |
| } |
| } |
| |
| // Suffixes |
| int beforeSuffixPos = sb.length(); |
| if (!psp.isBogus()) { |
| sb.append(psp); |
| } |
| sb.append(AffixUtils::escape(UnicodeStringCharSequence(ps))); |
| |
| // Resolve Padding |
| if (paddingWidth != -1 && !paddingLocation.isNull()) { |
| while (paddingWidth - sb.length() > 0) { |
| sb.insert(afterPrefixPos, u'#'); |
| beforeSuffixPos++; |
| } |
| int addedLength; |
| switch (paddingLocation.get(status)) { |
| case PadPosition::UNUM_PAD_BEFORE_PREFIX: |
| addedLength = escapePaddingString(paddingString, sb, 0, status); |
| sb.insert(0, u'*'); |
| afterPrefixPos += addedLength + 1; |
| beforeSuffixPos += addedLength + 1; |
| break; |
| case PadPosition::UNUM_PAD_AFTER_PREFIX: |
| addedLength = escapePaddingString(paddingString, sb, afterPrefixPos, status); |
| sb.insert(afterPrefixPos, u'*'); |
| afterPrefixPos += addedLength + 1; |
| beforeSuffixPos += addedLength + 1; |
| break; |
| case PadPosition::UNUM_PAD_BEFORE_SUFFIX: |
| escapePaddingString(paddingString, sb, beforeSuffixPos, status); |
| sb.insert(beforeSuffixPos, u'*'); |
| break; |
| case PadPosition::UNUM_PAD_AFTER_SUFFIX: |
| sb.append(u'*'); |
| escapePaddingString(paddingString, sb, sb.length(), status); |
| break; |
| } |
| if (U_FAILURE(status)) { return sb; } |
| } |
| |
| // Negative affixes |
| // Ignore if the negative prefix pattern is "-" and the negative suffix is empty |
| if (!np.isBogus() || !ns.isBogus() || (npp.isBogus() && !nsp.isBogus()) || |
| (!npp.isBogus() && (npp.length() != 1 || npp.charAt(0) != u'-' || nsp.length() != 0))) { |
| sb.append(u';'); |
| if (!npp.isBogus()) { |
| sb.append(npp); |
| } |
| sb.append(AffixUtils::escape(UnicodeStringCharSequence(np))); |
| // Copy the positive digit format into the negative. |
| // This is optional; the pattern is the same as if '#' were appended here instead. |
| sb.append(sb, afterPrefixPos, beforeSuffixPos); |
| if (!nsp.isBogus()) { |
| sb.append(nsp); |
| } |
| sb.append(AffixUtils::escape(UnicodeStringCharSequence(ns))); |
| } |
| |
| return sb; |
| } |
| |
| int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex, |
| UErrorCode &status) { |
| (void)status; |
| if (input.length() == 0) { |
| input.setTo(kFallbackPaddingString, -1); |
| } |
| int startLength = output.length(); |
| if (input.length() == 1) { |
| if (input.compare(u"'", -1) == 0) { |
| output.insert(startIndex, u"''", -1); |
| } else { |
| output.insert(startIndex, input); |
| } |
| } else { |
| output.insert(startIndex, u'\''); |
| int offset = 1; |
| for (int i = 0; i < input.length(); i++) { |
| // it's okay to deal in chars here because the quote mark is the only interesting thing. |
| char16_t ch = input.charAt(i); |
| if (ch == u'\'') { |
| output.insert(startIndex + offset, u"''", -1); |
| offset += 2; |
| } else { |
| output.insert(startIndex + offset, ch); |
| offset += 1; |
| } |
| } |
| output.insert(startIndex + offset, u'\''); |
| } |
| return output.length() - startLength; |
| } |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |