]> granicus.if.org Git - icu/commitdiff
ICU-10273 support plurals with decimals in MessageFormat and PluralFormat (ported...
authorMarkus Scherer <markus.icu@gmail.com>
Wed, 11 Sep 2013 23:32:37 +0000 (23:32 +0000)
committerMarkus Scherer <markus.icu@gmail.com>
Wed, 11 Sep 2013 23:32:37 +0000 (23:32 +0000)
X-SVN-Rev: 34277

icu4c/source/i18n/decimfmt.cpp
icu4c/source/i18n/msgfmt.cpp
icu4c/source/i18n/plurfmt.cpp
icu4c/source/i18n/unicode/msgfmt.h
icu4c/source/i18n/unicode/plurfmt.h
icu4c/source/test/intltest/plurfmts.cpp
icu4c/source/test/intltest/plurfmts.h
icu4c/source/test/intltest/tmsgfmt.cpp
icu4c/source/test/intltest/tmsgfmt.h

index a2e77e2ffa5a222861310e2194faf35546ab2cd7..e8ceb601cae528a94b1cc5ae1fdfc4114dea384e 100644 (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
index 874d93826b8c5ed793e53a37dbe8da8b8caf3699..f9b353fcbe114a6393b549bb40cd168d27d382cc 100644 (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;
 }
index 22f6939d43e08fdaecfb1bc7fea6d51f9caaa8ad..4acbff042421d720ce4d223ff06eb7e8249d31db 100644 (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 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() {
index 652605d9fcafe998bf55c8ad8133327c61819b72..534b676a455a8d6327f4a4c5ebe516703f82aa0f 100644 (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,
index e5f05d9c43eca60a24c12a4e924bc016e8f3c5dc..4ebb8a8701e1856c8991b418d08f3f2c1bb00b90 100644 (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;
 };
index 6934159247609617026177b7d07959f11e1367ff..c3bc5cebb952b222e32322be4412b4f64f8592a8 100644 (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);
-                
             }
         }
     }
index e16b0e13a3d18669e6850eb643dbd9d3b3ef9811..d5c8086fdac8ff6e9693932cb854bcbb2e45136b 100644 (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, 
index afba612304a02b6bdd900f1e733d048d7dc7669a..bcaabe378770749749a17c40a48095acc12fe131 100644 (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 */
index 63dbf77b07dc81f7153f290f4153940c705d241c..93d48fce7fb3e4e919aa272397acbf380730d014 100644 (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);