blob: 20178824b0e20a1d3217c83059089c7a2e93b556 [file] [log] [blame]
// © 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 */