summaryrefslogtreecommitdiff
path: root/deps/icu-small/source/i18n/compactdecimalformat.cpp
diff options
context:
space:
mode:
authorSteven R. Loomis <srloomis@us.ibm.com>2016-04-08 19:03:16 -0700
committerSteven R. Loomis <srloomis@us.ibm.com>2016-05-04 16:02:45 -0700
commit2bbd1cd6004b3e1467e30d860385a85dad01fe24 (patch)
treeb812046e89e46e0de09bc858e0b128787cbc0632 /deps/icu-small/source/i18n/compactdecimalformat.cpp
parentcd752e8463fad7c4805951d9ba47cd2f39691f2d (diff)
downloadandroid-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.cpp999
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