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