ICU-10273 support plurals with decimals in MessageFormat and PluralFormat (ported from Java r34087 & r34276)

X-SVN-Rev: 34277
This commit is contained in:
Markus Scherer 2013-09-11 23:32:37 +00:00
parent 56d546f8c5
commit fb38bbbee8
9 changed files with 380 additions and 101 deletions

View file

@ -1,7 +1,7 @@
/*
*******************************************************************************
* Copyright (C) 1997-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
* Copyright (C) 1997-2013, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
* File DECIMFMT.CPP
@ -1035,6 +1035,7 @@ DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const {
return result;
}
result.source = number;
int32_t minFractionDigits = getMinimumFractionDigits();
if (fMultiplier == NULL && fScale == 0 && fRoundingIncrement == 0 && areSignificantDigitsUsed() == FALSE &&
@ -1085,7 +1086,7 @@ DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) co
DigitList *digits = number.getDigitList();
if (digits == NULL || digits->getCount() <= 15) {
return getFixedDecimal(number.getDouble(), status);
return getFixedDecimal(number.getDouble(status), status);
}
// We have an incoming DigitList in the formattable, and it holds more digits than

View file

@ -39,6 +39,7 @@
#include "patternprops.h"
#include "messageimpl.h"
#include "msgfmt_impl.h"
#include "plurrule_impl.h"
#include "uassert.h"
#include "uelement.h"
#include "uhash.h"
@ -203,6 +204,16 @@ public:
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;
}
@ -229,8 +240,8 @@ MessageFormat::MessageFormat(const UnicodeString& pattern,
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);
@ -251,8 +262,8 @@ MessageFormat::MessageFormat(const UnicodeString& pattern,
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);
@ -274,8 +285,8 @@ MessageFormat::MessageFormat(const UnicodeString& pattern,
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);
@ -296,8 +307,8 @@ MessageFormat::MessageFormat(const MessageFormat& that)
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;
@ -440,8 +451,8 @@ MessageFormat::setLocale(const Locale& theLocale)
defaultDateFormat = NULL;
fLocale = theLocale;
setLocaleIDs(fLocale.getName(), fLocale.getName());
pluralProvider.reset(&fLocale);
ordinalProvider.reset(&fLocale);
pluralProvider.reset();
ordinalProvider.reset();
}
}
@ -831,12 +842,7 @@ MessageFormat::getFormats(int32_t& cnt) const
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*
@ -945,13 +951,55 @@ MessageFormat::format(const Formattable* arguments,
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,
@ -974,8 +1022,16 @@ void MessageFormat::format(int32_t msgStart, double pluralNumber,
}
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) {
@ -985,38 +1041,43 @@ void MessageFormat::format(int32_t msgStart, double pluralNumber,
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) ||
@ -1031,7 +1092,7 @@ void MessageFormat::format(int32_t msgStart, double pluralNumber,
(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);
}
@ -1060,26 +1121,26 @@ void MessageFormat::format(int32_t msgStart, double pluralNumber,
// 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.
@ -1094,7 +1155,7 @@ void MessageFormat::format(int32_t msgStart, double pluralNumber,
void MessageFormat::formatComplexSubMessage(int32_t msgStart,
double pluralNumber,
const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
@ -1105,7 +1166,7 @@ void MessageFormat::formatComplexSubMessage(int32_t msgStart,
}
if (!MessageImpl::jdkAposMode(msgPattern)) {
format(msgStart, pluralNumber, arguments, argumentNames, cnt, appendTo, NULL, success);
format(msgStart, plNumber, arguments, argumentNames, cnt, appendTo, NULL, success);
return;
}
@ -1127,8 +1188,15 @@ void MessageFormat::formatComplexSubMessage(int32_t msgStart,
} 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) {
@ -1144,7 +1212,7 @@ void MessageFormat::formatComplexSubMessage(int32_t msgStart,
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);
}
@ -1184,6 +1252,59 @@ FieldPosition* MessageFormat::updateMetaData(AppendableWrapper& /*dest*/, int32_
*/
}
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
@ -1797,32 +1918,55 @@ FormatNameEnumeration::~FormatNameEnumeration() {
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;
}

View file

