#include "patternprops.h"
#include "messageimpl.h"
#include "msgfmt_impl.h"
+#include "plurrule_impl.h"
#include "uassert.h"
#include "uelement.h"
#include "uhash.h"
append(s);
}
}
+ void formatAndAppend(const Format* formatter, const Formattable& arg,
+ const UnicodeString &argString, UErrorCode& ec) {
+ if (!argString.isEmpty()) {
+ if (U_SUCCESS(ec)) {
+ append(argString);
+ }
+ } else {
+ formatAndAppend(formatter, arg, ec);
+ }
+ }
int32_t length() {
return len;
}
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
- pluralProvider(&fLocale, UPLURAL_TYPE_CARDINAL),
- ordinalProvider(&fLocale, UPLURAL_TYPE_ORDINAL)
+ pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
+ ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, success);
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
- pluralProvider(&fLocale, UPLURAL_TYPE_CARDINAL),
- ordinalProvider(&fLocale, UPLURAL_TYPE_ORDINAL)
+ pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
+ ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, success);
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
- pluralProvider(&fLocale, UPLURAL_TYPE_CARDINAL),
- ordinalProvider(&fLocale, UPLURAL_TYPE_ORDINAL)
+ pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
+ ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, parseError, success);
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
- pluralProvider(&fLocale, UPLURAL_TYPE_CARDINAL),
- ordinalProvider(&fLocale, UPLURAL_TYPE_ORDINAL)
+ pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
+ ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
// This will take care of creating the hash tables (since they are NULL).
UErrorCode ec = U_ZERO_ERROR;
defaultDateFormat = NULL;
fLocale = theLocale;
setLocaleIDs(fLocale.getName(), fLocale.getName());
- pluralProvider.reset(&fLocale);
- ordinalProvider.reset(&fLocale);
+ pluralProvider.reset();
+ ordinalProvider.reset();
}
}
UnicodeString MessageFormat::getArgName(int32_t partIndex) {
const MessagePattern::Part& part = msgPattern.getPart(partIndex);
- if (part.getType() == UMSGPAT_PART_TYPE_ARG_NAME) {
- return msgPattern.getSubstring(part);
- } else {
- UnicodeString temp;
- return itos(part.getValue(), temp);
- }
+ return msgPattern.getSubstring(part);
}
StringEnumeration*
UnicodeStringAppendable usapp(appendTo);
AppendableWrapper app(usapp);
- format(0, 0.0, arguments, argumentNames, cnt, app, pos, status);
+ format(0, NULL, arguments, argumentNames, cnt, app, pos, status);
return appendTo;
}
+namespace {
+
+/**
+ * Mutable input/output values for the PluralSelectorProvider.
+ * Separate so that it is possible to make MessageFormat Freezable.
+ */
+class PluralSelectorContext {
+public:
+ PluralSelectorContext(int32_t start, const UnicodeString &name,
+ const Formattable &num, double off, UErrorCode &errorCode)
+ : startIndex(start), argName(name), offset(off),
+ numberArgIndex(-1), formatter(NULL), forReplaceNumber(FALSE) {
+ // number needs to be set even when select() is not called.
+ // Keep it as a Number/Formattable:
+ // For format() methods, and to preserve information (e.g., BigDecimal).
+ if(off == 0) {
+ number = num;
+ } else {
+ number = num.getDouble(errorCode) - off;
+ }
+ }
+
+ // Input values for plural selection with decimals.
+ int32_t startIndex;
+ const UnicodeString &argName;
+ /** argument number - plural offset */
+ Formattable number;
+ double offset;
+ // Output values for plural selection with decimals.
+ /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
+ int32_t numberArgIndex;
+ const Format *formatter;
+ /** formatted argument number - plural offset */
+ UnicodeString numberString;
+ /** TRUE if number-offset was formatted with the stock number formatter */
+ UBool forReplaceNumber;
+};
+
+} // namespace
+
// if argumentNames is NULL, this means arguments is a numeric array.
// arguments can not be NULL.
-void MessageFormat::format(int32_t msgStart, double pluralNumber,
+// We use const void *plNumber rather than const PluralSelectorContext *pluralNumber
+// so that we need not declare the PluralSelectorContext in the public header file.
+void MessageFormat::format(int32_t msgStart, const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
}
prevIndex = part->getLimit();
if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
- const NumberFormat* nf = getDefaultNumberFormat(success);
- appendTo.formatAndAppend(nf, Formattable(pluralNumber), success);
+ const PluralSelectorContext &pluralNumber =
+ *static_cast<const PluralSelectorContext *>(plNumber);
+ if(pluralNumber.forReplaceNumber) {
+ // number-offset was already formatted.
+ appendTo.formatAndAppend(pluralNumber.formatter,
+ pluralNumber.number, pluralNumber.numberString, success);
+ } else {
+ const NumberFormat* nf = getDefaultNumberFormat(success);
+ appendTo.formatAndAppend(nf, pluralNumber.number, success);
+ }
continue;
}
if (type != UMSGPAT_PART_TYPE_ARG_START) {
UMessagePatternArgType argType = part->getArgType();
part = &msgPattern.getPart(++i);
const Formattable* arg;
- UnicodeString noArg;
+ UBool noArg = FALSE;
+ UnicodeString argName = msgPattern.getSubstring(*part);
if (argumentNames == NULL) {
int32_t argNumber = part->getValue(); // ARG_NUMBER
if (0 <= argNumber && argNumber < cnt) {
arg = arguments + argNumber;
} else {
arg = NULL;
- noArg.append(LEFT_CURLY_BRACE);
- itos(argNumber, noArg);
- noArg.append(RIGHT_CURLY_BRACE);
+ noArg = TRUE;
}
} else {
- UnicodeString key;
- if (part->getType() == UMSGPAT_PART_TYPE_ARG_NAME) {
- key = msgPattern.getSubstring(*part);
- } else /* UMSGPAT_PART_TYPE_ARG_NUMBER */ {
- itos(part->getValue(), key);
- }
- arg = getArgFromListByName(arguments, argumentNames, cnt, key);
+ arg = getArgFromListByName(arguments, argumentNames, cnt, argName);
if (arg == NULL) {
- noArg.append(LEFT_CURLY_BRACE);
- noArg.append(key);
- noArg.append(RIGHT_CURLY_BRACE);
+ noArg = TRUE;
}
}
++i;
int32_t prevDestLength = appendTo.length();
const Format* formatter = NULL;
- if (!noArg.isEmpty()) {
- appendTo.append(noArg);
+ if (noArg) {
+ appendTo.append(
+ UnicodeString(LEFT_CURLY_BRACE).append(argName).append(RIGHT_CURLY_BRACE));
} else if (arg == NULL) {
appendTo.append(NULL_STRING, 4);
+ } else if(plNumber!=NULL &&
+ static_cast<const PluralSelectorContext *>(plNumber)->numberArgIndex==(i-2)) {
+ const PluralSelectorContext &pluralNumber =
+ *static_cast<const PluralSelectorContext *>(plNumber);
+ if(pluralNumber.offset == 0) {
+ // The number was already formatted with this formatter.
+ appendTo.formatAndAppend(pluralNumber.formatter, pluralNumber.number,
+ pluralNumber.numberString, success);
+ } else {
+ // Do not use the formatted (number-offset) string for a named argument
+ // that formats the number without subtracting the offset.
+ appendTo.formatAndAppend(pluralNumber.formatter, *arg, success);
+ }
} else if ((formatter = getCachedFormatter(i -2))) {
// Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
if (dynamic_cast<const ChoiceFormat*>(formatter) ||
(subMsgString.indexOf(SINGLE_QUOTE) >= 0 && !MessageImpl::jdkAposMode(msgPattern))
) {
MessageFormat subMsgFormat(subMsgString, fLocale, success);
- subMsgFormat.format(0, 0, arguments, argumentNames, cnt, appendTo, ignore, success);
+ subMsgFormat.format(0, NULL, arguments, argumentNames, cnt, appendTo, ignore, success);
} else {
appendTo.append(subMsgString);
}
// because only this one converts non-double numeric types to double.
const double number = arg->getDouble(success);
int32_t subMsgStart = ChoiceFormat::findSubMessage(msgPattern, i, number);
- formatComplexSubMessage(subMsgStart, 0, arguments, argumentNames,
+ formatComplexSubMessage(subMsgStart, NULL, arguments, argumentNames,
cnt, appendTo, success);
} else if (UMSGPAT_ARG_TYPE_HAS_PLURAL_STYLE(argType)) {
if (!arg->isNumeric()) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
- const PluralFormat::PluralSelector &selector =
+ const PluralSelectorProvider &selector =
argType == UMSGPAT_ARG_TYPE_PLURAL ? pluralProvider : ordinalProvider;
// We must use the Formattable::getDouble() variant with the UErrorCode parameter
// because only this one converts non-double numeric types to double.
- double number = arg->getDouble(success);
- int32_t subMsgStart = PluralFormat::findSubMessage(msgPattern, i, selector, number,
- success);
double offset = msgPattern.getPluralOffset(i);
- formatComplexSubMessage(subMsgStart, number-offset, arguments, argumentNames,
+ PluralSelectorContext context(i, argName, *arg, offset, success);
+ int32_t subMsgStart = PluralFormat::findSubMessage(
+ msgPattern, i, selector, &context, arg->getDouble(success), success);
+ formatComplexSubMessage(subMsgStart, &context, arguments, argumentNames,
cnt, appendTo, success);
} else if (argType == UMSGPAT_ARG_TYPE_SELECT) {
int32_t subMsgStart = SelectFormat::findSubMessage(msgPattern, i, arg->getString(success), success);
- formatComplexSubMessage(subMsgStart, 0, arguments, argumentNames,
+ formatComplexSubMessage(subMsgStart, NULL, arguments, argumentNames,
cnt, appendTo, success);
} else {
// This should never happen.
void MessageFormat::formatComplexSubMessage(int32_t msgStart,
- double pluralNumber,
+ const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
}
if (!MessageImpl::jdkAposMode(msgPattern)) {
- format(msgStart, pluralNumber, arguments, argumentNames, cnt, appendTo, NULL, success);
+ format(msgStart, plNumber, arguments, argumentNames, cnt, appendTo, NULL, success);
return;
}
} else if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER || type == UMSGPAT_PART_TYPE_SKIP_SYNTAX) {
sb.append(msgString, prevIndex, index - prevIndex);
if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
- const NumberFormat* nf = getDefaultNumberFormat(success);
- sb.append(nf->format(pluralNumber, sb, success));
+ const PluralSelectorContext &pluralNumber =
+ *static_cast<const PluralSelectorContext *>(plNumber);
+ if(pluralNumber.forReplaceNumber) {
+ // number-offset was already formatted.
+ sb.append(pluralNumber.numberString);
+ } else {
+ const NumberFormat* nf = getDefaultNumberFormat(success);
+ sb.append(nf->format(pluralNumber.number, sb, success));
+ }
}
prevIndex = part.getLimit();
} else if (type == UMSGPAT_PART_TYPE_ARG_START) {
UnicodeString emptyPattern; // gcc 3.3.3 fails with "UnicodeString()" as the first parameter.
MessageFormat subMsgFormat(emptyPattern, fLocale, success);
subMsgFormat.applyPattern(sb, UMSGPAT_APOS_DOUBLE_REQUIRED, NULL, success);
- subMsgFormat.format(0, 0, arguments, argumentNames, cnt, appendTo, NULL, success);
+ subMsgFormat.format(0, NULL, arguments, argumentNames, cnt, appendTo, NULL, success);
} else {
appendTo.append(sb);
}
*/
}
+int32_t
+MessageFormat::findOtherSubMessage(int32_t partIndex) const {
+ int32_t count=msgPattern.countParts();
+ const MessagePattern::Part *part = &msgPattern.getPart(partIndex);
+ if(MessagePattern::Part::hasNumericValue(part->getType())) {
+ ++partIndex;
+ }
+ // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
+ // until ARG_LIMIT or end of plural-only pattern.
+ UnicodeString other(FALSE, OTHER_STRING, 5);
+ do {
+ part=&msgPattern.getPart(partIndex++);
+ UMessagePatternPartType type=part->getType();
+ if(type==UMSGPAT_PART_TYPE_ARG_LIMIT) {
+ break;
+ }
+ U_ASSERT(type==UMSGPAT_PART_TYPE_ARG_SELECTOR);
+ // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
+ if(msgPattern.partSubstringMatches(*part, other)) {
+ return partIndex;
+ }
+ if(MessagePattern::Part::hasNumericValue(msgPattern.getPartType(partIndex))) {
+ ++partIndex; // skip the numeric-value part of "=1" etc.
+ }
+ partIndex=msgPattern.getLimitPartIndex(partIndex);
+ } while(++partIndex<count);
+ return 0;
+}
+
+int32_t
+MessageFormat::findFirstPluralNumberArg(int32_t msgStart, const UnicodeString &argName) const {
+ for(int32_t i=msgStart+1;; ++i) {
+ const MessagePattern::Part &part=msgPattern.getPart(i);
+ UMessagePatternPartType type=part.getType();
+ if(type==UMSGPAT_PART_TYPE_MSG_LIMIT) {
+ return 0;
+ }
+ if(type==UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
+ return -1;
+ }
+ if(type==UMSGPAT_PART_TYPE_ARG_START) {
+ UMessagePatternArgType argType=part.getArgType();
+ if(!argName.isEmpty() && (argType==UMSGPAT_ARG_TYPE_NONE || argType==UMSGPAT_ARG_TYPE_SIMPLE)) {
+ // ARG_NUMBER or ARG_NAME
+ if(msgPattern.partSubstringMatches(msgPattern.getPart(i+1), argName)) {
+ return i;
+ }
+ }
+ i=msgPattern.getLimitPartIndex(i);
+ }
+ }
+}
+
void MessageFormat::copyObjects(const MessageFormat& that, UErrorCode& ec) {
// Deep copy pointer fields.
// We need not copy the formatAliases because they are re-filled
delete fFormatNames;
}
-
-MessageFormat::PluralSelectorProvider::PluralSelectorProvider(const Locale* loc, UPluralType t)
- : locale(loc), rules(NULL), type(t) {
+MessageFormat::PluralSelectorProvider::PluralSelectorProvider(const MessageFormat &mf, UPluralType t)
+ : msgFormat(mf), rules(NULL), type(t) {
}
MessageFormat::PluralSelectorProvider::~PluralSelectorProvider() {
- // We own the rules but not the locale.
delete rules;
}
-UnicodeString MessageFormat::PluralSelectorProvider::select(double number, UErrorCode& ec) const {
+UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double number,
+ UErrorCode& ec) const {
if (U_FAILURE(ec)) {
return UnicodeString(FALSE, OTHER_STRING, 5);
}
MessageFormat::PluralSelectorProvider* t = const_cast<MessageFormat::PluralSelectorProvider*>(this);
if(rules == NULL) {
- t->rules = PluralRules::forLocale(*locale, type, ec);
+ t->rules = PluralRules::forLocale(msgFormat.fLocale, type, ec);
if (U_FAILURE(ec)) {
return UnicodeString(FALSE, OTHER_STRING, 5);
}
}
- return rules->select(number);
+ // Select a sub-message according to how the number is formatted,
+ // which is specified in the selected sub-message.
+ // We avoid this circle by looking at how
+ // the number is formatted in the "other" sub-message
+ // which must always be present and usually contains the number.
+ // Message authors should be consistent across sub-messages.
+ PluralSelectorContext &context = *static_cast<PluralSelectorContext *>(ctx);
+ int32_t otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
+ context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
+ if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != NULL) {
+ context.formatter =
+ (const Format*)uhash_iget(msgFormat.cachedFormatters, context.numberArgIndex);
+ }
+ if(context.formatter == NULL) {
+ context.formatter = msgFormat.getDefaultNumberFormat(ec);
+ context.forReplaceNumber = TRUE;
+ }
+ U_ASSERT(context.number.getDouble(ec) == number); // argument number minus the offset
+ context.formatter->format(context.number, context.numberString, ec);
+ const DecimalFormat *decFmt = dynamic_cast<const DecimalFormat *>(context.formatter);
+ if(decFmt != NULL) {
+ FixedDecimal dec = decFmt->getFixedDecimal(context.number, ec);
+ return rules->select(dec);
+ } else {
+ return rules->select(number);
+ }
}
-void MessageFormat::PluralSelectorProvider::reset(const Locale* loc) {
- locale = loc;
+void MessageFormat::PluralSelectorProvider::reset() {
delete rules;
rules = NULL;
}
/*
*******************************************************************************
-* Copyright (C) 2009-2012, International Business Machines Corporation and
+* Copyright (C) 2009-2013, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
* File PLURFMT.CPP
-*
-* Modification History:
-*
-* Date Name Description
*******************************************************************************
*/
+#include "unicode/decimfmt.h"
#include "unicode/messagepattern.h"
#include "unicode/plurfmt.h"
#include "unicode/plurrule.h"
if (U_FAILURE(status)) return appendTo;
if (obj.isNumeric()) {
- return format(obj.getDouble(), appendTo, pos, status);
+ return format(obj, obj.getDouble(), appendTo, pos, status);
} else {
status = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
PluralFormat::format(int32_t number, UErrorCode& status) const {
FieldPosition fpos(0);
UnicodeString result;
- return format(number, result, fpos, status);
+ return format(Formattable(number), number, result, fpos, status);
}
UnicodeString
PluralFormat::format(double number, UErrorCode& status) const {
FieldPosition fpos(0);
UnicodeString result;
- return format(number, result, fpos, status);
+ return format(Formattable(number), number, result, fpos, status);
}
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const {
- return format((double)number, appendTo, pos, status);
+ return format(Formattable(number), (double)number, appendTo, pos, status);
}
UnicodeString&
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const {
+ return format(Formattable(number), (double)number, appendTo, pos, status);
+}
+
+UnicodeString&
+PluralFormat::format(const Formattable& numberObject, double number,
+ UnicodeString& appendTo,
+ FieldPosition& pos,
+ UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
if (msgPattern.countParts() == 0) {
- return numberFormat->format(number, appendTo, pos);
+ return numberFormat->format(numberObject, appendTo, pos, status);
}
// Get the appropriate sub-message.
- int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, number, status);
+ // Select it based on the formatted number-offset.
+ double numberMinusOffset = number - offset;
+ UnicodeString numberString;
+ FieldPosition ignorePos;
+ FixedDecimal dec(numberMinusOffset);
+ if (offset == 0) {
+ numberFormat->format(numberObject, numberString, ignorePos, status); // could be BigDecimal etc.
+ DecimalFormat *decFmt = dynamic_cast<DecimalFormat *>(numberFormat);
+ if(decFmt != NULL) {
+ dec = decFmt->getFixedDecimal(numberObject, status);
+ }
+ } else {
+ numberFormat->format(numberMinusOffset, numberString, ignorePos, status);
+ DecimalFormat *decFmt = dynamic_cast<DecimalFormat *>(numberFormat);
+ if(decFmt != NULL) {
+ dec = decFmt->getFixedDecimal(numberMinusOffset, status);
+ }
+ }
+ int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &dec, number, status);
+ if (U_FAILURE(status)) { return appendTo; }
// Replace syntactic # signs in the top level of this sub-message
// (not in nested arguments) with the formatted number-offset.
const UnicodeString& pattern = msgPattern.getPatternString();
- number -= offset;
int32_t prevIndex = msgPattern.getPart(partIndex).getLimit();
for (;;) {
const MessagePattern::Part& part = msgPattern.getPart(++partIndex);
(type == UMSGPAT_PART_TYPE_SKIP_SYNTAX && MessageImpl::jdkAposMode(msgPattern))) {
appendTo.append(pattern, prevIndex, index - prevIndex);
if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
- numberFormat->format(number, appendTo);
+ appendTo.append(numberString);
}
prevIndex = part.getLimit();
} else if (type == UMSGPAT_PART_TYPE_ARG_START) {
}
int32_t PluralFormat::findSubMessage(const MessagePattern& pattern, int32_t partIndex,
- const PluralSelector& selector, double number, UErrorCode& ec) {
+ const PluralSelector& selector, void *context,
+ double number, UErrorCode& ec) {
if (U_FAILURE(ec)) {
return 0;
}
} else {
offset=0;
}
- // The keyword is empty until we need to match against non-explicit, not-"other" value.
+ // The keyword is empty until we need to match against a non-explicit, not-"other" value.
// Then we get the keyword from the selector.
// (In other words, we never call the selector if we match against an explicit value,
// or if the only non-explicit keyword is "other".)
}
} else {
if(keyword.isEmpty()) {
- keyword=selector.select(number-offset, ec);
+ keyword=selector.select(context, number-offset, ec);
if(msgStart!=0 && (0 == keyword.compare(other))) {
// We have already seen an "other" sub-message.
// Do not match "other" again.
delete pluralRules;
}
-UnicodeString PluralFormat::PluralSelectorAdapter::select(double number,
+UnicodeString PluralFormat::PluralSelectorAdapter::select(void *context, double number,
UErrorCode& /*ec*/) const {
- return pluralRules->select(number);
+ FixedDecimal *dec=static_cast<FixedDecimal *>(context);
+ U_ASSERT(dec->source==number);
+ return pluralRules->select(*dec);
}
void PluralFormat::PluralSelectorAdapter::reset() {
TESTCASE_AUTO(testGetFormatNames);
TESTCASE_AUTO(TestTrimArgumentName);
TESTCASE_AUTO(TestSelectOrdinal);
+ TESTCASE_AUTO(TestDecimals);
TESTCASE_AUTO_END;
}
errorCode.logDataIfFailureAndReset("");
}
+void TestMessageFormat::TestDecimals() {
+ IcuTestErrorCode errorCode(*this, "TestDecimals");
+ // Simple number replacement.
+ MessageFormat m(
+ "{0,plural,one{one meter}other{# meters}}",
+ Locale::getEnglish(), errorCode);
+ Formattable args[1] = { (int32_t)1 };
+ FieldPosition ignore;
+ UnicodeString result;
+ assertEquals("simple format(1)", "one meter",
+ m.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 1.5;
+ result.remove();
+ assertEquals("simple format(1.5)", "1.5 meters",
+ m.format(args, 1, result, ignore, errorCode));
+
+ // Simple but explicit.
+ MessageFormat m0(
+ "{0,plural,one{one meter}other{{0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = 1;
+ result.remove();
+ assertEquals("explicit format(1)", "one meter",
+ m0.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 1.5;
+ result.remove();
+ assertEquals("explicit format(1.5)", "1.5 meters",
+ m0.format(args, 1, result, ignore, errorCode));
+
+ // With offset and specific simple format with optional decimals.
+ MessageFormat m1(
+ "{0,plural,offset:1 one{another meter}other{{0,number,00.#} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = 1;
+ result.remove();
+ assertEquals("offset format(1)", "01 meters",
+ m1.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 2;
+ result.remove();
+ assertEquals("offset format(1)", "another meter",
+ m1.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 2.5;
+ result.remove();
+ assertEquals("offset format(1)", "02.5 meters",
+ m1.format(args, 1, result, ignore, errorCode));
+
+ // With offset and specific simple format with forced decimals.
+ MessageFormat m2(
+ "{0,plural,offset:1 one{another meter}other{{0,number,0.0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = 1;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "1.0 meters",
+ m2.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 2;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.0 meters",
+ m2.format(args, 1, result, ignore, errorCode));
+
+ args[0] = 2.5;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.5 meters",
+ m2.format(args, 1, result, ignore, errorCode));
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */