- 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;
- }
-
- load(inLocale, result, status);
-
- if (U_FAILURE(status)) {
- delete result;
- return NULL;
- }
- return result;
-}
-
-namespace {
-
-struct CmptDecDataSink : public ResourceSink {
-
- CDFLocaleData& dataBundle; // Where to save values when they are read
- UBool isLatin; // Whether or not we are traversing the Latin tree
- UBool isFallback; // Whether or not we are traversing the Latin tree as fallback
-
- enum EPatternsTableKey { PATTERNS_SHORT, PATTERNS_LONG };
- enum EFormatsTableKey { DECIMAL_FORMAT, CURRENCY_FORMAT };
-
- /*
- * NumberElements{ <-- top (numbering system table)
- * latn{ <-- patternsTable (one per numbering system)
- * patternsLong{ <-- formatsTable (one per pattern)
- * decimalFormat{ <-- powersOfTenTable (one per format)
- * 1000{ <-- pluralVariantsTable (one per power of ten)
- * one{"0 thousand"} <-- plural variant and template
- */
-
- CmptDecDataSink(CDFLocaleData& _dataBundle)
- : dataBundle(_dataBundle), isLatin(FALSE), isFallback(FALSE) {}
- virtual ~CmptDecDataSink();
-
- virtual void put(const char *key, ResourceValue &value, UBool isRoot, UErrorCode &errorCode) {
- // SPECIAL CASE: Don't consume root in the non-Latin numbering system
- if (isRoot && !isLatin) { return; }
-
- ResourceTable patternsTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) {
-
- // Check for patternsShort or patternsLong
- EPatternsTableKey patternsTableKey;
- if (uprv_strcmp(key, gPatternsShort) == 0) {
- patternsTableKey = PATTERNS_SHORT;
- } else if (uprv_strcmp(key, gPatternsLong) == 0) {
- patternsTableKey = PATTERNS_LONG;
- } else {
- continue;
- }
-
- // Traverse into the formats table
- ResourceTable formatsTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) {
-
- // Check for decimalFormat or currencyFormat
- EFormatsTableKey formatsTableKey;
- if (uprv_strcmp(key, gDecimalFormatTag) == 0) {
- formatsTableKey = DECIMAL_FORMAT;
- // TODO: Enable this statement when currency support is added
- // } else if (uprv_strcmp(key, gCurrencyFormat) == 0) {
- // formatsTableKey = CURRENCY_FORMAT;
- } else {
- continue;
- }
-
- // Set the current style and destination based on the two keys
- UNumberCompactStyle style;
- CDFLocaleStyleData* destination = NULL;
- if (patternsTableKey == PATTERNS_LONG
- && formatsTableKey == DECIMAL_FORMAT) {
- style = UNUM_LONG;
- destination = &dataBundle.longData;
- } else if (patternsTableKey == PATTERNS_SHORT
- && formatsTableKey == DECIMAL_FORMAT) {
- style = UNUM_SHORT;
- destination = &dataBundle.shortData;
- // TODO: Enable the following statements when currency support is added
- // } else if (patternsTableKey == PATTERNS_SHORT
- // && formatsTableKey == CURRENCY_FORMAT) {
- // style = UNUM_SHORT_CURRENCY; // or whatever the enum gets named
- // destination = &dataBundle.shortCurrencyData;
- // } else {
- // // Silently ignore this case
- // continue;
- }
-
- // SPECIAL CASE: RULES FOR WHETHER OR NOT TO CONSUME THIS TABLE:
- // 1) Don't consume longData if shortData was consumed from the non-Latin
- // locale numbering system
- // 2) Don't consume longData for the first time if this is the root bundle and
- // shortData is already populated from a more specific locale. Note that if
- // both longData and shortData are both only in root, longData will be
- // consumed since it is alphabetically before shortData in the bundle.
- if (isFallback
- && style == UNUM_LONG
- && !dataBundle.shortData.isEmpty()
- && !dataBundle.shortData.fromFallback) {
- continue;
- }
- if (isRoot
- && style == UNUM_LONG
- && dataBundle.longData.isEmpty()
- && !dataBundle.shortData.isEmpty()) {
- continue;
- }
-
- // Set the "fromFallback" flag on the data object
- destination->fromFallback = isFallback;
-
- // Traverse into the powers of ten table
- ResourceTable powersOfTenTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
-
- // The key will always be some even power of 10. e.g 10000.
- char* endPtr = NULL;
- double power10 = uprv_strtod(key, &endPtr);
- if (*endPtr != 0) {
- errorCode = U_INTERNAL_PROGRAM_ERROR;
- return;
- }
- int32_t log10Value = computeLog10(power10, FALSE);
-
- // Silently ignore divisors that are too big.
- if (log10Value >= MAX_DIGITS) continue;
-
- // Iterate over the plural variants ("one", "other", etc)
- ResourceTable pluralVariantsTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
- const char* pluralVariant = key;
- const UnicodeString formatStr = value.getUnicodeString(errorCode);
-
- // Copy the data into the in-memory data bundle (do not overwrite
- // existing values)
- int32_t numZeros = populatePrefixSuffix(
- pluralVariant, log10Value, formatStr,
- destination->unitsByVariant, FALSE, errorCode);
-
- // If populatePrefixSuffix returns -1, it means that this key has been
- // encountered already.
- if (numZeros < 0) {
- continue;
- }
-
- // Set the divisor, which is based on the number of zeros in the template
- // string. If the divisor from here is different from the one previously
- // stored, it means that the number of zeros in different plural variants
- // differs; throw an exception.
- // TODO: How should I check for floating-point errors here?
- // Is there a good reason why "divisor" is double and not long like Java?
- double divisor = calculateDivisor(power10, numZeros);
- if (destination->divisors[log10Value] != 0.0
- && destination->divisors[log10Value] != divisor) {
- errorCode = U_INTERNAL_PROGRAM_ERROR;
- return;
- }
- destination->divisors[log10Value] = divisor;
- }
- }
- }
- }
- }
-};
-
-// Virtual destructors must be defined out of line.
-CmptDecDataSink::~CmptDecDataSink() {}
-
-} // namespace
-
-static void load(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) {
- LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(inLocale, status));
- if (U_FAILURE(status)) {
- return;
- }
- const char* nsName = ns->getName();
-
- LocalUResourceBundlePointer resource(ures_open(NULL, inLocale.getName(), &status));
- if (U_FAILURE(status)) {
- return;
- }
- CmptDecDataSink sink(*result);
- sink.isFallback = FALSE;
-
- // First load the number elements data if nsName is not Latin.
- if (uprv_strcmp(nsName, gLatnTag) != 0) {
- sink.isLatin = FALSE;
- CharString path;
- path.append(gNumberElementsTag, status)
- .append('/', status)
- .append(nsName, status);
- ures_getAllItemsWithFallback(resource.getAlias(), path.data(), sink, status);
- if (status == U_MISSING_RESOURCE_ERROR) {
- // Silently ignore and use Latin
- status = U_ZERO_ERROR;
- } else if (U_FAILURE(status)) {
- return;
- }
- sink.isFallback = TRUE;
- }
-
- // Now load Latin.
- sink.isLatin = TRUE;
- ures_getAllItemsWithFallback(resource.getAlias(), gLatnPath, sink, status);
- if (U_FAILURE(status)) return;
-
- // If longData is empty, default it to be equal to shortData
- if (result->longData.isEmpty()) {
- result->longData.setToBogus();
- }
-
- // Check for "other" variants in each of the three data classes, and resolve missing elements.
-
- if (!result->longData.isBogus()) {
- checkForOtherVariants(&result->longData, status);
- if (U_FAILURE(status)) return;
- fillInMissing(&result->longData);
- }
-
- checkForOtherVariants(&result->shortData, status);
- if (U_FAILURE(status)) return;
- fillInMissing(&result->shortData);
-
- // TODO: Enable this statement when currency support is added
- // checkForOtherVariants(&result->shortCurrencyData, status);
- // if (U_FAILURE(status)) return;
- // fillInMissing(&result->shortCurrencyData);
-}
-
-// 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, UBool overwrite, UErrorCode& status) {
- if (U_FAILURE(status)) {
- return 0;
- }
-
- // ICU 59 HACK: Ignore negative part of format string, mimicking ICU 58 behavior.
- // TODO(sffc): Make sure this is fixed during the overhaul port in ICU 60.
- int32_t semiPos = formatStr.indexOf(';', 0);
- if (semiPos == -1) {
- semiPos = formatStr.length();
- }
- UnicodeString positivePart = formatStr.tempSubString(0, semiPos);
-
- int32_t firstIdx = positivePart.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 = positivePart.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx);
- CDFUnit* unit = createCDFUnit(variant, log10Value, result, status);
- if (U_FAILURE(status)) {
- return 0;
- }
-
- // Return -1 if we are not overwriting an existing value
- if (unit->isSet() && !overwrite) {
- return -1;
- }
- unit->markAsSet();
-
- // Everything up to first 0 is the prefix
- unit->prefix = positivePart.tempSubString(0, firstIdx);
- fixQuotes(unit->prefix);
- // Everything beyond the last 0 is the suffix
- unit->suffix = positivePart.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 && positivePart.charAt(idx) == u_0) {
- ++idx;
- }
- return (idx - firstIdx);
-}
-
-// Calculate a divisor based on the magnitude and number of zeros in the
-// template string.
-static double calculateDivisor(double power10, int32_t numZeros) {
- double divisor = power10;
- for (int32_t i = 1; i < numZeros; ++i) {
- divisor /= 10.0;
- }
- return divisor;
-}
-
-static UBool onlySpaces(UnicodeString u) {
- return u.trim().length() == 0;