@ -1,17 +1,14 @@
/*
*******************************************************************************
* 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"
@ -207,7 +204,7 @@ PluralFormat::format(const Formattable& obj,
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;
@ -218,14 +215,14 @@ UnicodeString
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);
}
@ -234,7 +231,7 @@ PluralFormat::format(int32_t number,
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const {
return format((double)number, appendTo, pos, status);
return format(Formattable(number), (double)number, appendTo, pos, status);
}
UnicodeString&
@ -242,18 +239,44 @@ PluralFormat::format(double number,
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);
@ -265,7 +288,7 @@ PluralFormat::format(double number,
(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) {
@ -370,7 +393,8 @@ PluralFormat::parseObject(const UnicodeString& /*source*/,
}
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;
}
@ -383,7 +407,7 @@ int32_t PluralFormat::findSubMessage(const MessagePattern& pattern, int32_t part
} 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".)
@ -436,7 +460,7 @@ int32_t PluralFormat::findSubMessage(const MessagePattern& pattern, int32_t part
}
} 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.
@ -463,9 +487,11 @@ PluralFormat::PluralSelectorAdapter::~PluralSelectorAdapter() {
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() {

View file

@ -874,13 +874,13 @@ private:
*/
class U_I18N_API PluralSelectorProvider : public PluralFormat::PluralSelector {
public:
PluralSelectorProvider(const Locale* loc, UPluralType type);
PluralSelectorProvider(const MessageFormat &mf, UPluralType type);
virtual ~PluralSelectorProvider();
virtual UnicodeString select(double number, UErrorCode& ec) const;
virtual UnicodeString select(void *ctx, double number, UErrorCode& ec) const;
void reset(const Locale* loc);
void reset();
private:
const Locale* locale;
const MessageFormat &msgFormat;
PluralRules* rules;
UPluralType type;
};
@ -956,7 +956,7 @@ private:
* AppendableWrapper, updates the field position.
*
* @param msgStart Index to msgPattern part to start formatting from.
* @param pluralNumber Zero except when formatting a plural argument sub-message
* @param plNumber NULL except when formatting a plural argument sub-message
* where a '#' is replaced by the format string for this number.
* @param arguments The formattable objects array. (Must not be NULL.)
* @param argumentNames NULL if numbered values are used. Otherwise the same
@ -969,7 +969,7 @@ private:
* @param success The error code status.
*/
void format(int32_t msgStart,
double pluralNumber,
const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
@ -1008,6 +1008,20 @@ private:
FieldPosition* updateMetaData(AppendableWrapper& dest, int32_t prevLength,
FieldPosition* fp, const Formattable* argId) const;
/**
* Finds the "other" sub-message.
* @param partIndex the index of the first PluralFormat argument style part.
* @return the "other" sub-message start part index.
*/
int32_t findOtherSubMessage(int32_t partIndex) const;
/**
* Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
* Returns -1 if it is a REPLACE_NUMBER.
* Returns 0 if there is neither.
*/
int32_t findFirstPluralNumberArg(int32_t msgStart, const UnicodeString &argName) const;
Format* getCachedFormatter(int32_t argumentNumber) const;
UnicodeString getLiteralStringUntilNextArgument(int32_t from) const;
@ -1015,7 +1029,7 @@ private:
void copyObjects(const MessageFormat& that, UErrorCode& ec);
void formatComplexSubMessage(int32_t msgStart,
double pluralNumber,
const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,

View file

@ -6,10 +6,6 @@
*
* File PLURFMT.H
*
* Modification History:*
* Date Name Description
*
********************************************************************************
*/
@ -543,11 +539,12 @@ private:
/**
* Given a number, returns the appropriate PluralFormat keyword.
*
* @param context worker object for the selector.
* @param number The number to be plural-formatted.
* @param ec Error code.
* @return The selected PluralFormat keyword.
*/
virtual UnicodeString select(double number, UErrorCode& ec) const = 0;
virtual UnicodeString select(void *context, double number, UErrorCode& ec) const = 0;
};
/**
@ -560,7 +557,7 @@ private:
virtual ~PluralSelectorAdapter();
virtual UnicodeString select(double number, UErrorCode& /*ec*/) const;
virtual UnicodeString select(void *context, double number, UErrorCode& /*ec*/) const;
void reset();
@ -585,11 +582,17 @@ private:
*/
void copyObjects(const PluralFormat& other);
UnicodeString& format(const Formattable& numberObject, double number,
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const;
/**
* Finds the PluralFormat sub-message for the given number, or the "other" sub-message.
* @param pattern A MessagePattern.
* @param partIndex the index of the first PluralFormat argument style part.
* @param selector the PluralSelector for mapping the number (minus offset) to a keyword.
* @param context worker object for the selector.
* @param number a number to be matched to one of the PluralFormat argument's explicit values,
* or mapped via the PluralSelector.
* @param ec ICU error code.
@ -597,7 +600,7 @@ private:
*/
static int32_t findSubMessage(
const MessagePattern& pattern, int32_t partIndex,
const PluralSelector& selector, double number, UErrorCode& ec);
const PluralSelector& selector, void *context, double number, UErrorCode& ec);
friend class MessageFormat;
};

View file

@ -8,12 +8,14 @@
#if !UCONFIG_NO_FORMATTING
#include "plurults.h"
#include "plurfmts.h"
#include "cmemory.h"
#include "unicode/dcfmtsym.h"
#include "unicode/decimfmt.h"
#include "unicode/msgfmt.h"
#include "unicode/plurrule.h"
#include "unicode/plurfmt.h"
#include "unicode/plurrule.h"
#include "cmemory.h"
#include "plurfmts.h"
#include "plurults.h"
#define PLURAL_PATTERN_DATA 4
#define PLURAL_TEST_ARRAY_SIZE 256
@ -38,6 +40,7 @@ void PluralFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(pluralFormatExtendedTest);
TESTCASE_AUTO(pluralFormatExtendedParseTest);
TESTCASE_AUTO(ordinalFormatTest);
TESTCASE_AUTO(TestDecimals);
TESTCASE_AUTO_END;
}
@ -567,7 +570,7 @@ PluralFormatTest::pluralFormatExtendedTest(void) {
dataerrln("Failed to apply pattern - %s", u_errorName(status));
return;
}
for (int32_t i = 0; i < 7; ++i) {
for (int32_t i = 0; i <= 7; ++i) {
UnicodeString result = pf.format(i, status);
if (U_FAILURE(status)) {
errln("PluralFormat.format(value %d) failed - %s", i, u_errorName(status));
@ -662,6 +665,22 @@ PluralFormatTest::ordinalFormatTest(void) {
}
}
void
PluralFormatTest::TestDecimals() {
IcuTestErrorCode errorCode(*this, "TestDecimals");
// Simple number replacement.
PluralFormat pf(Locale::getEnglish(), "one{one meter}other{# meters}", errorCode);
assertEquals("simple format(1)", "one meter", pf.format(1, errorCode));
assertEquals("simple format(1.5)", "1.5 meters", pf.format(1.5, errorCode));
PluralFormat pf2(Locale::getEnglish(),
"offset:1 one{another meter}other{another # meters}", errorCode);
DecimalFormat df("0.0", new DecimalFormatSymbols(Locale::getEnglish(), errorCode), errorCode);
pf2.setNumberFormat(&df, errorCode);
assertEquals("offset-decimals format(1)", "another 0.0 meters", pf2.format(1, errorCode));
assertEquals("offset-decimals format(2)", "another 1.0 meters", pf2.format(2, errorCode));
assertEquals("offset-decimals format(2.5)", "another 1.5 meters", pf2.format(2.5, errorCode));
}
void
PluralFormatTest::numberFormatTest(PluralFormat* plFmt,
NumberFormat *numFmt,
@ -707,7 +726,6 @@ PluralFormatTest::numberFormatTest(PluralFormat* plFmt,
}
else {
errln( *message+UnicodeString(" got:")+plResult+UnicodeString(" expecting:")+numResult);
}
}
}

View file

@ -1,6 +1,6 @@
/********************************************************************
* COPYRIGHT:
* Copyright (c) 1997-2012, International Business Machines Corporation and
* Copyright (c) 1997-2013, International Business Machines Corporation and
* others. All Rights Reserved.
********************************************************************/
@ -32,6 +32,7 @@ private:
void pluralFormatExtendedTest();
void pluralFormatExtendedParseTest();
void ordinalFormatTest();
void TestDecimals();
void numberFormatTest(PluralFormat* plFmt,
NumberFormat *numFmt,
int32_t start,

View file

@ -67,6 +67,7 @@ TestMessageFormat::runIndexedTest(int32_t index, UBool exec,
TESTCASE_AUTO(testGetFormatNames);
TESTCASE_AUTO(TestTrimArgumentName);
TESTCASE_AUTO(TestSelectOrdinal);
TESTCASE_AUTO(TestDecimals);
TESTCASE_AUTO_END;
}
@ -1892,4 +1893,74 @@ void TestMessageFormat::TestSelectOrdinal() {
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 */

View file

@ -1,6 +1,6 @@
/********************************************************************
* COPYRIGHT:
* Copyright (c) 1997-2012, International Business Machines Corporation and
* Copyright (c) 1997-2013, International Business Machines Corporation and
* others. All Rights Reserved.
********************************************************************/
#ifndef _TESTMESSAGEFORMAT
@ -117,6 +117,7 @@ public:
void testGetFormatNames();
void TestTrimArgumentName();
void TestSelectOrdinal();
void TestDecimals();
private:
UnicodeString GetPatternAndSkipSyntax(const MessagePattern& pattern);