michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 1997-2012, International Business Machines Corporation and * michael@0: * others. All Rights Reserved. * michael@0: ******************************************************************************* michael@0: * michael@0: * File COMPACTDECIMALFORMAT.CPP michael@0: * michael@0: ******************************************************************************** michael@0: */ michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "charstr.h" michael@0: #include "cstring.h" michael@0: #include "digitlst.h" michael@0: #include "mutex.h" michael@0: #include "unicode/compactdecimalformat.h" michael@0: #include "unicode/numsys.h" michael@0: #include "unicode/plurrule.h" michael@0: #include "unicode/ures.h" michael@0: #include "ucln_in.h" michael@0: #include "uhash.h" michael@0: #include "umutex.h" michael@0: #include "unicode/ures.h" michael@0: #include "uresimp.h" michael@0: michael@0: #define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) michael@0: michael@0: // Maps locale name to CDFLocaleData struct. michael@0: static UHashtable* gCompactDecimalData = NULL; michael@0: static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER; michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static const int32_t MAX_DIGITS = 15; michael@0: static const char gOther[] = "other"; michael@0: static const char gLatnTag[] = "latn"; michael@0: static const char gNumberElementsTag[] = "NumberElements"; michael@0: static const char gDecimalFormatTag[] = "decimalFormat"; michael@0: static const char gPatternsShort[] = "patternsShort"; michael@0: static const char gPatternsLong[] = "patternsLong"; michael@0: static const char gRoot[] = "root"; michael@0: michael@0: static const UChar u_0 = 0x30; michael@0: static const UChar u_apos = 0x27; michael@0: michael@0: static const UChar kZero[] = {u_0}; michael@0: michael@0: // Used to unescape single quotes. michael@0: enum QuoteState { michael@0: OUTSIDE, michael@0: INSIDE_EMPTY, michael@0: INSIDE_FULL michael@0: }; michael@0: michael@0: enum FallbackFlags { michael@0: ANY = 0, michael@0: MUST = 1, michael@0: NOT_ROOT = 2 michael@0: // Next one will be 4 then 6 etc. michael@0: }; michael@0: michael@0: michael@0: // CDFUnit represents a prefix-suffix pair for a particular variant michael@0: // and log10 value. michael@0: struct CDFUnit : public UMemory { michael@0: UnicodeString prefix; michael@0: UnicodeString suffix; michael@0: inline CDFUnit() : prefix(), suffix() { michael@0: prefix.setToBogus(); michael@0: } michael@0: inline ~CDFUnit() {} michael@0: inline UBool isSet() const { michael@0: return !prefix.isBogus(); michael@0: } michael@0: inline void markAsSet() { michael@0: prefix.remove(); michael@0: } michael@0: }; michael@0: michael@0: // CDFLocaleStyleData contains formatting data for a particular locale michael@0: // and style. michael@0: class CDFLocaleStyleData : public UMemory { michael@0: public: michael@0: // What to divide by for each log10 value when formatting. These values michael@0: // will be powers of 10. For English, would be: michael@0: // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ... michael@0: double divisors[MAX_DIGITS]; michael@0: // Maps plural variants to CDFUnit[MAX_DIGITS] arrays. michael@0: // To format a number x, michael@0: // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]). michael@0: // Compute the plural variant for displayNum michael@0: // (e.g zero, one, two, few, many, other). michael@0: // Compute cdfUnits = unitsByVariant[pluralVariant]. michael@0: // Prefix and suffix to use at cdfUnits[log10(x)] michael@0: UHashtable* unitsByVariant; michael@0: inline CDFLocaleStyleData() : unitsByVariant(NULL) {} michael@0: ~CDFLocaleStyleData(); michael@0: // Init initializes this object. michael@0: void Init(UErrorCode& status); michael@0: inline UBool isBogus() const { michael@0: return unitsByVariant == NULL; michael@0: } michael@0: void setToBogus(); michael@0: private: michael@0: CDFLocaleStyleData(const CDFLocaleStyleData&); michael@0: CDFLocaleStyleData& operator=(const CDFLocaleStyleData&); michael@0: }; michael@0: michael@0: // CDFLocaleData contains formatting data for a particular locale. michael@0: struct CDFLocaleData : public UMemory { michael@0: CDFLocaleStyleData shortData; michael@0: CDFLocaleStyleData longData; michael@0: inline CDFLocaleData() : shortData(), longData() { } michael@0: inline ~CDFLocaleData() { } michael@0: // Init initializes this object. michael@0: void Init(UErrorCode& status); michael@0: }; michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: U_CDECL_BEGIN michael@0: michael@0: static UBool U_CALLCONV cdf_cleanup(void) { michael@0: if (gCompactDecimalData != NULL) { michael@0: uhash_close(gCompactDecimalData); michael@0: gCompactDecimalData = NULL; michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: static void U_CALLCONV deleteCDFUnits(void* ptr) { michael@0: delete [] (icu::CDFUnit*) ptr; michael@0: } michael@0: michael@0: static void U_CALLCONV deleteCDFLocaleData(void* ptr) { michael@0: delete (icu::CDFLocaleData*) ptr; michael@0: } michael@0: michael@0: U_CDECL_END michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static UBool divisors_equal(const double* lhs, const double* rhs); michael@0: static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); michael@0: michael@0: static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status); michael@0: static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status); michael@0: static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status); michael@0: static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status); michael@0: static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status); michael@0: static UBool isRoot(const UResourceBundle* rb, UErrorCode& status); michael@0: static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status); michael@0: static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status); michael@0: static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status); michael@0: static UBool onlySpaces(UnicodeString u); michael@0: static void fixQuotes(UnicodeString& s); michael@0: static void fillInMissing(CDFLocaleStyleData* result); michael@0: static int32_t computeLog10(double x, UBool inRange); michael@0: static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status); michael@0: static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value); michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) michael@0: michael@0: CompactDecimalFormat::CompactDecimalFormat( michael@0: const DecimalFormat& decimalFormat, michael@0: const UHashtable* unitsByVariant, michael@0: const double* divisors, michael@0: PluralRules* pluralRules) michael@0: : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) { michael@0: } michael@0: michael@0: CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) michael@0: : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) { michael@0: } michael@0: michael@0: CompactDecimalFormat* U_EXPORT2 michael@0: CompactDecimalFormat::createInstance( michael@0: const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { michael@0: LocalPointer decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status)); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: LocalPointer pluralRules(PluralRules::forLocale(inLocale, status)); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: CompactDecimalFormat* result = michael@0: new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias()); michael@0: if (result == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: pluralRules.orphan(); michael@0: result->setMaximumSignificantDigits(3); michael@0: result->setSignificantDigitsUsed(TRUE); michael@0: result->setGroupingUsed(FALSE); michael@0: return result; michael@0: } michael@0: michael@0: CompactDecimalFormat& michael@0: CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { michael@0: if (this != &rhs) { michael@0: DecimalFormat::operator=(rhs); michael@0: _unitsByVariant = rhs._unitsByVariant; michael@0: _divisors = rhs._divisors; michael@0: delete _pluralRules; michael@0: _pluralRules = rhs._pluralRules->clone(); michael@0: } michael@0: return *this; michael@0: } michael@0: michael@0: CompactDecimalFormat::~CompactDecimalFormat() { michael@0: delete _pluralRules; michael@0: } michael@0: michael@0: michael@0: Format* michael@0: CompactDecimalFormat::clone(void) const { michael@0: return new CompactDecimalFormat(*this); michael@0: } michael@0: michael@0: UBool michael@0: CompactDecimalFormat::operator==(const Format& that) const { michael@0: if (this == &that) { michael@0: return TRUE; michael@0: } michael@0: return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that)); michael@0: } michael@0: michael@0: UBool michael@0: CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const { michael@0: return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules); michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: double number, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos) const { michael@0: DigitList orig, rounded; michael@0: orig.set(number); michael@0: UBool isNegative; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: _round(orig, rounded, isNegative, status); michael@0: if (U_FAILURE(status)) { michael@0: return appendTo; michael@0: } michael@0: double roundedDouble = rounded.getDouble(); michael@0: if (isNegative) { michael@0: roundedDouble = -roundedDouble; michael@0: } michael@0: int32_t baseIdx = computeLog10(roundedDouble, TRUE); michael@0: double numberToFormat = roundedDouble / _divisors[baseIdx]; michael@0: UnicodeString variant = _pluralRules->select(numberToFormat); michael@0: if (isNegative) { michael@0: numberToFormat = -numberToFormat; michael@0: } michael@0: const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx); michael@0: appendTo += unit->prefix; michael@0: DecimalFormat::format(numberToFormat, appendTo, pos); michael@0: appendTo += unit->suffix; michael@0: return appendTo; michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: double /* number */, michael@0: UnicodeString& appendTo, michael@0: FieldPositionIterator* /* posIter */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: return appendTo; michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: int64_t number, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos) const { michael@0: return format((double) number, appendTo, pos); michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: int64_t /* number */, michael@0: UnicodeString& appendTo, michael@0: FieldPositionIterator* /* posIter */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: return appendTo; michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: const StringPiece& /* number */, michael@0: UnicodeString& appendTo, michael@0: FieldPositionIterator* /* posIter */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: return appendTo; michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format( michael@0: const DigitList& /* number */, michael@0: UnicodeString& appendTo, michael@0: FieldPositionIterator* /* posIter */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: return appendTo; michael@0: } michael@0: michael@0: UnicodeString& michael@0: CompactDecimalFormat::format(const DigitList& /* number */, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& /* pos */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: return appendTo; michael@0: } michael@0: michael@0: void michael@0: CompactDecimalFormat::parse( michael@0: const UnicodeString& /* text */, michael@0: Formattable& /* result */, michael@0: ParsePosition& /* parsePosition */) const { michael@0: } michael@0: michael@0: void michael@0: CompactDecimalFormat::parse( michael@0: const UnicodeString& /* text */, michael@0: Formattable& /* result */, michael@0: UErrorCode& status) const { michael@0: status = U_UNSUPPORTED_ERROR; michael@0: } michael@0: michael@0: CurrencyAmount* michael@0: CompactDecimalFormat::parseCurrency( michael@0: const UnicodeString& /* text */, michael@0: ParsePosition& /* pos */) const { michael@0: return NULL; michael@0: } michael@0: michael@0: void CDFLocaleStyleData::Init(UErrorCode& status) { michael@0: if (unitsByVariant != NULL) { michael@0: return; michael@0: } michael@0: unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: uhash_setKeyDeleter(unitsByVariant, uprv_free); michael@0: uhash_setValueDeleter(unitsByVariant, deleteCDFUnits); michael@0: } michael@0: michael@0: CDFLocaleStyleData::~CDFLocaleStyleData() { michael@0: setToBogus(); michael@0: } michael@0: michael@0: void CDFLocaleStyleData::setToBogus() { michael@0: if (unitsByVariant != NULL) { michael@0: uhash_close(unitsByVariant); michael@0: unitsByVariant = NULL; michael@0: } michael@0: } michael@0: michael@0: void CDFLocaleData::Init(UErrorCode& status) { michael@0: shortData.Init(status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: longData.Init(status); michael@0: } michael@0: michael@0: // Helper method for operator= michael@0: static UBool divisors_equal(const double* lhs, const double* rhs) { michael@0: for (int32_t i = 0; i < MAX_DIGITS; ++i) { michael@0: if (lhs[i] != rhs[i]) { michael@0: return FALSE; michael@0: } michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: // getCDFLocaleStyleData returns pointer to formatting data for given locale and michael@0: // style within the global cache. On cache miss, getCDFLocaleStyleData loads michael@0: // the data from CLDR into the global cache before returning the pointer. If a michael@0: // UNUM_LONG data is requested for a locale, and that locale does not have michael@0: // UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for michael@0: // that locale. michael@0: static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: CDFLocaleData* result = NULL; michael@0: const char* key = inLocale.getName(); michael@0: { michael@0: Mutex lock(&gCompactDecimalMetaLock); michael@0: if (gCompactDecimalData == NULL) { michael@0: gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: uhash_setKeyDeleter(gCompactDecimalData, uprv_free); michael@0: uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData); michael@0: ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup); michael@0: } else { michael@0: result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); michael@0: } michael@0: } michael@0: if (result != NULL) { michael@0: return extractDataByStyleEnum(*result, style, status); michael@0: } michael@0: michael@0: result = loadCDFLocaleData(inLocale, status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: { michael@0: Mutex lock(&gCompactDecimalMetaLock); michael@0: CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); michael@0: if (temp != NULL) { michael@0: delete result; michael@0: result = temp; michael@0: } else { michael@0: uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: } michael@0: } michael@0: return extractDataByStyleEnum(*result, style, status); michael@0: } michael@0: michael@0: static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) { michael@0: switch (style) { michael@0: case UNUM_SHORT: michael@0: return &data.shortData; michael@0: case UNUM_LONG: michael@0: if (!data.longData.isBogus()) { michael@0: return &data.longData; michael@0: } michael@0: return &data.shortData; michael@0: default: michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: // loadCDFLocaleData loads formatting data from CLDR for a given locale. The michael@0: // caller owns the returned pointer. michael@0: static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: CDFLocaleData* result = new CDFLocaleData; michael@0: if (result == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: result->Init(status); michael@0: if (U_FAILURE(status)) { michael@0: delete result; michael@0: return NULL; michael@0: } michael@0: michael@0: initCDFLocaleData(inLocale, result, status); michael@0: if (U_FAILURE(status)) { michael@0: delete result; michael@0: return NULL; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // initCDFLocaleData initializes result with data from CLDR. michael@0: // inLocale is the locale, the CLDR data is stored in result. michael@0: // We load the UNUM_SHORT and UNUM_LONG data looking first in local numbering michael@0: // system and not including root locale in fallback. Next we try in the latn michael@0: // numbering system where we fallback all the way to root. If we don't find michael@0: // UNUM_SHORT data in these three places, we report an error. If we find michael@0: // UNUM_SHORT data before finding UNUM_LONG data we make UNUM_LONG data fall michael@0: // back to UNUM_SHORT data. michael@0: static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) { michael@0: LocalPointer ns(NumberingSystem::createInstance(inLocale, status)); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: const char* numberingSystemName = ns->getName(); michael@0: UResourceBundle* rb = ures_open(NULL, inLocale.getName(), &status); michael@0: rb = ures_getByKeyWithFallback(rb, gNumberElementsTag, rb, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(rb); michael@0: return; michael@0: } michael@0: UResourceBundle* shortDataFillIn = NULL; michael@0: UResourceBundle* longDataFillIn = NULL; michael@0: UResourceBundle* shortData = NULL; michael@0: UResourceBundle* longData = NULL; michael@0: michael@0: if (uprv_strcmp(numberingSystemName, gLatnTag) != 0) { michael@0: LocalUResourceBundlePointer localResource( michael@0: tryGetByKeyWithFallback(rb, numberingSystemName, NULL, NOT_ROOT, status)); michael@0: shortData = tryGetDecimalFallback( michael@0: localResource.getAlias(), gPatternsShort, &shortDataFillIn, NOT_ROOT, status); michael@0: longData = tryGetDecimalFallback( michael@0: localResource.getAlias(), gPatternsLong, &longDataFillIn, NOT_ROOT, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: ures_close(shortDataFillIn); michael@0: ures_close(longDataFillIn); michael@0: ures_close(rb); michael@0: return; michael@0: } michael@0: michael@0: // If we haven't found UNUM_SHORT look in latn numbering system. We must michael@0: // succeed at finding UNUM_SHORT here. michael@0: if (shortData == NULL) { michael@0: LocalUResourceBundlePointer latnResource(tryGetByKeyWithFallback(rb, gLatnTag, NULL, MUST, status)); michael@0: shortData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsShort, &shortDataFillIn, MUST, status); michael@0: if (longData == NULL) { michael@0: longData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsLong, &longDataFillIn, ANY, status); michael@0: if (longData != NULL && isRoot(longData, status) && !isRoot(shortData, status)) { michael@0: longData = NULL; michael@0: } michael@0: } michael@0: } michael@0: initCDFLocaleStyleData(shortData, &result->shortData, status); michael@0: ures_close(shortDataFillIn); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(longDataFillIn); michael@0: ures_close(rb); michael@0: } michael@0: michael@0: if (longData == NULL) { michael@0: result->longData.setToBogus(); michael@0: } else { michael@0: initCDFLocaleStyleData(longData, &result->longData, status); michael@0: } michael@0: ures_close(longDataFillIn); michael@0: ures_close(rb); michael@0: } michael@0: michael@0: /** michael@0: * tryGetDecimalFallback attempts to fetch the "decimalFormat" resource bundle michael@0: * with a particular style. style is either "patternsShort" or "patternsLong." michael@0: * FillIn, flags, and status work in the same way as in tryGetByKeyWithFallback. michael@0: */ michael@0: static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { michael@0: UResourceBundle* first = tryGetByKeyWithFallback(numberSystemResource, style, fillIn, flags, status); michael@0: UResourceBundle* second = tryGetByKeyWithFallback(first, gDecimalFormatTag, fillIn, flags, status); michael@0: if (fillIn == NULL) { michael@0: ures_close(first); michael@0: } michael@0: return second; michael@0: } michael@0: michael@0: // tryGetByKeyWithFallback returns a sub-resource bundle that matches given michael@0: // criteria or NULL if none found. rb is the resource bundle that we are michael@0: // searching. If rb == NULL then this function behaves as if no sub-resource michael@0: // is found; path is the key of the sub-resource, michael@0: // (i.e "foo" but not "foo/bar"); If fillIn is NULL, caller must always call michael@0: // ures_close() on returned resource. See below for example when fillIn is michael@0: // not NULL. flags is ANY or NOT_ROOT. Optionally, these values michael@0: // can be ored with MUST. MUST by itself is the same as ANY | MUST. michael@0: // The locale of the returned sub-resource will either match the michael@0: // flags or the returned sub-resouce will be NULL. If MUST is included in michael@0: // flags, and not suitable sub-resource is found then in addition to returning michael@0: // NULL, this function also sets status to U_MISSING_RESOURCE_ERROR. If MUST michael@0: // is not included in flags, then this function just returns NULL if no michael@0: // such sub-resource is found and will never set status to michael@0: // U_MISSING_RESOURCE_ERROR. michael@0: // michael@0: // Example: This code first searches for "foo/bar" sub-resource without falling michael@0: // back to ROOT. Then searches for "baz" sub-resource as last resort. michael@0: // michael@0: // UResourcebundle* fillIn = NULL; michael@0: // UResourceBundle* data = tryGetByKeyWithFallback(rb, "foo", &fillIn, NON_ROOT, status); michael@0: // data = tryGetByKeyWithFallback(data, "bar", &fillIn, NON_ROOT, status); michael@0: // if (!data) { michael@0: // data = tryGetbyKeyWithFallback(rb, "baz", &fillIn, MUST, status); michael@0: // } michael@0: // if (U_FAILURE(status)) { michael@0: // ures_close(fillIn); michael@0: // return; michael@0: // } michael@0: // doStuffWithNonNullSubresource(data); michael@0: // michael@0: // /* Wrong! don't do the following as it can leak memory if fillIn gets set michael@0: // to NULL. */ michael@0: // fillIn = tryGetByKeyWithFallback(rb, "wrong", &fillIn, ANY, status); michael@0: // michael@0: // ures_close(fillIn); michael@0: // michael@0: static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: UBool must = (flags & MUST); michael@0: if (rb == NULL) { michael@0: if (must) { michael@0: status = U_MISSING_RESOURCE_ERROR; michael@0: } michael@0: return NULL; michael@0: } michael@0: UResourceBundle* result = NULL; michael@0: UResourceBundle* ownedByUs = NULL; michael@0: if (fillIn == NULL) { michael@0: ownedByUs = ures_getByKeyWithFallback(rb, path, NULL, &status); michael@0: result = ownedByUs; michael@0: } else { michael@0: *fillIn = ures_getByKeyWithFallback(rb, path, *fillIn, &status); michael@0: result = *fillIn; michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: ures_close(ownedByUs); michael@0: if (status == U_MISSING_RESOURCE_ERROR && !must) { michael@0: status = U_ZERO_ERROR; michael@0: } michael@0: return NULL; michael@0: } michael@0: flags = (FallbackFlags) (flags & ~MUST); michael@0: switch (flags) { michael@0: case NOT_ROOT: michael@0: { michael@0: UBool bRoot = isRoot(result, status); michael@0: if (bRoot || U_FAILURE(status)) { michael@0: ures_close(ownedByUs); michael@0: if (must && (status == U_ZERO_ERROR)) { michael@0: status = U_MISSING_RESOURCE_ERROR; michael@0: } michael@0: return NULL; michael@0: } michael@0: return result; michael@0: } michael@0: case ANY: michael@0: return result; michael@0: default: michael@0: ures_close(ownedByUs); michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: static UBool isRoot(const UResourceBundle* rb, UErrorCode& status) { michael@0: const char* actualLocale = ures_getLocaleByType( michael@0: rb, ULOC_ACTUAL_LOCALE, &status); michael@0: if (U_FAILURE(status)) { michael@0: return FALSE; michael@0: } michael@0: return uprv_strcmp(actualLocale, gRoot) == 0; michael@0: } michael@0: michael@0: michael@0: // initCDFLocaleStyleData loads formatting data for a particular style. michael@0: // decimalFormatBundle is the "decimalFormat" resource bundle in CLDR. michael@0: // Loaded data stored in result. michael@0: static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // Iterate through all the powers of 10. michael@0: int32_t size = ures_getSize(decimalFormatBundle); michael@0: UResourceBundle* power10 = NULL; michael@0: for (int32_t i = 0; i < size; ++i) { michael@0: power10 = ures_getByIndex(decimalFormatBundle, i, power10, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(power10); michael@0: return; michael@0: } michael@0: populatePower10(power10, result, status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(power10); michael@0: return; michael@0: } michael@0: } michael@0: ures_close(power10); michael@0: fillInMissing(result); michael@0: } michael@0: michael@0: // populatePower10 grabs data for a particular power of 10 from CLDR. michael@0: // The loaded data is stored in result. michael@0: static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: char* endPtr = NULL; michael@0: double power10 = uprv_strtod(ures_getKey(power10Bundle), &endPtr); michael@0: if (*endPtr != 0) { michael@0: status = U_INTERNAL_PROGRAM_ERROR; michael@0: return; michael@0: } michael@0: int32_t log10Value = computeLog10(power10, FALSE); michael@0: // Silently ignore divisors that are too big. michael@0: if (log10Value == MAX_DIGITS) { michael@0: return; michael@0: } michael@0: int32_t size = ures_getSize(power10Bundle); michael@0: int32_t numZeros = 0; michael@0: UBool otherVariantDefined = FALSE; michael@0: UResourceBundle* variantBundle = NULL; michael@0: // Iterate over all the plural variants for the power of 10 michael@0: for (int32_t i = 0; i < size; ++i) { michael@0: variantBundle = ures_getByIndex(power10Bundle, i, variantBundle, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(variantBundle); michael@0: return; michael@0: } michael@0: const char* variant = ures_getKey(variantBundle); michael@0: int32_t resLen; michael@0: const UChar* formatStrP = ures_getString(variantBundle, &resLen, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(variantBundle); michael@0: return; michael@0: } michael@0: UnicodeString formatStr(false, formatStrP, resLen); michael@0: if (uprv_strcmp(variant, gOther) == 0) { michael@0: otherVariantDefined = TRUE; michael@0: } michael@0: int32_t nz = populatePrefixSuffix( michael@0: variant, log10Value, formatStr, result->unitsByVariant, status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(variantBundle); michael@0: return; michael@0: } michael@0: if (nz != numZeros) { michael@0: // We expect all format strings to have the same number of 0's michael@0: // left of the decimal point. michael@0: if (numZeros != 0) { michael@0: status = U_INTERNAL_PROGRAM_ERROR; michael@0: ures_close(variantBundle); michael@0: return; michael@0: } michael@0: numZeros = nz; michael@0: } michael@0: } michael@0: ures_close(variantBundle); michael@0: // We expect to find an OTHER variant for each power of 10. michael@0: if (!otherVariantDefined) { michael@0: status = U_INTERNAL_PROGRAM_ERROR; michael@0: return; michael@0: } michael@0: double divisor = power10; michael@0: for (int32_t i = 1; i < numZeros; ++i) { michael@0: divisor /= 10.0; michael@0: } michael@0: result->divisors[log10Value] = divisor; michael@0: } michael@0: michael@0: // populatePrefixSuffix Adds a specific prefix-suffix pair to result for a michael@0: // given variant and log10 value. michael@0: // variant is 'zero', 'one', 'two', 'few', 'many', or 'other'. michael@0: // formatStr is the format string from which the prefix and suffix are michael@0: // extracted. It is usually of form 'Pefix 000 suffix'. michael@0: // populatePrefixSuffix returns the number of 0's found in formatStr michael@0: // before the decimal point. michael@0: // In the special case that formatStr contains only spaces for prefix michael@0: // and suffix, populatePrefixSuffix returns log10Value + 1. michael@0: static int32_t populatePrefixSuffix( michael@0: const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: int32_t firstIdx = formatStr.indexOf(kZero, LENGTHOF(kZero), 0); michael@0: // We must have 0's in format string. michael@0: if (firstIdx == -1) { michael@0: status = U_INTERNAL_PROGRAM_ERROR; michael@0: return 0; michael@0: } michael@0: int32_t lastIdx = formatStr.lastIndexOf(kZero, LENGTHOF(kZero), firstIdx); michael@0: CDFUnit* unit = createCDFUnit(variant, log10Value, result, status); michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: // Everything up to first 0 is the prefix michael@0: unit->prefix = formatStr.tempSubString(0, firstIdx); michael@0: fixQuotes(unit->prefix); michael@0: // Everything beyond the last 0 is the suffix michael@0: unit->suffix = formatStr.tempSubString(lastIdx + 1); michael@0: fixQuotes(unit->suffix); michael@0: michael@0: // If there is effectively no prefix or suffix, ignore the actual number of michael@0: // 0's and act as if the number of 0's matches the size of the number. michael@0: if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) { michael@0: return log10Value + 1; michael@0: } michael@0: michael@0: // Calculate number of zeros before decimal point michael@0: int32_t idx = firstIdx + 1; michael@0: while (idx <= lastIdx && formatStr.charAt(idx) == u_0) { michael@0: ++idx; michael@0: } michael@0: return (idx - firstIdx); michael@0: } michael@0: michael@0: static UBool onlySpaces(UnicodeString u) { michael@0: return u.trim().length() == 0; michael@0: } michael@0: michael@0: // fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j. michael@0: // Modifies s in place. michael@0: static void fixQuotes(UnicodeString& s) { michael@0: QuoteState state = OUTSIDE; michael@0: int32_t len = s.length(); michael@0: int32_t dest = 0; michael@0: for (int32_t i = 0; i < len; ++i) { michael@0: UChar ch = s.charAt(i); michael@0: if (ch == u_apos) { michael@0: if (state == INSIDE_EMPTY) { michael@0: s.setCharAt(dest, ch); michael@0: ++dest; michael@0: } michael@0: } else { michael@0: s.setCharAt(dest, ch); michael@0: ++dest; michael@0: } michael@0: michael@0: // Update state michael@0: switch (state) { michael@0: case OUTSIDE: michael@0: state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE; michael@0: break; michael@0: case INSIDE_EMPTY: michael@0: case INSIDE_FULL: michael@0: state = ch == u_apos ? OUTSIDE : INSIDE_FULL; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: s.truncate(dest); michael@0: } michael@0: michael@0: // fillInMissing ensures that the data in result is complete. michael@0: // result data is complete if for each variant in result, there exists michael@0: // a prefix-suffix pair for each log10 value and there also exists michael@0: // a divisor for each log10 value. michael@0: // michael@0: // First this function figures out for which log10 values, the other michael@0: // variant already had data. These are the same log10 values defined michael@0: // in CLDR. michael@0: // michael@0: // For each log10 value not defined in CLDR, it uses the divisor for michael@0: // the last defined log10 value or 1. michael@0: // michael@0: // Then for each variant, it does the following. For each log10 michael@0: // value not defined in CLDR, copy the prefix-suffix pair from the michael@0: // previous log10 value. If log10 value is defined in CLDR but is michael@0: // missing from given variant, copy the prefix-suffix pair for that michael@0: // log10 value from the 'other' variant. michael@0: static void fillInMissing(CDFLocaleStyleData* result) { michael@0: const CDFUnit* otherUnits = michael@0: (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); michael@0: UBool definedInCLDR[MAX_DIGITS]; michael@0: double lastDivisor = 1.0; michael@0: for (int32_t i = 0; i < MAX_DIGITS; ++i) { michael@0: if (!otherUnits[i].isSet()) { michael@0: result->divisors[i] = lastDivisor; michael@0: definedInCLDR[i] = FALSE; michael@0: } else { michael@0: lastDivisor = result->divisors[i]; michael@0: definedInCLDR[i] = TRUE; michael@0: } michael@0: } michael@0: // Iterate over each variant. michael@0: int32_t pos = -1; michael@0: const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos); michael@0: for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) { michael@0: CDFUnit* units = (CDFUnit*) element->value.pointer; michael@0: for (int32_t i = 0; i < MAX_DIGITS; ++i) { michael@0: if (definedInCLDR[i]) { michael@0: if (!units[i].isSet()) { michael@0: units[i] = otherUnits[i]; michael@0: } michael@0: } else { michael@0: if (i == 0) { michael@0: units[0].markAsSet(); michael@0: } else { michael@0: units[i] = units[i - 1]; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest michael@0: // value computeLog10 will return MAX_DIGITS -1 even for michael@0: // numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return michael@0: // up to MAX_DIGITS. michael@0: static int32_t computeLog10(double x, UBool inRange) { michael@0: int32_t result = 0; michael@0: int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS; michael@0: while (x >= 10.0) { michael@0: x /= 10.0; michael@0: ++result; michael@0: if (result == max) { michael@0: break; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // createCDFUnit returns a pointer to the prefix-suffix pair for a given michael@0: // variant and log10 value within table. If no such prefix-suffix pair is michael@0: // stored in table, one is created within table before returning pointer. michael@0: static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant); michael@0: if (cdfUnit == NULL) { michael@0: cdfUnit = new CDFUnit[MAX_DIGITS]; michael@0: if (cdfUnit == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: uhash_put(table, uprv_strdup(variant), cdfUnit, &status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: } michael@0: CDFUnit* result = &cdfUnit[log10Value]; michael@0: result->markAsSet(); michael@0: return result; michael@0: } michael@0: michael@0: // getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given michael@0: // variant and log10 value within table. If the given variant doesn't exist, it michael@0: // falls back to the OTHER variant. Therefore, this method will always return michael@0: // some non-NULL value. michael@0: static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) { michael@0: CharString cvariant; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: const CDFUnit *cdfUnit = NULL; michael@0: cvariant.appendInvariantChars(variant, status); michael@0: if (!U_FAILURE(status)) { michael@0: cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data()); michael@0: } michael@0: if (cdfUnit == NULL) { michael@0: cdfUnit = (const CDFUnit*) uhash_get(table, gOther); michael@0: } michael@0: return &cdfUnit[log10Value]; michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: #endif