diff --git a/.gitattributes b/.gitattributes index fbf4c3bc87f..992432b7e1c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -139,6 +139,7 @@ icu4c/source/data/xml/rbnf/mt.xml -text icu4c/source/data/xml/rbnf/root.xml -text icu4c/source/extra/uconv/pkgdataMakefile.in -text icu4c/source/extra/uconv/samples/ibm-37-test.txt -text +icu4c/source/i18n/currpinf.cpp -text icu4c/source/i18n/dtitv_impl.h -text icu4c/source/i18n/dtitvfmt.cpp -text icu4c/source/i18n/dtitvinf.cpp -text @@ -146,6 +147,7 @@ icu4c/source/i18n/numsys.cpp -text icu4c/source/i18n/tmunit.cpp -text icu4c/source/i18n/tmutamt.cpp -text icu4c/source/i18n/tmutfmt.cpp -text +icu4c/source/i18n/unicode/currpinf.h -text icu4c/source/i18n/unicode/dtitvfmt.h -text icu4c/source/i18n/unicode/dtitvinf.h -text icu4c/source/i18n/unicode/numsys.h -text diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index bd056a4d7d5..61f79699f7a 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -81,7 +81,7 @@ ulocdata.o measfmt.o currfmt.o curramt.o currunit.o measure.o utmscale.o \ csdetect.o csmatch.o csr2022.o csrecog.o csrmbcs.o csrsbcs.o csrucode.o csrutf8.o inputext.o \ wintzimpl.o windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o \ zonemeta.o zstrfmt.o plurrule.o plurfmt.o dtitvfmt.o dtitvinf.o \ -tmunit.o tmutamt.o tmutfmt.o colldata.o bmsearch.o bms.o +tmunit.o tmutamt.o tmutfmt.o colldata.o bmsearch.o bms.o currpinf.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/i18n/currpinf.cpp b/icu4c/source/i18n/currpinf.cpp new file mode 100644 index 00000000000..5ae4c505468 --- /dev/null +++ b/icu4c/source/i18n/currpinf.cpp @@ -0,0 +1,344 @@ +/* + ******************************************************************************* + * Copyright (C) 2009, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +#include "unicode/currpinf.h" + +#if !UCONFIG_NO_FORMATTING + +//#define CURRENCY_PLURAL_INFO_DEBUG 1 + +#ifdef CURRENCY_PLURAL_INFO_DEBUG +#include +#endif + + +#include "unicode/locid.h" +#include "unicode/plurrule.h" +#include "unicode/ures.h" +#include "cstring.h" +#include "hash.h" +#include "uresimp.h" + +U_NAMESPACE_BEGIN + +U_CDECL_BEGIN + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2); + +U_CDECL_END + +UBool +U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2) { + const UnicodeString* affix_1 = (UnicodeString*)val1.pointer; + const UnicodeString* affix_2 = (UnicodeString*)val2.pointer; + return *affix_1 == *affix_2; +} + +//#define CURRPINF_DEBUG + +#ifdef CURRPINF_DEBUG +#include "stdio.h" +#endif + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CurrencyPluralInfo) + +static const UChar gDefaultCurrencyPluralPattern[] = {'0', '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4, 0}; +static const UChar gTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; +static const UChar gPluralCountOther[] = {0x6F, 0x74, 0x68, 0x65, 0x72, 0}; +static const UChar gPart0[] = {0x7B, 0x30, 0x7D, 0}; +static const UChar gPart1[] = {0x7B, 0x31, 0x7D, 0}; + +static const char gNumberPatternsTag[]="NumberPatterns"; +static const char gCurrUnitPtnTag[]="CurrencyUnitPatterns"; + +CurrencyPluralInfo::CurrencyPluralInfo(UErrorCode& status) +: fPluralCountToCurrencyUnitPattern(NULL), + fPluralRules(NULL), + fLocale(NULL) { + initialize(Locale::getDefault(), status); +} + +CurrencyPluralInfo::CurrencyPluralInfo(const Locale& locale, UErrorCode& status) +: fPluralCountToCurrencyUnitPattern(NULL), + fPluralRules(NULL), + fLocale(NULL) { + initialize(locale, status); +} + +CurrencyPluralInfo::CurrencyPluralInfo(const CurrencyPluralInfo& info) +: UObject(info), + fPluralCountToCurrencyUnitPattern(NULL), + fPluralRules(NULL), + fLocale(NULL) { + *this = info; +} + + +CurrencyPluralInfo& +CurrencyPluralInfo::operator=(const CurrencyPluralInfo& info) { + if (this == &info) { + return *this; + } + + deleteHash(fPluralCountToCurrencyUnitPattern); + UErrorCode status = U_ZERO_ERROR; + fPluralCountToCurrencyUnitPattern = initHash(status); + copyHash(info.fPluralCountToCurrencyUnitPattern, + fPluralCountToCurrencyUnitPattern, status); + if ( U_FAILURE(status) ) { + return *this; + } + + delete fPluralRules; + delete fLocale; + if (info.fPluralRules) { + fPluralRules = info.fPluralRules->clone(); + } else { + fPluralRules = NULL; + } + if (info.fLocale) { + fLocale = info.fLocale->clone(); + } else { + fLocale = NULL; + } + return *this; +} + + +CurrencyPluralInfo::~CurrencyPluralInfo() { + deleteHash(fPluralCountToCurrencyUnitPattern); + fPluralCountToCurrencyUnitPattern = NULL; + delete fPluralRules; + delete fLocale; + fPluralRules = NULL; + fLocale = NULL; +} + +UBool +CurrencyPluralInfo::operator==(const CurrencyPluralInfo& info) const { +#ifdef CURRENCY_PLURAL_INFO_DEBUG + if (*fPluralRules == *info.fPluralRules) { + std::cout << "same plural rules\n"; + } + if (*fLocale == *info.fLocale) { + std::cout << "same locale\n"; + } + if (fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern)) { + std::cout << "same pattern\n"; + } +#endif + return *fPluralRules == *info.fPluralRules && + *fLocale == *info.fLocale && + fPluralCountToCurrencyUnitPattern->equals(*info.fPluralCountToCurrencyUnitPattern); +} + + +CurrencyPluralInfo* +CurrencyPluralInfo::clone() const { + return new CurrencyPluralInfo(*this); +} + +const PluralRules* +CurrencyPluralInfo::getPluralRules() const { + return fPluralRules; +} + +UnicodeString& +CurrencyPluralInfo::getCurrencyPluralPattern(const UnicodeString& pluralCount, + UnicodeString& result) const { + const UnicodeString* currencyPluralPattern = + (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(pluralCount); + if (currencyPluralPattern == NULL) { + // fall back to "other" + if (pluralCount.compare(gPluralCountOther)) { + currencyPluralPattern = + (UnicodeString*)fPluralCountToCurrencyUnitPattern->get(gPluralCountOther); + } + if (currencyPluralPattern == NULL) { + // no currencyUnitPatterns defined, + // fallback to predefined defult. + // This should never happen when ICU resource files are + // available, since currencyUnitPattern of "other" is always + // defined in root. + result = UnicodeString(gDefaultCurrencyPluralPattern); + return result; + } + } + result = *currencyPluralPattern; + return result; +} + +const Locale& +CurrencyPluralInfo::getLocale() const { + return *fLocale; +} + +void +CurrencyPluralInfo::setPluralRules(const UnicodeString& ruleDescription, + UErrorCode& status) { + if (U_SUCCESS(status)) { + fPluralRules = PluralRules::createRules(ruleDescription, status); + } +} + + +void +CurrencyPluralInfo::setCurrencyPluralPattern(const UnicodeString& pluralCount, + const UnicodeString& pattern, + UErrorCode& status) { + if (U_SUCCESS(status)) { + fPluralCountToCurrencyUnitPattern->put(pluralCount, new UnicodeString(pattern), status); + } +} + + +void +CurrencyPluralInfo::setLocale(const Locale& loc, UErrorCode& status) { + initialize(loc, status); +} + + +void +CurrencyPluralInfo::initialize(const Locale& loc, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + delete fLocale; + fLocale = loc.clone(); + fPluralRules = PluralRules::forLocale(loc, status); + setupCurrencyPluralPattern(loc, status); +} + + +void +CurrencyPluralInfo::setupCurrencyPluralPattern(const Locale& loc, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + + fPluralCountToCurrencyUnitPattern = initHash(status); + if (U_FAILURE(status)) { + return; + } + + UErrorCode ec = U_ZERO_ERROR; + UResourceBundle *rb = ures_open(NULL, loc.getName(), &ec); + UResourceBundle *numberPatterns = ures_getByKey(rb, gNumberPatternsTag, NULL, &ec); + int32_t ptnLen; + // TODO: 0 to be NumberFormat::fNumberStyle + const UChar* numberStylePattern = ures_getStringByIndex(numberPatterns, 0, + &ptnLen, &ec); + ures_close(numberPatterns); + + if (U_FAILURE(ec)) { + ures_close(rb); + return; + } + + UResourceBundle *currencyRes = ures_getByKeyWithFallback(rb, gCurrUnitPtnTag, NULL, &ec); + + StringEnumeration* keywords = fPluralRules->getKeywords(ec); + if (U_SUCCESS(ec)) { + const char* pluralCount; + while ((pluralCount = keywords->next(NULL, ec)) != NULL) { + if ( U_SUCCESS(ec) ) { + int32_t ptnLen; + UErrorCode err = U_ZERO_ERROR; + const UChar* patternChars = ures_getStringByKeyWithFallback( + currencyRes, pluralCount, &ptnLen, &err); + if (U_SUCCESS(err) && ptnLen > 0) { + UnicodeString* pattern = new UnicodeString(patternChars, ptnLen); +#ifdef CURRPINF_DEBUG + char result_1[1000]; + pattern->extract(0, pattern->length(), result_1, "UTF-8"); + printf("pluralCount: %s; pattern: %s\n", pluralCount, result_1); +#endif + pattern->findAndReplace(gPart0, numberStylePattern); + pattern->findAndReplace(gPart1, gTripleCurrencySign); + +#ifdef CURRPINF_DEBUG + pattern->extract(0, pattern->length(), result_1, "UTF-8"); + printf("pluralCount: %s; pattern: %s\n", pluralCount, result_1); +#endif + + fPluralCountToCurrencyUnitPattern->put(UnicodeString(pluralCount), pattern, status); + } + } + } + } + delete keywords; + ures_close(currencyRes); + ures_close(rb); +} + + + +void +CurrencyPluralInfo::deleteHash(Hashtable* hTable) +{ + if ( hTable == NULL ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + while ( (element = hTable->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UHashTok valueTok = element->value; + const UnicodeString* value = (UnicodeString*)valueTok.pointer; + delete value; + } + delete hTable; + hTable = NULL; +} + + +Hashtable* +CurrencyPluralInfo::initHash(UErrorCode& status) { + if ( U_FAILURE(status) ) { + return NULL; + } + Hashtable* hTable; + if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + hTable->setValueCompartor(ValueComparator); + return hTable; +} + + +void +CurrencyPluralInfo::copyHash(const Hashtable* source, + Hashtable* target, + UErrorCode& status) { + if ( U_FAILURE(status) ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + if ( source ) { + while ( (element = source->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UnicodeString* key = (UnicodeString*)keyTok.pointer; + const UHashTok valueTok = element->value; + const UnicodeString* value = (UnicodeString*)valueTok.pointer; + UnicodeString* copy = new UnicodeString(*value); + target->put(UnicodeString(*key), copy, status); + if ( U_FAILURE(status) ) { + return; + } + } + } +} + + +U_NAMESPACE_END + +#endif diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c79e8ea2554..5bcfbf1f125 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -49,6 +49,8 @@ #include "unicode/ures.h" #include "unicode/uchar.h" #include "unicode/curramt.h" +#include "unicode/currpinf.h" +#include "unicode/plurrule.h" #include "ucurrimp.h" #include "util.h" #include "digitlst.h" @@ -58,9 +60,56 @@ #include "uassert.h" #include "putilimp.h" #include +#include "hash.h" + U_NAMESPACE_BEGIN +U_CDECL_BEGIN + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV AffixValueComparator(UHashTok val1, UHashTok val2); + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV AffixPatternValueComparator(UHashTok val1, UHashTok val2); + +U_CDECL_END + + +UBool +U_CALLCONV AffixValueComparator(UHashTok val1, UHashTok val2) { + const DecimalFormat::AffixesForCurrency* affix_1 = + (DecimalFormat::AffixesForCurrency*)val1.pointer; + const DecimalFormat::AffixesForCurrency* affix_2 = + (DecimalFormat::AffixesForCurrency*)val2.pointer; + return affix_1->negPrefixForCurrency == affix_2->negPrefixForCurrency && + affix_1->negSuffixForCurrency == affix_2->negSuffixForCurrency && + affix_1->posPrefixForCurrency == affix_2->posPrefixForCurrency && + affix_1->posSuffixForCurrency == affix_2->posSuffixForCurrency; +} + + +UBool +U_CALLCONV AffixPatternValueComparator(UHashTok val1, UHashTok val2) { + const DecimalFormat::AffixPatternsForCurrency* affix_1 = + (DecimalFormat::AffixPatternsForCurrency*)val1.pointer; + const DecimalFormat::AffixPatternsForCurrency* affix_2 = + (DecimalFormat::AffixPatternsForCurrency*)val2.pointer; + return affix_1->negPrefixPatternForCurrency == + affix_2->negPrefixPatternForCurrency && + affix_1->negSuffixPatternForCurrency == + affix_2->negSuffixPatternForCurrency && + affix_1->posPrefixPatternForCurrency == + affix_2->posPrefixPatternForCurrency && + affix_1->posSuffixPatternForCurrency == + affix_2->posSuffixPatternForCurrency; +} + + //#define FMT_DEBUG #ifdef FMT_DEBUG @@ -116,6 +165,7 @@ const int32_t DecimalFormat::kMaxScientificIntegerDigits = 8; * with a locale. */ const char DecimalFormat::fgNumberPatterns[]="NumberPatterns"; +static const UChar fgTripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; inline int32_t _min(int32_t a, int32_t b) { return (agetLocale(), status); + if (U_FAILURE(status)) { + return; + } + + // the pattern used in format is not fixed until formatting, + // in which, the number is known and + // will be used to pick the right pattern based on plural count. + // Here, set the pattern as the pattern of plural count == "other". + // For most locale, the patterns are probably the same for all + // plural count. If not, the right pattern need to be re-applied + // during format. + fCurrencyPluralInfo->getCurrencyPluralPattern("other", currencyPluralPatternForOther); + patternUsed = ¤cyPluralPatternForOther; + // TODO: not needed? + setCurrencyForSymbols(); + + } else { + patternUsed = pattern; + } + + if (patternUsed->indexOf(kCurrencySign) != -1) { + // initialize for currency, not only for plural format, + // but also for mix parsing + if (fCurrencyPluralInfo == NULL) { + fCurrencyPluralInfo = new CurrencyPluralInfo(fSymbols->getLocale(), status); + if (U_FAILURE(status)) { + return; + } + } + // need it for mix parsing + setupCurrencyAffixPatterns(status); + // expanded affixes for plural names + if (patternUsed->indexOf(fgTripleCurrencySign) != -1) { + setupCurrencyAffixes(*patternUsed, TRUE, TRUE, status); + } + } + + applyPatternWithoutExpandAffix(*patternUsed,FALSE, parseErr, status); + + // expand affixes + if (fCurrencySignCount != fgCurrencySignCountInPluralFormat) { + expandAffixAdjustWidth(NULL); } - applyPattern(*pattern, FALSE /*not localized*/,parseErr, status); - // If it was a currency format, apply the appropriate rounding by // resetting the currency. NOTE: this copies fCurrency on top of itself. - if (fIsCurrencyFormat) { - setCurrency(getCurrency(), status); + if (fCurrencySignCount > fgCurrencySignCountZero) { + setCurrencyInternally(getCurrency(), status); } } + +void +DecimalFormat::setupCurrencyAffixPatterns(UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + UParseError parseErr; + fAffixPatternsForCurrency = initHashForAffixPattern(status); + if (U_FAILURE(status)) { + return; + } + + // Save the default currency patterns of this locale. + // Here, chose onlyApplyPatternWithoutExpandAffix without + // expanding the affix patterns into affixes. + UnicodeString currencyPattern; + UErrorCode error = U_ZERO_ERROR; + UResourceBundle *resource = ures_open((char *)0, fSymbols->getLocale().getName(), &error); + UResourceBundle *numberPatterns = ures_getByKey(resource, fgNumberPatterns, NULL, &error); + int32_t patLen = 0; + const UChar *patResStr = ures_getStringByIndex(numberPatterns, kCurrencyStyle, &patLen, &error); + ures_close(numberPatterns); + ures_close(resource); + + if (U_SUCCESS(error)) { + applyPatternWithoutExpandAffix(UnicodeString(patResStr, patLen), false, + parseErr, status); + AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( + *fNegPrefixPattern, + *fNegSuffixPattern, + *fPosPrefixPattern, + *fPosSuffixPattern); + fAffixPatternsForCurrency->put("default", affixPtn, status); + } + + // save the unique currency plural patterns of this locale. + Hashtable* pluralPtn = fCurrencyPluralInfo->fPluralCountToCurrencyUnitPattern; + const UHashElement* element = NULL; + int32_t pos = -1; + Hashtable pluralPatternSet; + while ((element = pluralPtn->nextElement(pos)) != NULL) { + const UHashTok valueTok = element->value; + const UnicodeString* value = (UnicodeString*)valueTok.pointer; + const UHashTok keyTok = element->key; + const UnicodeString* key = (UnicodeString*)keyTok.pointer; + if (pluralPatternSet.geti(*value) != 1) { + pluralPatternSet.puti(*value, 1, status); + applyPatternWithoutExpandAffix(*value, false, parseErr, status); + AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( + *fNegPrefixPattern, + *fNegSuffixPattern, + *fPosPrefixPattern, + *fPosSuffixPattern); + fAffixPatternsForCurrency->put(*key, affixPtn, status); + } + } +} + + +void +DecimalFormat::setupCurrencyAffixes(const UnicodeString& pattern, + UBool setupForCurrentPattern, + UBool setupForPluralPattern, + UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + UParseError parseErr; + if (setupForCurrentPattern) { + if (fAffixesForCurrency) { + deleteHashForAffix(fAffixesForCurrency); + } + fAffixesForCurrency = initHashForAffix(status); + if (U_SUCCESS(status)) { + applyPatternWithoutExpandAffix(pattern, false, parseErr, status); + const PluralRules* pluralRules = fCurrencyPluralInfo->getPluralRules(); + StringEnumeration* keywords = pluralRules->getKeywords(status); + if (U_SUCCESS(status)) { + const char* pluralCountCh; + while ((pluralCountCh = keywords->next(NULL, status)) != NULL) { + if ( U_SUCCESS(status) ) { + UnicodeString pluralCount = UnicodeString(pluralCountCh); + expandAffixAdjustWidth(&pluralCount); + AffixesForCurrency* affix = new AffixesForCurrency( + fNegativePrefix, fNegativeSuffix, fPositivePrefix, fPositiveSuffix); + fAffixesForCurrency->put(pluralCount, affix, status); + } + } + } + delete keywords; + } + } + + if (U_FAILURE(status)) { + return; + } + + if (setupForPluralPattern) { + if (fPluralAffixesForCurrency) { + deleteHashForAffix(fPluralAffixesForCurrency); + } + fPluralAffixesForCurrency = initHashForAffix(status); + if (U_SUCCESS(status)) { + const PluralRules* pluralRules = fCurrencyPluralInfo->getPluralRules(); + StringEnumeration* keywords = pluralRules->getKeywords(status); + if (U_SUCCESS(status)) { + const char* pluralCountCh; + while ((pluralCountCh = keywords->next(NULL, status)) != NULL) { + if ( U_SUCCESS(status) ) { + UnicodeString pluralCount = UnicodeString(pluralCountCh); + UnicodeString ptn; + fCurrencyPluralInfo->getCurrencyPluralPattern(pluralCount, ptn); + applyPatternInternally(pluralCount, ptn, false, parseErr, status); + AffixesForCurrency* affix = new AffixesForCurrency( + fNegativePrefix, fNegativeSuffix, fPositivePrefix, fPositiveSuffix); + fPluralAffixesForCurrency->put(pluralCount, affix, status); + } + } + } + delete keywords; + } + } +} + + //------------------------------------------------------------------------------ DecimalFormat::~DecimalFormat() @@ -352,6 +640,10 @@ DecimalFormat::~DecimalFormat() delete fCurrencyChoice; delete fSymbols; delete fRoundingIncrement; + deleteHashForAffixPattern(); + deleteHashForAffix(fAffixesForCurrency); + deleteHashForAffix(fPluralAffixesForCurrency); + delete fCurrencyPluralInfo; } //------------------------------------------------------------------------------ @@ -366,7 +658,13 @@ DecimalFormat::DecimalFormat(const DecimalFormat &source) fNegSuffixPattern(NULL), fCurrencyChoice(NULL), fSymbols(NULL), - fRoundingIncrement(NULL) + fRoundingIncrement(NULL), + fStyle(0), + fCurrencySignCount(0), + fAffixPatternsForCurrency(NULL), + fAffixesForCurrency(NULL), + fPluralAffixesForCurrency(NULL), + fCurrencyPluralInfo(NULL) { *this = source; } @@ -430,7 +728,7 @@ DecimalFormat::operator=(const DecimalFormat& rhs) fUseExponentialNotation = rhs.fUseExponentialNotation; fExponentSignAlwaysShown = rhs.fExponentSignAlwaysShown; /*Bertrand A. D. Update 98.03.17*/ - fIsCurrencyFormat = rhs.fIsCurrencyFormat; + fCurrencySignCount = rhs.fCurrencySignCount; /*end of Update*/ fMinExponentDigits = rhs.fMinExponentDigits; // if (fDigitList == NULL) @@ -444,6 +742,32 @@ DecimalFormat::operator=(const DecimalFormat& rhs) fMinSignificantDigits = rhs.fMinSignificantDigits; fMaxSignificantDigits = rhs.fMaxSignificantDigits; fUseSignificantDigits = rhs.fUseSignificantDigits; + fFormatPattern = rhs.fFormatPattern; + fStyle = rhs.fStyle; + fCurrencySignCount = rhs.fCurrencySignCount; + if (rhs.fCurrencyPluralInfo) { + delete fCurrencyPluralInfo; + fCurrencyPluralInfo = rhs.fCurrencyPluralInfo->clone(); + } + if (rhs.fAffixPatternsForCurrency) { + UErrorCode status = U_ZERO_ERROR; + deleteHashForAffixPattern(); + fAffixPatternsForCurrency = initHashForAffixPattern(status); + copyHashForAffixPattern(rhs.fAffixPatternsForCurrency, + fAffixPatternsForCurrency, status); + } + if (rhs.fAffixesForCurrency) { + UErrorCode status = U_ZERO_ERROR; + deleteHashForAffix(fAffixesForCurrency); + fAffixesForCurrency = initHashForAffixPattern(status); + copyHashForAffix(rhs.fAffixesForCurrency, fAffixesForCurrency, status); + } + if (rhs.fPluralAffixesForCurrency) { + UErrorCode status = U_ZERO_ERROR; + deleteHashForAffix(fPluralAffixesForCurrency); + fPluralAffixesForCurrency = initHashForAffixPattern(status); + copyHashForAffix(rhs.fPluralAffixesForCurrency, fPluralAffixesForCurrency, status); + } } return *this; } @@ -466,7 +790,7 @@ DecimalFormat::operator==(const Format& that) const if (!NumberFormat::operator==(that)) { if (first) { printf("[ "); first = FALSE; } else { printf(", "); } debug("NumberFormat::!="); - } + } else { if (!((fPosPrefixPattern == other->fPosPrefixPattern && // both null fPositivePrefix == other->fPositivePrefix) || (fPosPrefixPattern != 0 && other->fPosPrefixPattern != 0 && @@ -562,11 +886,46 @@ DecimalFormat::operator==(const Format& that) const debug("Symbols !="); } // TODO Add debug stuff for significant digits here + if (fUseSignificantDigits != other->fUseSignificantDigits) { + debug("fUseSignificantDigits !="); + } + if (fUseSignificantDigits && + fMinSignificantDigits != other->fMinSignificantDigits) { + debug("fMinSignificantDigits !="); + } + if (fUseSignificantDigits && + fMaxSignificantDigits != other->fMaxSignificantDigits) { + debug("fMaxSignificantDigits !="); + } + if (!first) { printf(" ]"); } + if (fCurrencySignCount != other->fCurrencySignCount) { + debug("fCurrencySignCount !="); + } + if (fCurrencyPluralInfo == other->fCurrencyPluralInfo) { + debug("fCurrencyPluralInfo == "); + if (fCurrencyPluralInfo == NULL) { + debug("fCurrencyPluralInfo == NULL"); + } + } + if (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo != NULL && + *fCurrencyPluralInfo != *(other->fCurrencyPluralInfo)) { + debug("fCurrencyPluralInfo !="); + } + if (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo == NULL || + fCurrencyPluralInfo == NULL && other->fCurrencyPluralInfo != NULL) { + debug("fCurrencyPluralInfo one NULL, the other not"); + } + if (fCurrencyPluralInfo == NULL && other->fCurrencyPluralInfo == NULL) { + debug("fCurrencyPluralInfo == "); + } + } #endif return (NumberFormat::operator==(that) && - ((fPosPrefixPattern == other->fPosPrefixPattern && // both null + ((fCurrencySignCount == fgCurrencySignCountInPluralFormat) ? + (fAffixPatternsForCurrency->equals(*other->fAffixPatternsForCurrency)) : + (((fPosPrefixPattern == other->fPosPrefixPattern && // both null fPositivePrefix == other->fPositivePrefix) || (fPosPrefixPattern != 0 && other->fPosPrefixPattern != 0 && *fPosPrefixPattern == *other->fPosPrefixPattern)) && @@ -581,7 +940,7 @@ DecimalFormat::operator==(const Format& that) const ((fNegSuffixPattern == other->fNegSuffixPattern && // both null fNegativeSuffix == other->fNegativeSuffix) || (fNegSuffixPattern != 0 && other->fNegSuffixPattern != 0 && - *fNegSuffixPattern == *other->fNegSuffixPattern)) && + *fNegSuffixPattern == *other->fNegSuffixPattern)))) && ((fRoundingIncrement == other->fRoundingIncrement) // both null || (fRoundingIncrement != NULL && other->fRoundingIncrement != NULL && @@ -597,7 +956,12 @@ DecimalFormat::operator==(const Format& that) const fUseSignificantDigits == other->fUseSignificantDigits && (!fUseSignificantDigits || (fMinSignificantDigits == other->fMinSignificantDigits && - fMaxSignificantDigits == other->fMaxSignificantDigits))); + fMaxSignificantDigits == other->fMaxSignificantDigits)) && + fCurrencySignCount == other->fCurrencySignCount && + ((fCurrencyPluralInfo == other->fCurrencyPluralInfo && + fCurrencyPluralInfo == NULL) || + (fCurrencyPluralInfo != NULL && other->fCurrencyPluralInfo != NULL && + *fCurrencyPluralInfo == *(other->fCurrencyPluralInfo)))); } //------------------------------------------------------------------------------ @@ -813,6 +1177,7 @@ UBool DecimalFormat::isGroupingPosition(int32_t pos) const { //------------------------------------------------------------------------------ + /** * Complete the formatting of a finite number. On entry, the fDigitList must * be filled in with the correct digits. @@ -827,13 +1192,13 @@ DecimalFormat::subformat(UnicodeString& appendTo, UChar32 zero = getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); int32_t zeroDelta = zero - '0'; // '0' is the DigitList representation of zero const UnicodeString *grouping ; - if(fIsCurrencyFormat) { + if(fCurrencySignCount > fgCurrencySignCountZero) { grouping = &getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); }else{ grouping = &getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); } const UnicodeString *decimal; - if(fIsCurrencyFormat) { + if(fCurrencySignCount > fgCurrencySignCountZero) { decimal = &getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); } else { decimal = &getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); @@ -1252,9 +1617,20 @@ void DecimalFormat::parse(const UnicodeString& text, UChar* currency = parseCurrency ? curbuf : NULL; DigitList digits; - if (!subparse(text, parsePosition, digits, status, currency)) { - parsePosition.setIndex(backup); - return; + if (fCurrencySignCount > fgCurrencySignCountZero) { + if (!parseForCurrency(text, parsePosition, digits, + status, currency)) { + return; + } + } else { + if (!subparse(text, + fNegPrefixPattern, fNegSuffixPattern, + fPosPrefixPattern, fPosSuffixPattern, + FALSE, + parsePosition, digits, status, currency)) { + parsePosition.setIndex(backup); + return; + } } // Handle infinity @@ -1313,6 +1689,116 @@ void DecimalFormat::parse(const UnicodeString& text, } + +UBool +DecimalFormat::parseForCurrency(const UnicodeString& text, + ParsePosition& parsePosition, + DigitList& digits, + UBool* status, + UChar* currency) const { + int origPos = parsePosition.getIndex(); + int maxPosIndex = origPos; + int maxErrorPos = -1; + // First, parse against current pattern. + // Since current pattern could be set by applyPattern(), + // it could be an arbitrary pattern, and it may not be the one + // defined in current locale. + UBool tmpStatus[fgStatusLength]; + ParsePosition tmpPos(origPos); + DigitList tmpDigitList; + UBool found = subparse(text, + fNegPrefixPattern, fNegSuffixPattern, + fPosPrefixPattern, fPosSuffixPattern, + TRUE, + tmpPos, tmpDigitList, tmpStatus, currency); + if (found) { + if (tmpPos.getIndex() > maxPosIndex) { + maxPosIndex = tmpPos.getIndex(); + for (int32_t i = 0; i < fgStatusLength; ++i) { + status[i] = tmpStatus[i]; + } + digits = tmpDigitList; + } + } else { + maxErrorPos = tmpPos.getErrorIndex(); + } + // Then, parse against affix patterns. + // Those are currency patterns and currency plural patterns. + int32_t pos = -1; + const UHashElement* element = NULL; + while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UHashTok valueTok = element->value; + const AffixPatternsForCurrency* affixPtn = (AffixPatternsForCurrency*)valueTok.pointer; + UBool tmpStatus[fgStatusLength]; + ParsePosition tmpPos(origPos); + DigitList tmpDigitList; + UBool result = subparse(text, + &affixPtn->negPrefixPatternForCurrency, + &affixPtn->negSuffixPatternForCurrency, + &affixPtn->posPrefixPatternForCurrency, + &affixPtn->posSuffixPatternForCurrency, + TRUE, + tmpPos, tmpDigitList, tmpStatus, currency); + if (result) { + found = true; + if (tmpPos.getIndex() > maxPosIndex) { + maxPosIndex = tmpPos.getIndex(); + for (int32_t i = 0; i < fgStatusLength; ++i) { + status[i] = tmpStatus[i]; + } + digits = tmpDigitList; + } + } else { + maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? + tmpPos.getErrorIndex() : maxErrorPos; + } + } + // Finally, parse against simple affix to find the match. + // For example, in TestMonster suite, + // if the to-be-parsed text is "-\u00A40,00". + // complexAffixCompare will not find match, + // since there is no ISO code matches "\u00A4", + // and the parse stops at "\u00A4". + // We will just use simple affix comparison (look for exact match) + // to pass it. + UBool tmpStatus_2[fgStatusLength]; + ParsePosition tmpPos_2(origPos); + DigitList tmpDigitList_2; + // set currencySignCount to 0 so that compareAffix function will + // fall to compareSimpleAffix path, not compareComplexAffix path. + // ?? TODO: is it right? need "false"? + UBool result = subparse(text, + &fNegativePrefix, &fNegativeSuffix, + &fPositivePrefix, &fPositiveSuffix, + FALSE, + tmpPos_2, tmpDigitList_2, tmpStatus_2, + currency); + if (result) { + if (tmpPos_2.getIndex() > maxPosIndex) { + maxPosIndex = tmpPos_2.getIndex(); + for (int32_t i = 0; i < fgStatusLength; ++i) { + status[i] = tmpStatus_2[i]; + } + digits = tmpDigitList_2; + } + found = true; + } else { + maxErrorPos = (tmpPos_2.getErrorIndex() > maxErrorPos) ? + tmpPos_2.getErrorIndex() : maxErrorPos; + } + + if (!found) { + //parsePosition.setIndex(origPos); + parsePosition.setErrorIndex(maxErrorPos); + } else { + parsePosition.setIndex(maxPosIndex); + parsePosition.setErrorIndex(-1); + } + return found; +} + + /* This is an old implimentation that was preparing for 64-bit numbers in ICU. It is very slow, and 64-bit numbers are not ANSI-C compatible. This code @@ -1325,6 +1811,11 @@ is here if we change our minds. * Parse the given text into a number. The text is parsed beginning at * parsePosition, until an unparseable character is seen. * @param text the string to parse. + * @param negPrefix negative prefix. + * @param negSuffix negative suffix. + * @param posPrefix positive prefix. + * @param posSuffix positive suffix. + * @param currencyParsing whether it is currency parsing or not. * @param parsePosition The position at which to being parsing. Upon * return, the first unparsed character. * @param digits the DigitList to set to the parsed value. @@ -1335,7 +1826,13 @@ is here if we change our minds. * currency parsing mode, any currency is parsed, not just the * currency that this formatter is set to. */ -UBool DecimalFormat::subparse(const UnicodeString& text, ParsePosition& parsePosition, +UBool DecimalFormat::subparse(const UnicodeString& text, + const UnicodeString* negPrefix, + const UnicodeString* negSuffix, + const UnicodeString* posPrefix, + const UnicodeString* posSuffix, + UBool currencyParsing, + ParsePosition& parsePosition, DigitList& digits, UBool* status, UChar* currency) const { @@ -1348,8 +1845,8 @@ UBool DecimalFormat::subparse(const UnicodeString& text, ParsePosition& parsePos } // Match positive and negative prefixes; prefer longest match. - int32_t posMatch = compareAffix(text, position, FALSE, TRUE, currency); - int32_t negMatch = compareAffix(text, position, TRUE, TRUE, currency); + int32_t posMatch = compareAffix(text, position, FALSE, TRUE, posPrefix, currencyParsing, currency); + int32_t negMatch = compareAffix(text, position, TRUE, TRUE, negPrefix,currencyParsing, currency); if (posMatch >= 0 && negMatch >= 0) { if (posMatch > negMatch) { negMatch = -1; @@ -1390,7 +1887,7 @@ UBool DecimalFormat::subparse(const UnicodeString& text, ParsePosition& parsePos UChar32 zero = getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); const UnicodeString *decimal; - if(fIsCurrencyFormat) { + if(fCurrencySignCount > fgCurrencySignCountZero) { decimal = &getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); } else { decimal = &getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); @@ -1564,10 +2061,10 @@ UBool DecimalFormat::subparse(const UnicodeString& text, ParsePosition& parsePos // Match positive and negative suffixes; prefer longest match. if (posMatch >= 0) { - posMatch = compareAffix(text, position, FALSE, FALSE, currency); + posMatch = compareAffix(text, position, FALSE, FALSE, posSuffix, currencyParsing, currency); } if (negMatch >= 0) { - negMatch = compareAffix(text, position, TRUE, FALSE, currency); + negMatch = compareAffix(text, position, TRUE, FALSE, negSuffix, currencyParsing, currency); } if (posMatch >= 0 && negMatch >= 0) { if (posMatch > negMatch) { @@ -1625,6 +2122,8 @@ int32_t DecimalFormat::skipPadding(const UnicodeString& text, int32_t position) * @param pos offset into input at which to begin matching * @param isNegative * @param isPrefix + * @param affixPat affix pattern used for currency affix comparison. + * @param currencyParsing whether it is currency parsing or not * @param currency return value for parsed currency, for generic * currency parsing mode, or null for normal parsing. In generic * currency parsing mode, any currency is parsed, not just the @@ -1635,30 +2134,17 @@ int32_t DecimalFormat::compareAffix(const UnicodeString& text, int32_t pos, UBool isNegative, UBool isPrefix, + const UnicodeString* affixPat, + UBool currencyParsing, UChar* currency) const { const UnicodeString *patternToCompare; - if (fCurrencyChoice != NULL || currency != NULL) { - if (isNegative) { - if (isPrefix) { - patternToCompare = fNegPrefixPattern; - } - else { - patternToCompare = fNegSuffixPattern; - } + if (fCurrencyChoice != NULL || currency != NULL || + (fCurrencySignCount > fgCurrencySignCountZero && currencyParsing)) { + + if (affixPat != NULL) { + return compareComplexAffix(*affixPat, text, pos, currency); } - else { - if (isPrefix) { - patternToCompare = fPosPrefixPattern; - } - else { - patternToCompare = fPosSuffixPattern; - } - } - if (patternToCompare != NULL) { - return compareComplexAffix(*patternToCompare, text, pos, currency); - } - /* else the caller modified the pattern. Fallback to normal behavior. */ } if (isNegative) { @@ -1797,9 +2283,11 @@ int32_t DecimalFormat::compareComplexAffix(const UnicodeString& affixPat, { int32_t start = pos; U_ASSERT(currency != NULL || - (fCurrencyChoice != NULL && *getCurrency() != 0)); + (fCurrencyChoice != NULL && *getCurrency() != 0) || + fCurrencySignCount > fgCurrencySignCountZero); - for (int32_t i=0; i= 0; ) { + for (int32_t i=0; + i= 0 && pos < text.length(); ) { UChar32 c = affixPat.char32At(i); i += U16_LENGTH(c); @@ -1812,48 +2300,41 @@ int32_t DecimalFormat::compareComplexAffix(const UnicodeString& affixPat, switch (c) { case kCurrencySign: { - // If currency != null, then perform generic currency matching. - // Otherwise, do currency choice parsing. + // since the currency names in choice format is saved + // the same way as other currency names, + // do not need to do currency choice parsing here. + // the general currency parsing parse against all names, + // including names in choice format. UBool intl = igetLocale().getName(); - ec = U_ZERO_ERROR; - } - // Delegate parse of display name => ISO code to Currency - ParsePosition ppos(pos); - UChar curr[4]; - uprv_parseCurrency(loc, text, ppos, curr, ec); + // Try to parse display name for our locale; first + // determine our locale. + const char* loc = fCurrencyPluralInfo->getLocale().getName(); + ParsePosition ppos(pos); + UChar curr[4]; + UErrorCode ec = U_ZERO_ERROR; + // Delegate parse of display name => ISO code to Currency + uprv_parseCurrency(loc, text, ppos, curr, ec); - // If parse succeeds, populate currency[0] - if (U_SUCCESS(ec) && ppos.getIndex() != pos) { + // If parse succeeds, populate currency[0] + if (U_SUCCESS(ec) && ppos.getIndex() != pos) { + if (currency) { u_strcpy(currency, curr); - pos = ppos.getIndex(); - } else { - pos = -1; } + pos = ppos.getIndex(); } else { - if (intl) { - ++i; - pos = match(text, pos, getCurrency()); - } else { - ParsePosition ppos(pos); - Formattable result; - fCurrencyChoice->parse(text, result, ppos); - pos = (ppos.getIndex() == pos) ? -1 : ppos.getIndex(); - } + pos = -1; } continue; } @@ -1958,7 +2439,7 @@ DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) // If the currency symbols are the same, there is no need to recalculate. setCurrencyForSymbols(); } - expandAffixes(); + expandAffixes(NULL); } //------------------------------------------------------------------------------ // Setting the symbols is equlivalent to adopting a newly created localized @@ -1970,6 +2451,42 @@ DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) adoptDecimalFormatSymbols(new DecimalFormatSymbols(symbols)); } + +const CurrencyPluralInfo* +DecimalFormat::getCurrencyPluralInfo(void) const +{ + return fCurrencyPluralInfo; +} + + +void +DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) +{ + if (toAdopt != NULL) { + delete fCurrencyPluralInfo; + fCurrencyPluralInfo = toAdopt; + // re-set currency affix patterns and currency affixes. + if (fCurrencySignCount > fgCurrencySignCountZero) { + UErrorCode status = U_ZERO_ERROR; + if (fAffixPatternsForCurrency) { + deleteHashForAffixPattern(); + } + setupCurrencyAffixPatterns(status); + if (fCurrencySignCount == fgCurrencySignCountInPluralFormat) { + // only setup the affixes of the plural pattern. + setupCurrencyAffixes(fFormatPattern, FALSE, TRUE, status); + } + } + } +} + +void +DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) +{ + adoptCurrencyPluralInfo(info.clone()); +} + + /** * Update the currency object to match the symbols. This method * is used only when the caller has passed in a symbols object @@ -2006,7 +2523,7 @@ DecimalFormat::setCurrencyForSymbols() { c = intlCurrencySymbol; } ec = U_ZERO_ERROR; // reset local error code! - setCurrency(c, ec); + setCurrencyInternally(c, ec); } @@ -2430,19 +2947,26 @@ DecimalFormat::toLocalizedPattern(UnicodeString& result) const * affix pattern string is null, do not expand it. This method should be * called any time the symbols or the affix patterns change in order to keep * the expanded affix strings up to date. + * This method also will be called before formatting if format currency + * plural names, since the plural name is not a static one, it is + * based on the currency plural count, the affix will be known only + * after the currency plural count is know. + * In which case, the parameter + * 'pluralCount' will be a non-null currency plural count. + * In all other cases, the 'pluralCount' is null, which means it is not needed. */ -void DecimalFormat::expandAffixes() { +void DecimalFormat::expandAffixes(const UnicodeString* pluralCount) { if (fPosPrefixPattern != 0) { - expandAffix(*fPosPrefixPattern, fPositivePrefix, 0, FALSE); + expandAffix(*fPosPrefixPattern, fPositivePrefix, 0, FALSE, pluralCount); } if (fPosSuffixPattern != 0) { - expandAffix(*fPosSuffixPattern, fPositiveSuffix, 0, FALSE); + expandAffix(*fPosSuffixPattern, fPositiveSuffix, 0, FALSE, pluralCount); } if (fNegPrefixPattern != 0) { - expandAffix(*fNegPrefixPattern, fNegativePrefix, 0, FALSE); + expandAffix(*fNegPrefixPattern, fNegativePrefix, 0, FALSE, pluralCount); } if (fNegSuffixPattern != 0) { - expandAffix(*fNegSuffixPattern, fNegativeSuffix, 0, FALSE); + expandAffix(*fNegSuffixPattern, fNegativeSuffix, 0, FALSE, pluralCount); } #ifdef FMT_DEBUG UnicodeString s; @@ -2463,7 +2987,9 @@ void DecimalFormat::expandAffixes() { * after kQuote are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, * PATTERN_MINUS, and kCurrencySign. If kCurrencySign is doubled (kQuote + * kCurrencySign + kCurrencySign), it is interpreted as an international - * currency sign. Any other character after a kQuote represents itself. + * currency sign. If CURRENCY_SIGN is tripled, it is interpreted as + * currency plural long names, such as "US Dollars". + * Any other character after a kQuote represents itself. * kQuote must be followed by another character; kQuote may not occur by * itself at the end of the pattern. * @@ -2487,11 +3013,18 @@ void DecimalFormat::expandAffixes() { * currencyChoice member variable will be initialized if it is null. If * doFormat is true, then it is assumed that the currencyChoice has been * created, and it will be used to format the value in digitList. + * @param pluralCount the plural count. It is only used for currency + * plural format. In which case, it is the plural + * count of the currency amount. For example, + * in en_US, it is the singular "one", or the plural + * "other". For all other cases, it is null, and + * is not being used. */ void DecimalFormat::expandAffix(const UnicodeString& pattern, UnicodeString& affix, double number, - UBool doFormat) const { + UBool doFormat, + const UnicodeString* pluralCount) const { affix.remove(); for (int i=0; ilength() >= 10) { + break; + } + pluralCount->extract(0, pluralCount->length(), pluralCountChar); + UBool isChoiceFormat; + const UChar* s = ucurr_getPluralName(currencyUChars, + fSymbols != NULL ? fSymbols->getLocale().getName() : + Locale::getDefault().getName(), &isChoiceFormat, + pluralCountChar, &len, &ec); + affix += UnicodeString(s, len); + } else if(intl) { affix += currencyUChars; } else { int32_t len; @@ -2611,7 +3169,9 @@ void DecimalFormat::expandAffix(const UnicodeString& pattern, */ int32_t DecimalFormat::appendAffix(UnicodeString& buf, double number, UBool isNegative, UBool isPrefix) const { - if (fCurrencyChoice != 0) { + // plural format precedes choice format + if (fCurrencyChoice != 0 && + fCurrencySignCount != fgCurrencySignCountInPluralFormat) { const UnicodeString* affixPat; if (isPrefix) { affixPat = isNegative ? fNegPrefixPattern : fPosPrefixPattern; @@ -2620,7 +3180,7 @@ int32_t DecimalFormat::appendAffix(UnicodeString& buf, double number, } if (affixPat) { UnicodeString affixBuf; - expandAffix(*affixPat, affixBuf, number, TRUE); + expandAffix(*affixPat, affixBuf, number, TRUE, NULL); buf.append(affixBuf); return affixBuf.length(); } @@ -2628,10 +3188,27 @@ int32_t DecimalFormat::appendAffix(UnicodeString& buf, double number, } const UnicodeString* affix; - if (isPrefix) { - affix = isNegative ? &fNegativePrefix : &fPositivePrefix; + if (fCurrencySignCount == fgCurrencySignCountInPluralFormat) { + UnicodeString pluralCount = fCurrencyPluralInfo->getPluralRules()->select(number); + AffixesForCurrency* oneSet; + if (fStyle == NumberFormat::kPluralCurrencyStyle) { + oneSet = (AffixesForCurrency*)fPluralAffixesForCurrency->get(pluralCount); + } else { + oneSet = (AffixesForCurrency*)fAffixesForCurrency->get(pluralCount); + } + if (isPrefix) { + affix = isNegative ? &oneSet->negPrefixForCurrency : + &oneSet->posPrefixForCurrency; + } else { + affix = isNegative ? &oneSet->negSuffixForCurrency : + &oneSet->posSuffixForCurrency; + } } else { - affix = isNegative ? &fNegativeSuffix : &fPositiveSuffix; + if (isPrefix) { + affix = isNegative ? &fNegativePrefix : &fPositivePrefix; + } else { + affix = isNegative ? &fNegativeSuffix : &fPositiveSuffix; + } } buf.append(*affix); return affix->length(); @@ -2764,6 +3341,16 @@ DecimalFormat::appendAffixPattern(UnicodeString& appendTo, UnicodeString& DecimalFormat::toPattern(UnicodeString& result, UBool localized) const { + if (fStyle == NumberFormat::kPluralCurrencyStyle) { + // the prefix or suffix pattern might not be defined yet, + // so they can not be synthesized, + // instead, get them directly. + // but it might not be the actual pattern used in formatting. + // the actual pattern used in formatting depends on the + // formatted number's plural count. + result = fFormatPattern; + return result; + } result.remove(); UChar32 zero, sigDigit = kPatternSignificantDigit; UnicodeString digit, group; @@ -3024,10 +3611,10 @@ DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, //------------------------------------------------------------------------------ void -DecimalFormat::applyPattern(const UnicodeString& pattern, - UBool localized, - UParseError& parseError, - UErrorCode& status) +DecimalFormat::applyPatternWithoutExpandAffix(const UnicodeString& pattern, + UBool localized, + UParseError& parseError, + UErrorCode& status) { if (U_FAILURE(status)) { @@ -3101,7 +3688,6 @@ DecimalFormat::applyPattern(const UnicodeString& pattern, DigitList roundingInc; int8_t expDigits = -1; UBool expSignAlways = FALSE; - UBool isCurrency = FALSE; // The affix is either the prefix or the suffix. UnicodeString* affix = &prefix; @@ -3266,8 +3852,17 @@ DecimalFormat::applyPattern(const UnicodeString& pattern, if ((pos+1) < pattern.length() && pattern[pos+1] == kCurrencySign) { affix->append(kCurrencySign); ++pos; // Skip over the doubled character + if ((pos+1) < pattern.length() && + pattern[pos+1] == kCurrencySign) { + affix->append(kCurrencySign); + ++pos; // Skip over the doubled character + fCurrencySignCount = fgCurrencySignCountInPluralFormat; + } else { + fCurrencySignCount = fgCurrencySignCountInISOFormat; + } + } else { + fCurrencySignCount = fgCurrencySignCountInSymbolFormat; } - isCurrency = TRUE; // Fall through to append(ch) } else if (ch == kQuote) { // A quote outside quotes indicates either the opening @@ -3468,7 +4063,6 @@ DecimalFormat::applyPattern(const UnicodeString& pattern, fMinExponentDigits = expDigits; } fExponentSignAlwaysShown = expSignAlways; - fIsCurrencyFormat = isCurrency; int32_t digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; // The effectiveDecimalPos is the position the decimal is at or // would be at if there is no decimal. Note that if @@ -3579,7 +4173,7 @@ DecimalFormat::applyPattern(const UnicodeString& pattern, setMaximumFractionDigits(kDoubleFractionDigits); fUseExponentialNotation = FALSE; - fIsCurrencyFormat = FALSE; + fCurrencySignCount = 0; setGroupingUsed(FALSE); fGroupingSize = 0; fGroupingSize2 = 0; @@ -3614,13 +4208,61 @@ DecimalFormat::applyPattern(const UnicodeString& pattern, s.append("\"").append(pattern).append("\"->"); debugout(s); #endif - expandAffixes(); + + // save the pattern + fFormatPattern = pattern; +} + + +void +DecimalFormat::expandAffixAdjustWidth(const UnicodeString* pluralCount) { + expandAffixes(pluralCount); if (fFormatWidth > 0) { // Finish computing format width (see above) - fFormatWidth += fPositivePrefix.length() + fPositiveSuffix.length(); + // TODO: how to handle fFormatWidth, + // need to save in f(Plural)AffixesForCurrecy? + fFormatWidth += fPositivePrefix.length() + fPositiveSuffix.length(); } } + +void +DecimalFormat::applyPattern(const UnicodeString& pattern, + UBool localized, + UParseError& parseError, + UErrorCode& status) +{ + // do the following re-set first. since they change private data by + // apply pattern again. + if (pattern.indexOf(kCurrencySign) != -1) { + if (fCurrencyPluralInfo == NULL) { + // initialize currencyPluralInfo if needed + fCurrencyPluralInfo = new CurrencyPluralInfo(fSymbols->getLocale(), status); + } + if (fAffixPatternsForCurrency == NULL) { + setupCurrencyAffixPatterns(status); + } + if (pattern.indexOf(fgTripleCurrencySign) != -1) { + // only setup the affixes of the current pattern. + setupCurrencyAffixes(pattern, TRUE, FALSE, status); + } + } + applyPatternWithoutExpandAffix(pattern, localized, parseError, status); + expandAffixAdjustWidth(NULL); +} + + +void +DecimalFormat::applyPatternInternally(const UnicodeString& pluralCount, + const UnicodeString& pattern, + UBool localized, + UParseError& parseError, + UErrorCode& status) { + applyPatternWithoutExpandAffix(pattern, localized, parseError, status); + expandAffixAdjustWidth(&pluralCount); +} + + /** * Sets the maximum number of digits allowed in the integer portion of a * number. This override limits the integer digit count to 309. @@ -3694,7 +4336,8 @@ void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { fUseSignificantDigits = useSignificantDigits; } -void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { +void DecimalFormat::setCurrencyInternally(const UChar* theCurrency, + UErrorCode& ec) { // If we are a currency format, then modify our affixes to // encode the currency symbol for the given currency in our // locale, and adjust the decimal digits and rounding for the @@ -3708,7 +4351,7 @@ void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { double rounding = 0.0; int32_t frac = 0; - if (fIsCurrencyFormat && isCurr) { + if (fCurrencySignCount > fgCurrencySignCountZero && isCurr) { rounding = ucurr_getRoundingIncrement(theCurrency, &ec); frac = ucurr_getDefaultFractionDigits(theCurrency, &ec); } @@ -3716,17 +4359,30 @@ void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { NumberFormat::setCurrency(theCurrency, ec); if (U_FAILURE(ec)) return; - if (fIsCurrencyFormat) { + if (fCurrencySignCount > fgCurrencySignCountZero) { // NULL or empty currency is *legal* and indicates no currency. if (isCurr) { setRoundingIncrement(rounding); setMinimumFractionDigits(frac); setMaximumFractionDigits(frac); } - expandAffixes(); + expandAffixes(NULL); } } +void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { + // set the currency before compute affixes to get the right currency names + NumberFormat::setCurrency(theCurrency, ec); + if (fFormatPattern.indexOf(fgTripleCurrencySign) != -1) { + UnicodeString savedPtn = fFormatPattern; + setupCurrencyAffixes(fFormatPattern, TRUE, TRUE, ec); + UParseError parseErr; + applyPattern(savedPtn, FALSE, parseErr, ec); + } + // set the currency after apply pattern to get the correct rounding/fraction + setCurrencyInternally(theCurrency, ec); +} + // Deprecated variant with no UErrorCode parameter void DecimalFormat::setCurrency(const UChar* theCurrency) { UErrorCode ec = U_ZERO_ERROR; @@ -3765,6 +4421,135 @@ DecimalFormat::precision(UBool isIntegral) const { } } + +// TODO: template algorithm +Hashtable* +DecimalFormat::initHashForAffix(UErrorCode& status) { + if ( U_FAILURE(status) ) { + return NULL; + } + Hashtable* hTable; + if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + hTable->setValueCompartor(AffixValueComparator); + return hTable; +} + +Hashtable* +DecimalFormat::initHashForAffixPattern(UErrorCode& status) { + if ( U_FAILURE(status) ) { + return NULL; + } + Hashtable* hTable; + if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + hTable->setValueCompartor(AffixPatternValueComparator); + return hTable; +} + +void +DecimalFormat::deleteHashForAffix(Hashtable*& table) +{ + if ( table == NULL ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + while ( (element = table->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UHashTok valueTok = element->value; + const AffixesForCurrency* value = (AffixesForCurrency*)valueTok.pointer; + delete value; + } + delete table; + table = NULL; +} + + + +void +DecimalFormat::deleteHashForAffixPattern() +{ + if ( fAffixPatternsForCurrency == NULL ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UHashTok valueTok = element->value; + const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; + delete value; + } + delete fAffixPatternsForCurrency; + fAffixPatternsForCurrency = NULL; +} + + +void +DecimalFormat::copyHashForAffixPattern(const Hashtable* source, + Hashtable* target, + UErrorCode& status) { + if ( U_FAILURE(status) ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + if ( source ) { + while ( (element = source->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UnicodeString* key = (UnicodeString*)keyTok.pointer; + const UHashTok valueTok = element->value; + const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; + AffixPatternsForCurrency* copy = new AffixPatternsForCurrency( + value->negPrefixPatternForCurrency, + value->negSuffixPatternForCurrency, + value->posPrefixPatternForCurrency, + value->posSuffixPatternForCurrency); + target->put(UnicodeString(*key), copy, status); + if ( U_FAILURE(status) ) { + return; + } + } + } +} + + + +void +DecimalFormat::copyHashForAffix(const Hashtable* source, + Hashtable* target, + UErrorCode& status) { + if ( U_FAILURE(status) ) { + return; + } + int32_t pos = -1; + const UHashElement* element = NULL; + if ( source ) { + while ( (element = source->nextElement(pos)) != NULL ) { + const UHashTok keyTok = element->key; + const UnicodeString* key = (UnicodeString*)keyTok.pointer; + + const UHashTok valueTok = element->value; + const AffixesForCurrency* value = (AffixesForCurrency*)valueTok.pointer; + AffixesForCurrency* copy = new AffixesForCurrency( + value->negPrefixForCurrency, + value->negSuffixForCurrency, + value->posPrefixForCurrency, + value->posSuffixForCurrency); + target->put(UnicodeString(*key), copy, status); + if ( U_FAILURE(status) ) { + return; + } + } + } +} + + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/i18n.vcproj b/icu4c/source/i18n/i18n.vcproj index 73a86f4484c..4ad6b93dc44 100644 --- a/icu4c/source/i18n/i18n.vcproj +++ b/icu4c/source/i18n/i18n.vcproj @@ -1222,6 +1222,14 @@ RelativePath=".\currfmt.h" > + + + + diff --git a/icu4c/source/i18n/numfmt.cpp b/icu4c/source/i18n/numfmt.cpp index c3f025f6304..d46e2ef0871 100644 --- a/icu4c/source/i18n/numfmt.cpp +++ b/icu4c/source/i18n/numfmt.cpp @@ -74,6 +74,15 @@ static const UChar gLastResortPercentPat[] = { static const UChar gLastResortScientificPat[] = { 0x23, 0x45, 0x30, 0 /* "#E0" */ }; +static const UChar gLastResortIsoCurrencyPat[] = { + 0xA4, 0xA4, 0x23, 0x30, 0x2E, 0x30, 0x30, 0x3B, 0x28, 0xA4, 0xA4, 0x23, 0x30, 0x2E, 0x30, 0x30, 0x29, 0 /* "\u00A4\u00A4#0.00;(\u00A4\u00A4#0.00)" */ +}; +static const UChar gLastResortPluralCurrencyPat[] = { + 0x23, 0x30, 0x2E, 0x30, 0x30, 0xA0, 0xA4, 0xA4, 0xA4, 0 /* "#0.00\u00A0\u00A4\u00A4\u00A4*/ +}; + +static const UChar gSingleCurrencySign[] = {0xA4, 0}; +static const UChar gDoubleCurrencySign[] = {0xA4, 0xA4, 0}; // If the maximum base 10 exponent were 4, then the largest number would // be 99,999 which has 5 digits. @@ -86,7 +95,9 @@ static const UChar * const gLastResortNumberPatterns[] = gLastResortDecimalPat, gLastResortCurrencyPat, gLastResortPercentPat, - gLastResortScientificPat + gLastResortScientificPat, + gLastResortIsoCurrencyPat, + gLastResortPluralCurrencyPat, }; // ***************************************************************************** @@ -843,6 +854,8 @@ NumberFormat::makeInstance(const Locale& desiredLocale, // fall-through case kCurrencyStyle: + case kIsoCurrencyStyle: // do not support plural formatting here + case kPluralCurrencyStyle: f = new Win32NumberFormat(desiredLocale, curr, status); if (U_SUCCESS(status)) { @@ -877,7 +890,7 @@ NumberFormat::makeInstance(const Locale& desiredLocale, else { // If not all the styled patterns exists for the NumberFormat in this locale, // sets the status code to failure and returns nil. - if (ures_getSize(numberPatterns) < (int32_t)(sizeof(gLastResortNumberPatterns)/sizeof(gLastResortNumberPatterns[0]))) { + if (ures_getSize(numberPatterns) < (int32_t)(sizeof(gLastResortNumberPatterns)/sizeof(gLastResortNumberPatterns[0])) -2 ) { //minus 2: ISO and plural status = U_INVALID_FORMAT_ERROR; goto cleanup; } @@ -886,14 +899,25 @@ NumberFormat::makeInstance(const Locale& desiredLocale, symbolsToAdopt = new DecimalFormatSymbols(desiredLocale, status); int32_t patLen = 0; - const UChar *patResStr = ures_getStringByIndex(numberPatterns, (int32_t)style, &patLen, &status); + + /* for ISOCURRENCYSTYLE and PLURALCURRENCYSTYLE, + * the pattern is the same as the pattern of CURRENCYSTYLE + * but by replacing the single currency sign with + * double currency sign or triple currency sign. + */ + int styleInNumberPattern = ((style == kIsoCurrencyStyle || + style == kPluralCurrencyStyle) ? + kCurrencyStyle : style); + + const UChar *patResStr = ures_getStringByIndex(numberPatterns, (int32_t)styleInNumberPattern, &patLen, &status); + // Creates the specified decimal format style of the desired locale. pattern.setTo(TRUE, patResStr, patLen); } if (U_FAILURE(status) || symbolsToAdopt == NULL) { goto cleanup; } - if(style==kCurrencyStyle){ + if(style==kCurrencyStyle || style == kIsoCurrencyStyle){ const UChar* currPattern = symbolsToAdopt->getCurrencyPattern(); if(currPattern!=NULL){ pattern.setTo(currPattern, u_strlen(currPattern)); @@ -914,7 +938,16 @@ NumberFormat::makeInstance(const Locale& desiredLocale, r->setDefaultRuleSet(ns->getDescription(),status); f = (NumberFormat *) r; } else { - f = new DecimalFormat(pattern, symbolsToAdopt, status); + // replace single currency sign in the pattern with double currency sign + // if the style is kIsoCurrencyStyle + if (style == kIsoCurrencyStyle) { + pattern.findAndReplace(gSingleCurrencySign, gDoubleCurrencySign); + } + + f = new DecimalFormat(pattern, symbolsToAdopt, style, status); + if (U_FAILURE(status) || f == NULL) { + goto cleanup; + } } f->setLocaleIDs(ures_getLocaleByType(numberPatterns, ULOC_VALID_LOCALE, &status), diff --git a/icu4c/source/i18n/ucurr.cpp b/icu4c/source/i18n/ucurr.cpp index 7705a5bd8d2..eefc943e5be 100644 --- a/icu4c/source/i18n/ucurr.cpp +++ b/icu4c/source/i18n/ucurr.cpp @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2002-2008, International Business Machines +* Copyright (c) 2002-2009, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** */ @@ -74,6 +74,7 @@ static const char VAR_DELIM_STR[] = "_"; // Tag for localized display names (symbols) of currencies static const char CURRENCIES[] = "Currencies"; +static const char CURRENCYPLURALS[] = "CurrencyPlurals"; // Marker character indicating that a display name is a ChoiceFormat // pattern. Strings that start with one mark are ChoiceFormat @@ -544,6 +545,82 @@ ucurr_getName(const UChar* currency, return currency; } +U_CAPI const UChar* U_EXPORT2 +ucurr_getPluralName(const UChar* currency, + const char* locale, + UBool* isChoiceFormat, + const char* pluralCount, + int32_t* len, // fillin + UErrorCode* ec) { + // Look up the Currencies resource for the given locale. The + // Currencies locale data looks like this: + //|en { + //| CurrencyPlurals { + //| USD{ + //| one{"US dollar"} + //| other{"US dollars"} + //| } + //| } + //|} + + if (U_FAILURE(*ec)) { + return 0; + } + + // Use a separate UErrorCode here that does not propagate out of + // this function. + UErrorCode ec2 = U_ZERO_ERROR; + + char loc[ULOC_FULLNAME_CAPACITY]; + uloc_getName(locale, loc, sizeof(loc), &ec2); + if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + + char buf[ISO_COUNTRY_CODE_LENGTH+1]; + myUCharsToChars(buf, currency); + + const UChar* s = NULL; + ec2 = U_ZERO_ERROR; + UResourceBundle* rb = ures_open(NULL, loc, &ec2); + + rb = ures_getByKey(rb, CURRENCYPLURALS, rb, &ec2); + + // Fetch resource with multi-level resource inheritance fallback + rb = ures_getByKeyWithFallback(rb, buf, rb, &ec2); + + s = ures_getStringByKeyWithFallback(rb, pluralCount, len, &ec2); + if (U_FAILURE(ec2)) { + // fall back to "other" + ec2 = U_ZERO_ERROR; + s = ures_getStringByKeyWithFallback(rb, "other", len, &ec2); + if (U_FAILURE(ec2)) { + ures_close(rb); + // fall back to long name in Currencies + return ucurr_getName(currency, locale, UCURR_LONG_NAME, + isChoiceFormat, len, ec); + } + } + ures_close(rb); + + // If we've succeeded we're done. Otherwise, try to fallback. + // If that fails (because we are already at root) then exit. + if (U_SUCCESS(ec2)) { + if (ec2 == U_USING_DEFAULT_WARNING + || (ec2 == U_USING_FALLBACK_WARNING && *ec != U_USING_DEFAULT_WARNING)) { + *ec = ec2; + } + U_ASSERT(s != NULL); + return s; + } + + // If we fail to find a match, use the ISO 4217 code + *len = u_strlen(currency); // Should == ISO_COUNTRY_CODE_LENGTH, but maybe not...? + *ec = U_USING_DEFAULT_WARNING; + return currency; +} + U_CFUNC void uprv_parseCurrency(const char* locale, const U_NAMESPACE_QUALIFIER UnicodeString& text, @@ -634,8 +711,39 @@ uprv_parseCurrency(const char* locale, iso = ures_getKey(names); max = len; } + + // TODO: TextTrie + s = ures_getStringByIndex(names, UCURR_LONG_NAME, &len, &ec2); + if (len > max && text.compare(pos.getIndex(), len, s) == 0) { + iso = ures_getKey(names); + max = len; + } + + if (3 > max && text.compare(pos.getIndex(), 3, ures_getKey(names)) == 0) { + iso = ures_getKey(names); + max = 3; + } ures_close(names); } + + // try currency plurals + UErrorCode ec3 = U_ZERO_ERROR; + UResourceBundle* curr_p = ures_getByKey(rb, CURRENCYPLURALS, NULL, &ec3); + n = ures_getSize(curr_p); + for (int32_t i=0; i max && text.compare(pos.getIndex(), len, s) == 0) { + iso = ures_getKey(names); + max = len; + } + } + ures_close(names); + } + ures_close(curr_p); ures_close(curr); ures_close(rb); @@ -1136,9 +1244,9 @@ ucurr_countCurrencies(const char* locale, UErrorCode localStatus = U_ZERO_ERROR; char id[ULOC_FULLNAME_CAPACITY]; resLen = uloc_getKeywordValue(locale, "currency", id, ULOC_FULLNAME_CAPACITY, &localStatus); - // get country or country_variant in `id' uint32_t variantType = idForLocale(locale, id, sizeof(id), ec); + if (U_FAILURE(*ec)) { return 0; @@ -1253,8 +1361,8 @@ ucurr_forLocaleAndDate(const char* locale, char id[ULOC_FULLNAME_CAPACITY]; resLen = uloc_getKeywordValue(locale, "currency", id, ULOC_FULLNAME_CAPACITY, &localStatus); - // get country or country_variant in `id' - uint32_t variantType = idForLocale(locale, id, sizeof(id), ec); + // get country or country_variant in `id' + uint32_t variantType = idForLocale(locale, id, sizeof(id), ec); if (U_FAILURE(*ec)) { return 0; diff --git a/icu4c/source/i18n/unicode/currpinf.h b/icu4c/source/i18n/unicode/currpinf.h new file mode 100644 index 00000000000..6a166eab631 --- /dev/null +++ b/icu4c/source/i18n/unicode/currpinf.h @@ -0,0 +1,269 @@ +/* + ******************************************************************************* + * Copyright (C) 2009, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +#ifndef CURRPINF_H +#define CURRPINF_H + +#include "unicode/utypes.h" + +/** + * \file + * \brief C++ API: Currency Plural Information used by Decimal Format + */ + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/unistr.h" + +union UHashTok; + +U_NAMESPACE_BEGIN + +U_CDECL_BEGIN + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV ValueComparator(UHashTok val1, UHashTok val2) ; + +U_CDECL_END + + +class Locale; +class PluralRules; +class Hashtable; + +/** + * This class represents the information needed by + * DecimalFormat to format currency plural, + * such as "3.00 US dollars" or "1.00 US dollar". + * DecimalFormat creates for itself an instance of + * CurrencyPluralInfo from its locale data. + * If you need to change any of these symbols, you can get the + * CurrencyPluralInfo object from your + * DecimalFormat and modify it. + * + * Following are the information needed for currency plural format and parse: + * locale information, + * plural rule of the locale, + * currency plural pattern of the locale. + * + * @draft ICU 4.2 + */ +class U_I18N_API CurrencyPluralInfo : public UObject { +public: + + /** + * Create a CurrencyPluralInfo object for the default locale. + * @param status output param set to success/failure code on exit + * @draft ICU 4.2 + */ + CurrencyPluralInfo(UErrorCode& status); + + /** + * Create a CurrencyPluralInfo object for the given locale. + * @param locale the locale + * @param status output param set to success/failure code on exit + * @draft ICU 4.2 + */ + CurrencyPluralInfo(const Locale& locale, UErrorCode& status); + + /** + * Copy constructor + * + * @draft ICU 4.2 + */ + CurrencyPluralInfo(const CurrencyPluralInfo& info); + + + /** + * Assignment operator + * + * @draft ICU 4.2 + */ + CurrencyPluralInfo& operator=(const CurrencyPluralInfo& info); + + + /** + * Destructor + * + * @draft ICU 4.2 + */ + virtual ~CurrencyPluralInfo(); + + + /** + * Equal operator. + * + * @draft ICU 4.2 + */ + UBool operator==(const CurrencyPluralInfo& info) const; + + + /** + * Not equal operator + * + * @draft ICU 4.2 + */ + UBool operator!=(const CurrencyPluralInfo& info) const; + + + /** + * Clone + * + * @draft ICU 4.2 + */ + CurrencyPluralInfo* clone() const; + + + /** + * Gets plural rules of this locale, used for currency plural format + * + * @return plural rule + * @draft ICU 4.2 + */ + const PluralRules* getPluralRules() const; + + /** + * Given a plural count, gets currency plural pattern of this locale, + * used for currency plural format + * + * @param pluralCount currency plural count + * @param result output param to receive the pattern + * @return a currency plural pattern based on plural count + * @draft ICU 4.2 + */ + UnicodeString& getCurrencyPluralPattern(const UnicodeString& pluralCount, + UnicodeString& result) const; + + /** + * Get locale + * + * @return locale + * @draft ICU 4.2 + */ + const Locale& getLocale() const; + + /** + * Set plural rules. + * The plural rule is set when CurrencyPluralInfo + * instance is created. + * You can call this method to reset plural rules only if you want + * to modify the default plural rule of the locale. + * + * @param ruleDescription new plural rule description + * @param status output param set to success/failure code on exit + * @draft ICU 4.2 + */ + void setPluralRules(const UnicodeString& ruleDescription, + UErrorCode& status); + + /** + * Set currency plural pattern. + * The currency plural pattern is set when CurrencyPluralInfo + * instance is created. + * You can call this method to reset currency plural pattern only if + * you want to modify the default currency plural pattern of the locale. + * + * @param pluralCount the plural count for which the currency pattern will + * be overridden. + * @param pattern the new currency plural pattern + * @param status output param set to success/failure code on exit + * @draft ICU 4.2 + */ + void setCurrencyPluralPattern(const UnicodeString& pluralCount, + const UnicodeString& pattern, + UErrorCode& status); + + /** + * Set locale + * + * @param loc the new locale to set + * @param status output param set to success/failure code on exit + * @draft ICU 4.2 + */ + void setLocale(const Locale& loc, UErrorCode& status); + + /** + * ICU "poor man's RTTI", returns a UClassID for the actual class. + * + * @stable ICU 2.2 + */ + virtual UClassID getDynamicClassID() const; + + /** + * ICU "poor man's RTTI", returns a UClassID for this class. + * + * @stable ICU 2.2 + */ + static UClassID U_EXPORT2 getStaticClassID(); + +private: + friend class DecimalFormat; + + void initialize(const Locale& loc, UErrorCode& status); + + void setupCurrencyPluralPattern(const Locale& loc, UErrorCode& status); + + /* + * delete hash table + * + * @param hTable hash table to be deleted + */ + void deleteHash(Hashtable* hTable); + + + /* + * initialize hash table + * + * @param status output param set to success/failure code on exit + * @return hash table initialized + */ + Hashtable* initHash(UErrorCode& status); + + + + /** + * copy hash table + * + * @param source the source to copy from + * @param target the target to copy to + */ + void copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status); + + //-------------------- private data member --------------------- + // map from plural count to currency plural pattern, for example + // a plural pattern defined in "CurrencyUnitPatterns" is + // "one{{0} {1}}", in which "one" is a plural count + // and "{0} {1}" is a currency plural pattern". + // The currency plural pattern saved in this mapping is the pattern + // defined in "CurrencyUnitPattern" by replacing + // {0} with the number format pattern, + // and {1} with 3 currency sign. + Hashtable* fPluralCountToCurrencyUnitPattern; + + /* + * The plural rule is used to format currency plural name, + * for example: "3.00 US Dollars". + * If there are 3 currency signs in the currency patttern, + * the 3 currency signs will be replaced by currency plural name. + */ + PluralRules* fPluralRules; + + // locale + Locale* fLocale; +}; + + +inline UBool +CurrencyPluralInfo::operator!=(const CurrencyPluralInfo& info) const { return !operator==(info); } + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif // _CURRPINFO +//eof diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 16714321f43..abb1ba08c0a 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1,6 +1,6 @@ /* ******************************************************************************** -* Copyright (C) 1997-2008, International Business Machines +* Copyright (C) 1997-2009, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************** * @@ -36,10 +36,29 @@ #include "unicode/numfmt.h" #include "unicode/locid.h" +union UHashTok; + U_NAMESPACE_BEGIN + +U_CDECL_BEGIN + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV AffixValueComparator(UHashTok val1, UHashTok val2) ; + +/** + * @internal ICU 4.2 + */ +static UBool U_CALLCONV AffixPatternValueComparator(UHashTok val1, UHashTok val2) ; + +U_CDECL_END + class DigitList; class ChoiceFormat; +class CurrencyPluralInfo; +class Hashtable; /** * DecimalFormat is a concrete subclass of NumberFormat that formats decimal @@ -47,8 +66,8 @@ class ChoiceFormat; * and format numbers in any locale, including support for Western, Arabic, or * Indic digits. It also supports different flavors of numbers, including * integers ("123"), fixed-point numbers ("123.4"), scientific notation - * ("1.23E4"), percentages ("12%"), and currency amounts ("$123"). All of these - * flavors can be easily localized. + * ("1.23E4"), percentages ("12%"), and currency amounts ("$123", "USD123", + * "123 US dollars"). All of these flavors can be easily localized. * *

To obtain a NumberFormat for a specific locale (including the default * locale) call one of NumberFormat's factory methods such as @@ -100,6 +119,27 @@ class ChoiceFormat; * } * } * \endcode + *

+ * Another example use createInstance(style) + *

+ *

+ * // Print out a number using the localized number, currency,
+ * // percent, scientific, integer, iso currency, and plural currency
+ * // format for each locale
+ * Locale* locale = new Locale("en", "US");
+ * double myNumber = 1234.56;
+ * UErrorCode success = U_ZERO_ERROR;
+ * UnicodeString str;
+ * Formattable fmtable;
+ * for (int j=NumberFormat::kNumberStyle; 
+ *      j<=NumberFormat::kPluralCurrencyStyle; 
+ *      ++j) {
+ *     NumberFormat* format = NumberFormat::createInstance(locale, j, success);
+ *     str.remove();
+ *     cout << "format result " << form->format(myNumber, str) << endl;
+ *     format->parse(form->format(myNumber, str), fmtable, success);
+ * }
+ * * *

Patterns * @@ -208,6 +248,8 @@ class ChoiceFormat; * No * Currency sign, replaced by currency symbol. If * doubled, replaced by international currency symbol. + * If tripled, replaced by currency plural names, for example, + * "US dollar" or "US dollars" for America. * If present in a pattern, the monetary decimal separator * is used instead of the decimal separator. * @@ -322,6 +364,12 @@ class ChoiceFormat; * *

During parsing, grouping separators are ignored. * + *

For currency parsing, the formatter is able to parse every currency + * style formats no matter which style the formatter is constructed with. + * For example, a formatter instance gotten from + * NumberFormat.getInstance(ULocale, NumberFormat.CURRENCYSTYLE) can parse + * formats such as "USD1.00" and "3.00 US dollars". + * *

If parse(UnicodeString&,Formattable&,ParsePosition&) * fails to parse a string, it leaves the parse position unchanged. * The convenience method parse(UnicodeString&,Formattable&,UErrorCode&) @@ -685,6 +733,23 @@ public: DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status); + /* + * This API is for ICU use only. + * Create a DecimalFormat from the given pattern, symbols, and style. + * + * @param pattern a non-localized pattern string + * @param symbolsToAdopt the set of symbols to be used. The caller should not + * delete this object after making this call. + * @param style style of decimal format, kNumberStyle etc. + * @param status Output param set to success/failure code. If the + * pattern is invalid this will be set to a failure code. + * @internal ICU 4.2 + */ + DecimalFormat( const UnicodeString& pattern, + DecimalFormatSymbols* symbolsToAdopt, + NumberFormat::EStyles style, + UErrorCode& status); + /** * Create a DecimalFormat from the given pattern and symbols. * Use this constructor when you need to completely customize the @@ -970,6 +1035,31 @@ public: virtual void setDecimalFormatSymbols(const DecimalFormatSymbols& symbols); + /** + * Returns the currency plural format information, + * which is generally not changed by the programmer or user. + * @return desired CurrencyPluralInfo + * @draft ICU 4.2 + */ + virtual const CurrencyPluralInfo* getCurrencyPluralInfo(void) const; + + /** + * Sets the currency plural format information, + * which is generally not changed by the programmer or user. + * @param toAdopt CurrencyPluralInfo to be adopted. + * @draft ICU 4.2 + */ + virtual void adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt); + + /** + * Sets the currency plural format information, + * which is generally not changed by the programmer or user. + * @param info Currency Plural Info. + * @draft ICU 4.2 + */ + virtual void setCurrencyPluralInfo(const CurrencyPluralInfo& info); + + /** * Get the positive prefix. * @@ -1645,6 +1735,9 @@ public: virtual UClassID getDynamicClassID(void) const; private: + friend UBool U_CALLCONV AffixValueComparator(UHashTok val1, UHashTok val2); + friend UBool U_CALLCONV AffixPatternValueComparator(UHashTok val1, UHashTok val2); + DecimalFormat(); // default constructor not implemented int32_t precision(UBool isIntegral) const; @@ -1682,6 +1775,31 @@ private: UBool localized, UParseError& parseError, UErrorCode& status); + + /* + * similar to applyPattern, but without re-gen affix for currency + */ + void applyPatternInternally(const UnicodeString& pluralCount, + const UnicodeString& pattern, + UBool localized, + UParseError& parseError, + UErrorCode& status); + + /* + * only apply pattern without expand affixes + */ + void applyPatternWithoutExpandAffix(const UnicodeString& pattern, + UBool localized, + UParseError& parseError, + UErrorCode& status); + + + /* + * expand affixes (after apply patter) and re-compute fFormatWidth + */ + void expandAffixAdjustWidth(const UnicodeString* pluralCount); + + /** * Do the work of formatting a number, either a double or a long. * @@ -1698,6 +1816,7 @@ private: DigitList& digits, UBool isInteger) const; + void parse(const UnicodeString& text, Formattable& result, ParsePosition& pos, @@ -1708,16 +1827,35 @@ private: fgStatusLength // Leave last in list. } StatusFlags; - UBool subparse(const UnicodeString& text, ParsePosition& parsePosition, + UBool subparse(const UnicodeString& text, + const UnicodeString* negPrefix, + const UnicodeString* negSuffix, + const UnicodeString* posPrefix, + const UnicodeString* posSuffix, + UBool currencyParsing, + ParsePosition& parsePosition, DigitList& digits, UBool* status, UChar* currency) const; + // Mixed style parsing for currency. + // It parses against the current currency pattern + // using complex affix comparison + // parses against the currency plural patterns using complex affix comparison, + // and parses against the current pattern using simple affix comparison. + UBool parseForCurrency(const UnicodeString& text, + ParsePosition& parsePosition, + DigitList& digits, + UBool* status, + UChar* currency) const; + int32_t skipPadding(const UnicodeString& text, int32_t position) const; int32_t compareAffix(const UnicodeString& input, int32_t pos, UBool isNegative, UBool isPrefix, + const UnicodeString* affixPat, + UBool currencyParsing, UChar* currency) const; static int32_t compareSimpleAffix(const UnicodeString& affix, @@ -1752,7 +1890,7 @@ private: * there are special characters. Single quotes themselves must be * escaped in either case. */ - void appendAffixPattern(UnicodeString& appendTo, const UnicodeString& affix, + void appendAffixPattern(UnicodeString& appendTo, const UnicodeString& affix, UBool localized) const; void appendAffixPattern(UnicodeString& appendTo, @@ -1762,9 +1900,10 @@ private: void expandAffix(const UnicodeString& pattern, UnicodeString& affix, double number, - UBool doFormat) const; + UBool doFormat, + const UnicodeString* pluralCount) const; - void expandAffixes(); + void expandAffixes(const UnicodeString* pluralCount); static double round(double a, ERoundingMode mode, UBool isNegative); @@ -1776,6 +1915,47 @@ private: void setCurrencyForSymbols(); + // similar to setCurrency without re-compute the affixes for currency. + // If currency changes, the affix pattern for currency is not changed, + // but the affix will be changed. So, affixes need to be + // re-computed in setCurrency(), but not in setCurrencyInternally(). + virtual void setCurrencyInternally(const UChar* theCurrency, UErrorCode& ec); + + // set up currency affix patterns for mix parsing. + // The patterns saved here are the affix patterns of default currency + // pattern and the unique affix patterns of the plural currency patterns. + // Those patterns are used by parseForCurrency(). + void setupCurrencyAffixPatterns(UErrorCode& status); + + // set up the currency affixes used in currency plural formatting. + // It sets up both fAffixesForCurrency for currency pattern if the current + // pattern contains 3 currency signs, + // and it sets up fPluralAffixesForCurrency for currency plural patterns. + void setupCurrencyAffixes(const UnicodeString& pattern, + UBool setupForCurrentPattern, + UBool setupForPluralPattern, + UErrorCode& status); + + // hashtable operations + Hashtable* initHashForAffixPattern(UErrorCode& status); + Hashtable* initHashForAffix(UErrorCode& status); + + void deleteHashForAffixPattern(); + void deleteHashForAffix(Hashtable*& table); + + void copyHashForAffixPattern(const Hashtable* source, + Hashtable* target, UErrorCode& status); + void copyHashForAffix(const Hashtable* source, + Hashtable* target, UErrorCode& status); + + // currency sign count + enum { + fgCurrencySignCountZero, + fgCurrencySignCountInSymbolFormat, + fgCurrencySignCountInISOFormat, + fgCurrencySignCountInPluralFormat + } CurrencySignCount; + /** * Constants. */ @@ -1803,7 +1983,6 @@ private: int32_t fGroupingSize; int32_t fGroupingSize2; UBool fDecimalSeparatorAlwaysShown; - /*transient*/ UBool fIsCurrencyFormat; DecimalFormatSymbols* fSymbols; UBool fUseSignificantDigits; @@ -1826,6 +2005,110 @@ private: int32_t fFormatWidth; EPadPosition fPadPosition; + /* + * Following are used for currency format + */ + // pattern used in this formatter + UnicodeString fFormatPattern; + // style is only valid when decimal formatter is constructed by + // DecimalFormat(pattern, decimalFormatSymbol, style) + int fStyle; + /* + * Represents whether this is a currency format, and which + * currency format style. + * 0: not currency format type; + * 1: currency style -- symbol name, such as "$" for US dollar. + * 2: currency style -- ISO name, such as USD for US dollar. + * 3: currency style -- plural long name, such as "US Dollar" for + * "1.00 US Dollar", or "US Dollars" for + * "3.00 US Dollars". + */ + int fCurrencySignCount; + + + /* For currency parsing purose, + * Need to remember all prefix patterns and suffix patterns of + * every currency format pattern, + * including the pattern of default currecny style + * and plural currency style. And the patterns are set through applyPattern. + */ + // TODO: innerclass? + struct AffixPatternsForCurrency : public UMemory { + // negative prefix pattern + UnicodeString negPrefixPatternForCurrency; + // negative suffix pattern + UnicodeString negSuffixPatternForCurrency; + // positive prefix pattern + UnicodeString posPrefixPatternForCurrency; + // positive suffix pattern + UnicodeString posSuffixPatternForCurrency; + + AffixPatternsForCurrency(const UnicodeString& negPrefix, + const UnicodeString& negSuffix, + const UnicodeString& posPrefix, + const UnicodeString& posSuffix) { + negPrefixPatternForCurrency = negPrefix; + negSuffixPatternForCurrency = negSuffix; + posPrefixPatternForCurrency = posPrefix; + posSuffixPatternForCurrency = posSuffix; + } + }; + + /* affix for currency formatting when the currency sign in the pattern + * equals to 3, such as the pattern contains 3 currency sign or + * the formatter style is currency plural format style. + */ + struct AffixesForCurrency : public UMemory { + // negative prefix + UnicodeString negPrefixForCurrency; + // negative suffix + UnicodeString negSuffixForCurrency; + // positive prefix + UnicodeString posPrefixForCurrency; + // positive suffix + UnicodeString posSuffixForCurrency; + + int32_t formatWidth; + + AffixesForCurrency(const UnicodeString& negPrefix, + const UnicodeString& negSuffix, + const UnicodeString& posPrefix, + const UnicodeString& posSuffix) { + negPrefixForCurrency = negPrefix; + negSuffixForCurrency = negSuffix; + posPrefixForCurrency = posPrefix; + posSuffixForCurrency = posSuffix; + } + }; + + // Affix pattern set for currency. + // It is a set of AffixPatternsForCurrency, + // each element of the set saves the negative prefix pattern, + // negative suffix pattern, positive prefix pattern, + // and positive suffix pattern of a pattern. + // It is used for currency mixed style parsing. + // It is actually is a set. + // The set contains the default currency pattern from the locale, + // and the currency plural patterns. + // Since it is a set, it does not contain duplicated items. + // For example, if 2 currency plural patterns are the same, only one pattern + // is included in the set. When parsing, we do not check whether the plural + // count match or not. + Hashtable* fAffixPatternsForCurrency; + + // Following 2 are affixes for currency. + // It is a hash map from plural count to AffixesForCurrency. + // AffixesForCurrency saves the negative prefix, + // negative suffix, positive prefix, and positive suffix of a pattern. + // It is used during currency formatting only when the currency sign count + // is 3. In which case, the affixes are getting from here, not + // from the fNegativePrefix etc. + Hashtable* fAffixesForCurrency; // for current pattern + Hashtable* fPluralAffixesForCurrency; // for plural pattern + + // Information needed for DecimalFormat to format/parse currency plural. + CurrencyPluralInfo* fCurrencyPluralInfo; + protected: /** diff --git a/icu4c/source/i18n/unicode/numfmt.h b/icu4c/source/i18n/unicode/numfmt.h index b27ed470819..1d49ba1679a 100644 --- a/icu4c/source/i18n/unicode/numfmt.h +++ b/icu4c/source/i18n/unicode/numfmt.h @@ -1,6 +1,6 @@ /* ******************************************************************************** -* Copyright (C) 1997-2006, International Business Machines Corporation and others. +* Copyright (C) 1997-2009, International Business Machines Corporation and others. * All Rights Reserved. ******************************************************************************** * @@ -107,6 +107,22 @@ class StringEnumeration; * to get a format for displaying percentages. With this format, a * fraction from 0.53 is displayed as 53%. *

+ * Starting from ICU 4.2, you can use createInstance() by passing in a 'style' + * as parameter to get the correct instance. + * For example, + * use createInstance(...kNumberStyle...) to get the normal number format, + * createInstance(...kPercentStyle...) to get a format for displaying + * percentage, + * createInstance(...kScientificStyle...) to get a format for displaying + * scientific number, + * createInstance(...kCurrencyStyle...) to get the currency number format, + * in which the currency is represented by its symbol, for example, "$3.00". + * createInstance(...kIsoCurrencyStyle...) to get the currency number format, + * in which the currency is represented by its ISO code, for example "USD3.00". + * createInstance(...kPluralCurrencyStyle...) to get the currency number format, + * in which the currency is represented by its full name in plural format, + * for example, "3.00 US dollars" or "1.00 US dollar". + *

* You can also control the display of numbers with such methods as * getMinimumFractionDigits. If you want even more control over the * format or parsing, or want to give your users more control, you can @@ -146,6 +162,29 @@ class StringEnumeration; class U_I18N_API NumberFormat : public Format { public: + /** + * Constants for various number format styles. + * kNumberStyle specifies a normal number style of format. + * kCurrencyStyle specifies a currency format using currency symbol name, + * such as in "$1.00". + * kPercentStyle specifies a style of format to display percent. + * kScientificStyle specifies a style of format to display scientific number. + * kISOCurrencyStyle specifies a currency format using ISO currency code, + * such as in "USD1.00". + * kPluralCurrencyStyle specifies a currency format using currency plural + * names, such as in "1.00 US dollar" and "3.00 US dollars". + * @draft ICU 4.2 + */ + enum EStyles { + kNumberStyle, + kCurrencyStyle, + kPercentStyle, + kScientificStyle, + kIsoCurrencyStyle, + kPluralCurrencyStyle, + kStyleCount // ALWAYS LAST ENUM: number of styles + }; + /** * Alignment Field constants used to construct a FieldPosition object. * Signifies that the position of the integer part or fraction part of @@ -444,6 +483,17 @@ public: static NumberFormat* U_EXPORT2 createInstance(const Locale& inLocale, UErrorCode&); + /** + * Creates the specified decimal format style of the desired locale. + * @param desiredLocale the given locale. + * @param choice the given style. + * @param success Output param filled with success/failure status. + * @return A new NumberFormat instance. + * @draft ICU 4.2 + */ + static NumberFormat* U_EXPORT2 createInstance(const Locale& desiredLocale, EStyles choice, UErrorCode& success); + + /** * Returns a currency format for the current default locale. * @stable ICU 2.0 @@ -712,25 +762,6 @@ protected: private: - enum EStyles { - kNumberStyle, - kCurrencyStyle, - kPercentStyle, - kScientificStyle, - kStyleCount // ALWAYS LAST ENUM: number of styles - }; - - /** - * Creates the specified decimal format style of the desired locale. - * Hook for service registration, uses makeInstance directly if no services - * registered. - * @param desiredLocale the given locale. - * @param choice the given style. - * @param success Output param filled with success/failure status. - * @return A new NumberFormat instance. - */ - static NumberFormat* U_EXPORT2 createInstance(const Locale& desiredLocale, EStyles choice, UErrorCode& success); - /** * Creates the specified decimal format style of the desired locale. * @param desiredLocale the given locale. diff --git a/icu4c/source/i18n/unicode/ucurr.h b/icu4c/source/i18n/unicode/ucurr.h index c6b371304b7..4cd78b75043 100644 --- a/icu4c/source/i18n/unicode/ucurr.h +++ b/icu4c/source/i18n/unicode/ucurr.h @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2002-2008, International Business Machines +* Copyright (c) 2002-2009, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** */ @@ -138,6 +138,30 @@ ucurr_getName(const UChar* currency, int32_t* len, UErrorCode* ec); +/** + * Returns the plural name for the given currency in the + * given locale. For example, the plural name for the USD + * currency object in the en_US locale is "US dollar" or "US dollars". + * @param currency null-terminated 3-letter ISO 4217 code + * @param locale locale in which to display currency + * @param isChoiceFormat fill-in set to TRUE if the returned value + * is a ChoiceFormat pattern; otherwise it is a static string + * @param pluralCount plural count + * @param len fill-in parameter to receive length of result + * @param ec error code + * @return pointer to display string of 'len' UChars. If the resource + * data contains no entry for 'currency', then 'currency' itself is + * returned. + * @draft ICU 4.2 + */ +U_STABLE const UChar* U_EXPORT2 +ucurr_getPluralName(const UChar* currency, + const char* locale, + UBool* isChoiceFormat, + const char* pluralCount, + int32_t* len, + UErrorCode* ec); + /** * Returns the number of the number of fraction digits that should * be displayed for the given currency. diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 3438ac62fb9..27ee7c82b5e 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -27,8 +27,11 @@ #include "winnmtst.h" #include #include +#include +#include "cstring.h" static const UChar EUR[] = {69,85,82,0}; // "EUR" +static const UChar ISO_CURRENCY_USD[] = {0x55, 0x53, 0x44, 0}; // "USD" // ***************************************************************************** // class NumberFormatTest @@ -87,7 +90,11 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n CASE(35,TestRounding); CASE(36,TestNonpositiveMultiplier); CASE(37,TestNumberingSystems); - + CASE(38,TestSpaceParsing); + CASE(39,TestMultiCurrencySign); + CASE(40,TestCurrencyFormatForMixParsing); + CASE(41,TestDecimalFormatCurrencyParse); + CASE(42,TestCurrencyIsoPluralFormat); default: name = ""; break; } } @@ -2309,6 +2316,29 @@ void NumberFormatTest::TestHost() #ifdef U_WINDOWS Win32NumberTest::testLocales(this); #endif + for (NumberFormat::EStyles k = NumberFormat::kNumberStyle; + k < NumberFormat::kStyleCount; k = (NumberFormat::EStyles)(k+1)) { + UErrorCode status = U_ZERO_ERROR; + Locale loc("en_US@compat=host"); + NumberFormat *full = NumberFormat::createInstance(loc, status); + if (full == NULL || U_FAILURE(status)) { + errln("FAIL: Can't create number instance for host"); + return; + } + UnicodeString result1; + Formattable number(10.00); + full->format(number, result1, status); + if (U_FAILURE(status)) { + errln("FAIL: Can't format for host"); + return; + } + Formattable formattable; + full->parse(result1, formattable, status); + if (U_FAILURE(status)) { + errln("FAIL: Can't parse for host"); + return; + } + } } void NumberFormatTest::TestHostClone() @@ -2536,10 +2566,51 @@ void NumberFormatTest::TestNonpositiveMultiplier() { } +void +NumberFormatTest::TestSpaceParsing() { + // the data are: + // the string to be parsed, parsed position, parsed error index + const char* DATA[][3] = { + {"$124", "4", "-1"}, + {"$124 $124", "4", "-1"}, + {"$124 ", "4", "-1"}, + //{"$ 124 ", "5", "-1"}, // TODO: need to handle space correctly + //{"$\\u00A0124 ", "5", "-1"}, // TODO: need to handle space correctly + {"$ 124 ", "0", "0"}, + {"$\\u00A0124 ", "0", "0"}, + {" $ 124 ", "0", "0"}, // TODO: need to handle space correctly + {"124$", "0", "3"}, // TODO: need to handle space correctly + {"124 $", "5", "-1"}, + }; + UErrorCode status = U_ZERO_ERROR; + NumberFormat* foo = NumberFormat::createCurrencyInstance(status); + if (U_FAILURE(status)) { + delete foo; + return; + } + for (uint32_t i = 0; i < sizeof(DATA)/sizeof(DATA[0]); ++i) { + ParsePosition parsePosition(0); + UnicodeString stringToBeParsed = ctou(DATA[i][0]); + int parsedPosition = atoi(DATA[i][1]); + int errorIndex = atoi(DATA[i][2]); + Formattable result; + foo->parse(stringToBeParsed, result, parsePosition); + if (parsePosition.getIndex() != parsedPosition || + parsePosition.getErrorIndex() != errorIndex) { + errln("FAILED parse " + stringToBeParsed + "; wrong position, expected: (" + parsedPosition + ", " + errorIndex + "); got (" + parsePosition.getIndex() + ", " + parsePosition.getErrorIndex() + ")"); + } + if (parsePosition.getErrorIndex() == -1 && + result.getType() == Formattable::kLong && + result.getLong() != 124) { + errln("FAILED parse " + stringToBeParsed + "; wrong number, expect: 124, got " + result.getLong()); + } + } + delete foo; +} + /** * Test using various numbering systems and numbering system keyword. */ - void NumberFormatTest::TestNumberingSystems() { UErrorCode ec = U_ZERO_ERROR; @@ -2576,4 +2647,286 @@ void NumberFormatTest::TestNumberingSystems() { delete fmt3; } + +void +NumberFormatTest::TestMultiCurrencySign() { + const char* DATA[][6] = { + // the fields in the following test are: + // locale, + // currency pattern (with negative pattern), + // currency number to be formatted, + // currency format using currency symbol name, such as "$" for USD, + // currency format using currency ISO name, such as "USD", + // currency format using plural name, such as "US dollars". + // for US locale + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1234.56", "$1,234.56", "USD1,234.56", "US dollars1,234.56"}, + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "-1234.56", "-$1,234.56", "-USD1,234.56", "-US dollars1,234.56"}, + {"en_US", "\\u00A4#,##0.00;-\\u00A4#,##0.00", "1", "$1.00", "USD1.00", "US dollar1.00"}, + // for CHINA locale + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1234.56", "\\uFFE51,234.56", "CNY1,234.56", "\\u4EBA\\u6C11\\u5E011,234.56"}, + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "-1234.56", "(\\uFFE51,234.56)", "(CNY1,234.56)", "(\\u4EBA\\u6C11\\u5E011,234.56)"}, + {"zh_CN", "\\u00A4#,##0.00;(\\u00A4#,##0.00)", "1", "\\uFFE51.00", "CNY1.00", "\\u4EBA\\u6C11\\u5E011.00"} + }; + + const UChar doubleCurrencySign[] = {0xA4, 0xA4, 0}; + UnicodeString doubleCurrencyStr(doubleCurrencySign); + const UChar tripleCurrencySign[] = {0xA4, 0xA4, 0xA4, 0}; + UnicodeString tripleCurrencyStr(tripleCurrencySign); + + for (uint32_t i=0; iformat(numberToBeFormat, s); + // DATA[i][3] is the currency format result using a + // single currency sign. + // DATA[i][4] is the currency format result using + // double currency sign. + // DATA[i][5] is the currency format result using + // triple currency sign. + // DATA[i][j+2] is the currency format result using + // 'j' number of currency sign. + UnicodeString currencyFormatResult = ctou(DATA[i][2+j]); + if (s.compare(currencyFormatResult)) { + errln("FAIL format: Expected " + currencyFormatResult + "; Got " + s); + } + // mix style parsing + for (int k=3; k<=5; ++k) { + // DATA[i][3] is the currency format result using a + // single currency sign. + // DATA[i][4] is the currency format result using + // double currency sign. + // DATA[i][5] is the currency format result using + // triple currency sign. + UnicodeString oneCurrencyFormat = ctou(DATA[i][k]); + UErrorCode status = U_ZERO_ERROR; + Formattable parseRes; + fmt->parse(oneCurrencyFormat, parseRes, status); + if (U_FAILURE(status) || + (parseRes.getType() == Formattable::kDouble && + parseRes.getDouble() != numberToBeFormat) || + (parseRes.getType() == Formattable::kLong && + parseRes.getLong() != numberToBeFormat)) { + errln("FAILED parse " + oneCurrencyFormat); + } + } + delete fmt; + } + delete sym; + } +} + + +void +NumberFormatTest::TestCurrencyFormatForMixParsing() { + UErrorCode status = U_ZERO_ERROR; + MeasureFormat* curFmt = MeasureFormat::createCurrencyFormat(Locale("en_US"), status); + if (U_FAILURE(status)) { + delete curFmt; + return; + } + const char* formats[] = { + "$1,234.56", // string to be parsed + "USD1,234.56", + "US dollars1,234.56", + "1,234.56 US dollars" + }; + for (uint32_t i = 0; i < sizeof(formats)/sizeof(formats[0]); ++i) { + UnicodeString stringToBeParsed = ctou(formats[i]); + Formattable result; + UErrorCode status = U_ZERO_ERROR; + curFmt->parseObject(stringToBeParsed, result, status); + if (U_FAILURE(status)) { + errln("FAIL: measure format parsing"); + } + if (result.getType() != Formattable::kObject || + result.getObject()->getDynamicClassID() != CurrencyAmount::getStaticClassID() || + ((CurrencyAmount*)result.getObject())->getNumber().getDouble() != 1234.56 || + UnicodeString(((CurrencyAmount*)result.getObject())->getISOCurrency()).compare(ISO_CURRENCY_USD)) { + errln("FAIL: getCurrencyFormat of default locale (en_US) failed roundtripping the number "); + if (((CurrencyAmount*)result.getObject())->getNumber().getDouble() != 1234.56) { + errln((UnicodeString)"wong number, expect: 1234.56" + ", got: " + ((CurrencyAmount*)result.getObject())->getNumber().getDouble()); + } + if (((CurrencyAmount*)result.getObject())->getISOCurrency() != ISO_CURRENCY_USD) { + errln((UnicodeString)"wong currency, expect: USD" + ", got: " + ((CurrencyAmount*)result.getObject())->getISOCurrency()); + } + } + } + delete curFmt; +} + + +void +NumberFormatTest::TestDecimalFormatCurrencyParse() { + // Locale.US + UErrorCode status = U_ZERO_ERROR; + DecimalFormatSymbols* sym = new DecimalFormatSymbols(Locale("en_US"), status); + if (U_FAILURE(status)) { + delete sym; + return; + } + UnicodeString pat; + UChar currency = 0x00A4; + // "\xA4#,##0.00;-\xA4#,##0.00" + pat.append(currency).append(currency).append(currency).append("#,##0.00;-").append(currency).append(currency).append(currency).append("#,##0.00"); + DecimalFormat* fmt = new DecimalFormat(pat, sym, status); + if (U_FAILURE(status)) { + delete fmt; + errln("failed to new DecimalFormat in TestDecimalFormatCurrencyParse"); + return; + } + const char* DATA[][2] = { + // the data are: + // string to be parsed, the parsed result (number) + {"$1.00", "1"}, + {"USD1.00", "1"}, + {"1.00 US dollar", "1"}, + {"$1,234.56", "1234.56"}, + {"USD1,234.56", "1234.56"}, + {"1,234.56 US dollar", "1234.56"}, + }; + for (uint32_t i = 0; i < sizeof(DATA)/sizeof(DATA[0]); ++i) { + UnicodeString stringToBeParsed = ctou(DATA[i][0]); + double parsedResult = atof(DATA[i][1]); + UErrorCode status = U_ZERO_ERROR; + Formattable result; + fmt->parse(stringToBeParsed, result, status); + if (U_FAILURE(status) || + (result.getType() == Formattable::kDouble && + result.getDouble() != parsedResult) || + (result.getType() == Formattable::kLong && + result.getLong() != parsedResult)) { + errln((UnicodeString)"FAIL parse: Expected " + parsedResult); + } + } + delete fmt; +} + + +void +NumberFormatTest::TestCurrencyIsoPluralFormat() { + const char* DATA[][6] = { + // the data are: + // locale, + // currency amount to be formatted, + // currency ISO code to be formatted, + // format result using CURRENCYSTYLE, + // format result using ISOCURRENCYSTYLE, + // format result using PLURALCURRENCYSTYLE, + {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollar"}, + {"en_US", "1234.56", "USD", "$1,234.56", "USD1,234.56", "1,234.56 US dollars"}, + {"en_US", "-1234.56", "USD", "($1,234.56)", "(USD1,234.56)", "-1,234.56 US dollars"}, + {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00 \\u7F8E\\u5143"}, + {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD1,234.56", "1,234.56 \\u7F8E\\u5143"}, + {"zh_CN", "1", "CHY", "CHY1.00", "CHY1.00", "1.00 CHY"}, + {"zh_CN", "1234.56", "CHY", "CHY1,234.56", "CHY1,234.56", "1,234.56 CHY"}, + {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY1.00", "1.00 \\u4EBA\\u6C11\\u5E01"}, + {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY1,234.56", "1,234.56 \\u4EBA\\u6C11\\u5E01"}, + {"ru_RU", "1", "RUB", "1,00\\u00A0\\u0440\\u0443\\u0431.", "1,00\\u00A0RUB", "1,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0439 \\u0440\\u0443\\u0431\\u043B\\u044C"}, + {"ru_RU", "2", "RUB", "2,00\\u00A0\\u0440\\u0443\\u0431.", "2,00\\u00A0RUB", "2,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0445 \\u0440\\u0443\\u0431\\u043B\\u044F"}, + {"ru_RU", "5", "RUB", "5,00\\u00A0\\u0440\\u0443\\u0431.", "5,00\\u00A0RUB", "5,00 \\u0420\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u0438\\u0445 \\u0440\\u0443\\u0431\\u043B\\u0435\\u0439"}, + // test locale without currency information + {"ti_ET", "-1.23", "USD", "-US$1.23", "-USD1.23", "-1.23 USD"}, + // test choice format + {"es_AR", "1", "INR", "Re.\\u00A01,00", "INR\\u00A01,00", "1,00 rupia india"}, + }; + + for (uint32_t i=0; isetCurrency(currencyCode, status); + if (U_FAILURE(status)) { + delete numFmt; + errln((UnicodeString)"can not set currency:" + currencyISOCode); + continue; + } + + UnicodeString strBuf; + numFmt->format(numberToBeFormat, strBuf); + int resultDataIndex = k; + if ( k == NumberFormat::kCurrencyStyle ) { + resultDataIndex = k+2; + } + // DATA[i][resultDataIndex] is the currency format result + // using 'k' currency style. + UnicodeString formatResult = ctou(DATA[i][resultDataIndex]); + if (strBuf.compare(formatResult)) { + errln("FAIL: Expected " + formatResult + " actual: " + strBuf); + } + // test parsing, and test parsing for all currency formats. + for (int j = 3; j < 6; ++j) { + // DATA[i][3] is the currency format result using + // CURRENCYSTYLE formatter. + // DATA[i][4] is the currency format result using + // ISOCURRENCYSTYLE formatter. + // DATA[i][5] is the currency format result using + // PLURALCURRENCYSTYLE formatter. + UnicodeString oneCurrencyFormatResult = ctou(DATA[i][j]); + UErrorCode status = U_ZERO_ERROR; + Formattable parseResult; + numFmt->parse(oneCurrencyFormatResult, parseResult, status); + if (U_FAILURE(status) || + (parseResult.getType() == Formattable::kDouble && + parseResult.getDouble() != numberToBeFormat) || + (parseResult.getType() == Formattable::kLong && + parseResult.getLong() != numberToBeFormat)) { + errln((UnicodeString)"FAIL: getCurrencyFormat of locale " + + localeString + " failed roundtripping the number"); + if (parseResult.getType() == Formattable::kDouble) { + errln((UnicodeString)"expected: " + numberToBeFormat + "; actual: " +parseResult.getDouble()); + } else { + errln((UnicodeString)"expected: " + numberToBeFormat + "; actual: " +parseResult.getLong()); + } + } + } + delete numFmt; + } + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index 69128b0d3a7..7b6afe3550b 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -134,6 +134,13 @@ class NumberFormatTest: public CalendarTimeZoneTest { void TestNumberingSystems(); + + void TestSpaceParsing(); + void TestMultiCurrencySign(); + void TestCurrencyFormatForMixParsing(); + void TestDecimalFormatCurrencyParse(); + void TestCurrencyIsoPluralFormat(); + private: static UBool equalValue(const Formattable& a, const Formattable& b);