From fb38bbbee80cdb99202f220ba3e9256140cdac3f Mon Sep 17 00:00:00 2001 From: Markus Scherer Date: Wed, 11 Sep 2013 23:32:37 +0000 Subject: [PATCH] ICU-10273 support plurals with decimals in MessageFormat and PluralFormat (ported from Java r34087 & r34276) X-SVN-Rev: 34277 --- icu4c/source/i18n/decimfmt.cpp | 7 +- icu4c/source/i18n/msgfmt.cpp | 260 ++++++++++++++++++------ icu4c/source/i18n/plurfmt.cpp | 62 ++++-- icu4c/source/i18n/unicode/msgfmt.h | 28 ++- icu4c/source/i18n/unicode/plurfmt.h | 17 +- icu4c/source/test/intltest/plurfmts.cpp | 30 ++- icu4c/source/test/intltest/plurfmts.h | 3 +- icu4c/source/test/intltest/tmsgfmt.cpp | 71 +++++++ icu4c/source/test/intltest/tmsgfmt.h | 3 +- 9 files changed, 380 insertions(+), 101 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index a2e77e2ffa5..e8ceb601cae 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -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 diff --git a/icu4c/source/i18n/msgfmt.cpp b/icu4c/source/i18n/msgfmt.cpp index 874d93826b8..f9b353fcbe1 100644 --- a/icu4c/source/i18n/msgfmt.cpp +++ b/icu4c/source/i18n/msgfmt.cpp @@ -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(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(plNumber)->numberArgIndex==(i-2)) { + const PluralSelectorContext &pluralNumber = + *static_cast(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(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(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(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(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(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; } diff --git a/icu4c/source/i18n/plurfmt.cpp b/icu4c/source/i18n/plurfmt.cpp index 22f6939d43e..4acbff04242 100644 --- a/icu4c/source/i18n/plurfmt.cpp +++ b/icu4c/source/i18n/plurfmt.cpp @@ -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(numberFormat); + if(decFmt != NULL) { + dec = decFmt->getFixedDecimal(numberObject, status); + } + } else { + numberFormat->format(numberMinusOffset, numberString, ignorePos, status); + DecimalFormat *decFmt = dynamic_cast(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(context); + U_ASSERT(dec->source==number); + return pluralRules->select(*dec); } void PluralFormat::PluralSelectorAdapter::reset() { diff --git a/icu4c/source/i18n/unicode/msgfmt.h b/icu4c/source/i18n/unicode/msgfmt.h index 652605d9fca..534b676a455 100644 --- a/icu4c/source/i18n/unicode/msgfmt.h +++ b/icu4c/source/i18n/unicode/msgfmt.h @@ -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, diff --git a/icu4c/source/i18n/unicode/plurfmt.h b/icu4c/source/i18n/unicode/plurfmt.h index e5f05d9c43e..4ebb8a8701e 100644 --- a/icu4c/source/i18n/unicode/plurfmt.h +++ b/icu4c/source/i18n/unicode/plurfmt.h @@ -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; }; diff --git a/icu4c/source/test/intltest/plurfmts.cpp b/icu4c/source/test/intltest/plurfmts.cpp index 69341592476..c3bc5cebb95 100644 --- a/icu4c/source/test/intltest/plurfmts.cpp +++ b/icu4c/source/test/intltest/plurfmts.cpp @@ -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); - } } } diff --git a/icu4c/source/test/intltest/plurfmts.h b/icu4c/source/test/intltest/plurfmts.h index e16b0e13a3d..d5c8086fdac 100644 --- a/icu4c/source/test/intltest/plurfmts.h +++ b/icu4c/source/test/intltest/plurfmts.h @@ -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, diff --git a/icu4c/source/test/intltest/tmsgfmt.cpp b/icu4c/source/test/intltest/tmsgfmt.cpp index afba612304a..bcaabe37877 100644 --- a/icu4c/source/test/intltest/tmsgfmt.cpp +++ b/icu4c/source/test/intltest/tmsgfmt.cpp @@ -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 */ diff --git a/icu4c/source/test/intltest/tmsgfmt.h b/icu4c/source/test/intltest/tmsgfmt.h index 63dbf77b07d..93d48fce7fb 100644 --- a/icu4c/source/test/intltest/tmsgfmt.h +++ b/icu4c/source/test/intltest/tmsgfmt.h @@ -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);