intl/icu/source/i18n/compactdecimalformat.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /*
     2 *******************************************************************************
     3 * Copyright (C) 1997-2012, International Business Machines Corporation and    *
     4 * others. All Rights Reserved.                                                *
     5 *******************************************************************************
     6 *
     7 * File COMPACTDECIMALFORMAT.CPP
     8 *
     9 ********************************************************************************
    10 */
    11 #include "unicode/utypes.h"
    13 #if !UCONFIG_NO_FORMATTING
    15 #include "charstr.h"
    16 #include "cstring.h"
    17 #include "digitlst.h"
    18 #include "mutex.h"
    19 #include "unicode/compactdecimalformat.h"
    20 #include "unicode/numsys.h"
    21 #include "unicode/plurrule.h"
    22 #include "unicode/ures.h"
    23 #include "ucln_in.h"
    24 #include "uhash.h"
    25 #include "umutex.h"
    26 #include "unicode/ures.h"
    27 #include "uresimp.h"
    29 #define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0]))
    31 // Maps locale name to CDFLocaleData struct.
    32 static UHashtable* gCompactDecimalData = NULL;
    33 static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER;
    35 U_NAMESPACE_BEGIN
    37 static const int32_t MAX_DIGITS = 15;
    38 static const char gOther[] = "other";
    39 static const char gLatnTag[] = "latn";
    40 static const char gNumberElementsTag[] = "NumberElements";
    41 static const char gDecimalFormatTag[] = "decimalFormat";
    42 static const char gPatternsShort[] = "patternsShort";
    43 static const char gPatternsLong[] = "patternsLong";
    44 static const char gRoot[] = "root";
    46 static const UChar u_0 = 0x30;
    47 static const UChar u_apos = 0x27;
    49 static const UChar kZero[] = {u_0};
    51 // Used to unescape single quotes.
    52 enum QuoteState {
    53   OUTSIDE,
    54   INSIDE_EMPTY,
    55   INSIDE_FULL
    56 };
    58 enum FallbackFlags {
    59   ANY = 0,
    60   MUST = 1,
    61   NOT_ROOT = 2
    62   // Next one will be 4 then 6 etc.
    63 };
    66 // CDFUnit represents a prefix-suffix pair for a particular variant
    67 // and log10 value.
    68 struct CDFUnit : public UMemory {
    69   UnicodeString prefix;
    70   UnicodeString suffix;
    71   inline CDFUnit() : prefix(), suffix() {
    72     prefix.setToBogus();
    73   }
    74   inline ~CDFUnit() {}
    75   inline UBool isSet() const {
    76     return !prefix.isBogus();
    77   }
    78   inline void markAsSet() {
    79     prefix.remove();
    80   }
    81 };
    83 // CDFLocaleStyleData contains formatting data for a particular locale
    84 // and style.
    85 class CDFLocaleStyleData : public UMemory {
    86  public:
    87   // What to divide by for each log10 value when formatting. These values
    88   // will be powers of 10. For English, would be:
    89   // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ...
    90   double divisors[MAX_DIGITS];
    91   // Maps plural variants to CDFUnit[MAX_DIGITS] arrays.
    92   // To format a number x,
    93   // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]).
    94   // Compute the plural variant for displayNum
    95   // (e.g zero, one, two, few, many, other).
    96   // Compute cdfUnits = unitsByVariant[pluralVariant].
    97   // Prefix and suffix to use at cdfUnits[log10(x)]
    98   UHashtable* unitsByVariant;
    99   inline CDFLocaleStyleData() : unitsByVariant(NULL) {}
   100   ~CDFLocaleStyleData();
   101   // Init initializes this object.
   102   void Init(UErrorCode& status);
   103   inline UBool isBogus() const {
   104     return unitsByVariant == NULL;
   105   }
   106   void setToBogus();
   107  private:
   108   CDFLocaleStyleData(const CDFLocaleStyleData&);
   109   CDFLocaleStyleData& operator=(const CDFLocaleStyleData&);
   110 };
   112 // CDFLocaleData contains formatting data for a particular locale.
   113 struct CDFLocaleData : public UMemory {
   114   CDFLocaleStyleData shortData;
   115   CDFLocaleStyleData longData;
   116   inline CDFLocaleData() : shortData(), longData() { }
   117   inline ~CDFLocaleData() { }
   118   // Init initializes this object.
   119   void Init(UErrorCode& status);
   120 };
   122 U_NAMESPACE_END
   124 U_CDECL_BEGIN
   126 static UBool U_CALLCONV cdf_cleanup(void) {
   127   if (gCompactDecimalData != NULL) {
   128     uhash_close(gCompactDecimalData);
   129     gCompactDecimalData = NULL;
   130   }
   131   return TRUE;
   132 }
   134 static void U_CALLCONV deleteCDFUnits(void* ptr) {
   135   delete [] (icu::CDFUnit*) ptr;
   136 }
   138 static void U_CALLCONV deleteCDFLocaleData(void* ptr) {
   139   delete (icu::CDFLocaleData*) ptr;
   140 }
   142 U_CDECL_END
   144 U_NAMESPACE_BEGIN
   146 static UBool divisors_equal(const double* lhs, const double* rhs);
   147 static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status);
   149 static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status);
   150 static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status);
   151 static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status);
   152 static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status);
   153 static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status);
   154 static UBool isRoot(const UResourceBundle* rb, UErrorCode& status);
   155 static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status);
   156 static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status);
   157 static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status);
   158 static UBool onlySpaces(UnicodeString u);
   159 static void fixQuotes(UnicodeString& s);
   160 static void fillInMissing(CDFLocaleStyleData* result);
   161 static int32_t computeLog10(double x, UBool inRange);
   162 static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status);
   163 static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value);
   165 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat)
   167 CompactDecimalFormat::CompactDecimalFormat(
   168     const DecimalFormat& decimalFormat,
   169     const UHashtable* unitsByVariant,
   170     const double* divisors,
   171     PluralRules* pluralRules)
   172   : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) {
   173 }
   175 CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source)
   176     : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) {
   177 }
   179 CompactDecimalFormat* U_EXPORT2
   180 CompactDecimalFormat::createInstance(
   181     const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) {
   182   LocalPointer<DecimalFormat> decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status));
   183   if (U_FAILURE(status)) {
   184     return NULL;
   185   }
   186   LocalPointer<PluralRules> pluralRules(PluralRules::forLocale(inLocale, status));
   187   if (U_FAILURE(status)) {
   188     return NULL;
   189   }
   190   const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status);
   191   if (U_FAILURE(status)) {
   192     return NULL;
   193   }
   194   CompactDecimalFormat* result =
   195       new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias());
   196   if (result == NULL) {
   197     status = U_MEMORY_ALLOCATION_ERROR;
   198     return NULL;
   199   }
   200   pluralRules.orphan();
   201   result->setMaximumSignificantDigits(3);
   202   result->setSignificantDigitsUsed(TRUE);
   203   result->setGroupingUsed(FALSE);
   204   return result;
   205 }
   207 CompactDecimalFormat&
   208 CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) {
   209   if (this != &rhs) {
   210     DecimalFormat::operator=(rhs);
   211     _unitsByVariant = rhs._unitsByVariant;
   212     _divisors = rhs._divisors;
   213     delete _pluralRules;
   214     _pluralRules = rhs._pluralRules->clone();
   215   }
   216   return *this;
   217 }
   219 CompactDecimalFormat::~CompactDecimalFormat() {
   220   delete _pluralRules;
   221 }
   224 Format*
   225 CompactDecimalFormat::clone(void) const {
   226   return new CompactDecimalFormat(*this);
   227 }
   229 UBool
   230 CompactDecimalFormat::operator==(const Format& that) const {
   231   if (this == &that) {
   232     return TRUE;
   233   }
   234   return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that));
   235 }
   237 UBool
   238 CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const {
   239   return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules);
   240 }
   242 UnicodeString&
   243 CompactDecimalFormat::format(
   244     double number,
   245     UnicodeString& appendTo,
   246     FieldPosition& pos) const {
   247   DigitList orig, rounded;
   248   orig.set(number);
   249   UBool isNegative;
   250   UErrorCode status = U_ZERO_ERROR;
   251   _round(orig, rounded, isNegative, status);
   252   if (U_FAILURE(status)) {
   253     return appendTo;
   254   }
   255   double roundedDouble = rounded.getDouble();
   256   if (isNegative) {
   257     roundedDouble = -roundedDouble;
   258   }
   259   int32_t baseIdx = computeLog10(roundedDouble, TRUE);
   260   double numberToFormat = roundedDouble / _divisors[baseIdx];
   261   UnicodeString variant = _pluralRules->select(numberToFormat);
   262   if (isNegative) {
   263     numberToFormat = -numberToFormat;
   264   }
   265   const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx);
   266   appendTo += unit->prefix;
   267   DecimalFormat::format(numberToFormat, appendTo, pos);
   268   appendTo += unit->suffix;
   269   return appendTo;
   270 }
   272 UnicodeString&
   273 CompactDecimalFormat::format(
   274     double /* number */,
   275     UnicodeString& appendTo,
   276     FieldPositionIterator* /* posIter */,
   277     UErrorCode& status) const {
   278   status = U_UNSUPPORTED_ERROR;
   279   return appendTo;
   280 }
   282 UnicodeString&
   283 CompactDecimalFormat::format(
   284     int64_t number,
   285     UnicodeString& appendTo,
   286     FieldPosition& pos) const {
   287   return format((double) number, appendTo, pos);
   288 }
   290 UnicodeString&
   291 CompactDecimalFormat::format(
   292     int64_t /* number */,
   293     UnicodeString& appendTo,
   294     FieldPositionIterator* /* posIter */,
   295     UErrorCode& status) const {
   296   status = U_UNSUPPORTED_ERROR;
   297   return appendTo;
   298 }
   300 UnicodeString&
   301 CompactDecimalFormat::format(
   302     const StringPiece& /* number */,
   303     UnicodeString& appendTo,
   304     FieldPositionIterator* /* posIter */,
   305     UErrorCode& status) const {
   306   status = U_UNSUPPORTED_ERROR;
   307   return appendTo;
   308 }
   310 UnicodeString&
   311 CompactDecimalFormat::format(
   312     const DigitList& /* number */,
   313     UnicodeString& appendTo,
   314     FieldPositionIterator* /* posIter */,
   315     UErrorCode& status) const {
   316   status = U_UNSUPPORTED_ERROR;
   317   return appendTo;
   318 }
   320 UnicodeString&
   321 CompactDecimalFormat::format(const DigitList& /* number */,
   322                              UnicodeString& appendTo,
   323                              FieldPosition& /* pos */,
   324                              UErrorCode& status) const {
   325   status = U_UNSUPPORTED_ERROR;
   326   return appendTo;
   327 }
   329 void
   330 CompactDecimalFormat::parse(
   331     const UnicodeString& /* text */,
   332     Formattable& /* result */,
   333     ParsePosition& /* parsePosition */) const {
   334 }
   336 void
   337 CompactDecimalFormat::parse(
   338     const UnicodeString& /* text */,
   339     Formattable& /* result */,
   340     UErrorCode& status) const {
   341   status = U_UNSUPPORTED_ERROR;
   342 }
   344 CurrencyAmount*
   345 CompactDecimalFormat::parseCurrency(
   346     const UnicodeString& /* text */,
   347     ParsePosition& /* pos */) const {
   348   return NULL;
   349 }
   351 void CDFLocaleStyleData::Init(UErrorCode& status) {
   352   if (unitsByVariant != NULL) {
   353     return;
   354   }
   355   unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
   356   if (U_FAILURE(status)) {
   357     return;
   358   }
   359   uhash_setKeyDeleter(unitsByVariant, uprv_free);
   360   uhash_setValueDeleter(unitsByVariant, deleteCDFUnits);
   361 }
   363 CDFLocaleStyleData::~CDFLocaleStyleData() {
   364   setToBogus();
   365 }
   367 void CDFLocaleStyleData::setToBogus() {
   368   if (unitsByVariant != NULL) {
   369     uhash_close(unitsByVariant);
   370     unitsByVariant = NULL;
   371   }
   372 }
   374 void CDFLocaleData::Init(UErrorCode& status) {
   375   shortData.Init(status);
   376   if (U_FAILURE(status)) {
   377     return;
   378   }
   379   longData.Init(status);
   380 }
   382 // Helper method for operator=
   383 static UBool divisors_equal(const double* lhs, const double* rhs) {
   384   for (int32_t i = 0; i < MAX_DIGITS; ++i) {
   385     if (lhs[i] != rhs[i]) {
   386       return FALSE;
   387     }
   388   }
   389   return TRUE;
   390 }
   392 // getCDFLocaleStyleData returns pointer to formatting data for given locale and 
   393 // style within the global cache. On cache miss, getCDFLocaleStyleData loads
   394 // the data from CLDR into the global cache before returning the pointer. If a
   395 // UNUM_LONG data is requested for a locale, and that locale does not have
   396 // UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for
   397 // that locale.
   398 static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) {
   399   if (U_FAILURE(status)) {
   400     return NULL;
   401   }
   402   CDFLocaleData* result = NULL;
   403   const char* key = inLocale.getName();
   404   {
   405     Mutex lock(&gCompactDecimalMetaLock);
   406     if (gCompactDecimalData == NULL) {
   407       gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
   408       if (U_FAILURE(status)) {
   409         return NULL;
   410       }
   411       uhash_setKeyDeleter(gCompactDecimalData, uprv_free);
   412       uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData);
   413       ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup);
   414     } else {
   415       result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key);
   416     }
   417   }
   418   if (result != NULL) {
   419     return extractDataByStyleEnum(*result, style, status);
   420   }
   422   result = loadCDFLocaleData(inLocale, status);
   423   if (U_FAILURE(status)) {
   424     return NULL;
   425   }
   427   {
   428     Mutex lock(&gCompactDecimalMetaLock);
   429     CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key);
   430     if (temp != NULL) {
   431       delete result;
   432       result = temp;
   433     } else {
   434       uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status);
   435       if (U_FAILURE(status)) {
   436         return NULL;
   437       }
   438     }
   439   }
   440   return extractDataByStyleEnum(*result, style, status);
   441 }
   443 static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) {
   444   switch (style) {
   445     case UNUM_SHORT:
   446       return &data.shortData;
   447     case UNUM_LONG:
   448       if (!data.longData.isBogus()) {
   449         return &data.longData;
   450       }
   451       return &data.shortData;
   452     default:
   453       status = U_ILLEGAL_ARGUMENT_ERROR;
   454       return NULL;
   455   }
   456 }
   458 // loadCDFLocaleData loads formatting data from CLDR for a given locale. The
   459 // caller owns the returned pointer.
   460 static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) {
   461   if (U_FAILURE(status)) {
   462     return NULL;
   463   }
   464   CDFLocaleData* result = new CDFLocaleData;
   465   if (result == NULL) {
   466     status = U_MEMORY_ALLOCATION_ERROR;
   467     return NULL;
   468   }
   469   result->Init(status);
   470   if (U_FAILURE(status)) {
   471     delete result;
   472     return NULL;
   473   }
   475   initCDFLocaleData(inLocale, result, status);
   476   if (U_FAILURE(status)) {
   477     delete result;
   478     return NULL;
   479   }
   480   return result;
   481 }
   483 // initCDFLocaleData initializes result with data from CLDR.
   484 // inLocale is the locale, the CLDR data is stored in result.
   485 // We load the UNUM_SHORT  and UNUM_LONG data looking first in local numbering
   486 // system and not including root locale in fallback. Next we try in the latn
   487 // numbering system where we fallback all the way to root. If we don't find
   488 // UNUM_SHORT data in these three places, we report an error. If we find
   489 // UNUM_SHORT data before finding UNUM_LONG data we make UNUM_LONG data fall
   490 // back to UNUM_SHORT data.
   491 static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) {
   492   LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(inLocale, status));
   493   if (U_FAILURE(status)) {
   494     return;
   495   }
   496   const char* numberingSystemName = ns->getName();
   497   UResourceBundle* rb = ures_open(NULL, inLocale.getName(), &status);
   498   rb = ures_getByKeyWithFallback(rb, gNumberElementsTag, rb, &status);
   499   if (U_FAILURE(status)) {
   500     ures_close(rb);
   501     return;
   502   }
   503   UResourceBundle* shortDataFillIn = NULL;
   504   UResourceBundle* longDataFillIn = NULL;
   505   UResourceBundle* shortData = NULL;
   506   UResourceBundle* longData = NULL;
   508   if (uprv_strcmp(numberingSystemName, gLatnTag) != 0) {
   509     LocalUResourceBundlePointer localResource(
   510         tryGetByKeyWithFallback(rb, numberingSystemName, NULL, NOT_ROOT, status));
   511     shortData = tryGetDecimalFallback(
   512         localResource.getAlias(), gPatternsShort, &shortDataFillIn, NOT_ROOT, status);
   513     longData = tryGetDecimalFallback(
   514         localResource.getAlias(), gPatternsLong, &longDataFillIn, NOT_ROOT, status);
   515   }
   516   if (U_FAILURE(status)) {
   517     ures_close(shortDataFillIn);
   518     ures_close(longDataFillIn);
   519     ures_close(rb);
   520     return;
   521   }
   523   // If we haven't found UNUM_SHORT look in latn numbering system. We must
   524   // succeed at finding UNUM_SHORT here.
   525   if (shortData == NULL) {
   526     LocalUResourceBundlePointer latnResource(tryGetByKeyWithFallback(rb, gLatnTag, NULL, MUST, status));
   527     shortData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsShort, &shortDataFillIn, MUST, status);
   528     if (longData == NULL) {
   529       longData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsLong, &longDataFillIn, ANY, status);
   530       if (longData != NULL && isRoot(longData, status) && !isRoot(shortData, status)) {
   531         longData = NULL;
   532       }
   533     }
   534   }
   535   initCDFLocaleStyleData(shortData, &result->shortData, status);
   536   ures_close(shortDataFillIn);
   537   if (U_FAILURE(status)) {
   538     ures_close(longDataFillIn);
   539     ures_close(rb);
   540   }
   542   if (longData == NULL) {
   543     result->longData.setToBogus();
   544   } else {
   545     initCDFLocaleStyleData(longData, &result->longData, status);
   546   }
   547   ures_close(longDataFillIn);
   548   ures_close(rb);
   549 }
   551 /**
   552  * tryGetDecimalFallback attempts to fetch the "decimalFormat" resource bundle
   553  * with a particular style. style is either "patternsShort" or "patternsLong."
   554  * FillIn, flags, and status work in the same way as in tryGetByKeyWithFallback.
   555  */
   556 static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) {
   557   UResourceBundle* first = tryGetByKeyWithFallback(numberSystemResource, style, fillIn, flags, status);
   558   UResourceBundle* second = tryGetByKeyWithFallback(first, gDecimalFormatTag, fillIn, flags, status);
   559   if (fillIn == NULL) {
   560     ures_close(first);
   561   }
   562   return second;
   563 }
   565 // tryGetByKeyWithFallback returns a sub-resource bundle that matches given
   566 // criteria or NULL if none found. rb is the resource bundle that we are
   567 // searching. If rb == NULL then this function behaves as if no sub-resource
   568 // is found; path is the key of the sub-resource,
   569 // (i.e "foo" but not "foo/bar"); If fillIn is NULL, caller must always call
   570 // ures_close() on returned resource. See below for example when fillIn is
   571 // not NULL. flags is ANY or NOT_ROOT. Optionally, these values
   572 // can be ored with MUST. MUST by itself is the same as ANY | MUST.
   573 // The locale of the returned sub-resource will either match the
   574 // flags or the returned sub-resouce will be NULL. If MUST is included in
   575 // flags, and not suitable sub-resource is found then in addition to returning
   576 // NULL, this function also sets status to U_MISSING_RESOURCE_ERROR. If MUST
   577 // is not included in flags, then this function just returns NULL if no
   578 // such sub-resource is found and will never set status to
   579 // U_MISSING_RESOURCE_ERROR.
   580 //
   581 // Example: This code first searches for "foo/bar" sub-resource without falling
   582 // back to ROOT. Then searches for "baz" sub-resource as last resort.
   583 //
   584 // UResourcebundle* fillIn = NULL;
   585 // UResourceBundle* data = tryGetByKeyWithFallback(rb, "foo", &fillIn, NON_ROOT, status);
   586 // data = tryGetByKeyWithFallback(data, "bar", &fillIn, NON_ROOT, status);
   587 // if (!data) {
   588 //   data = tryGetbyKeyWithFallback(rb, "baz", &fillIn, MUST,  status);
   589 // }
   590 // if (U_FAILURE(status)) {
   591 //   ures_close(fillIn);
   592 //   return;
   593 // }
   594 // doStuffWithNonNullSubresource(data);
   595 //
   596 // /* Wrong! don't do the following as it can leak memory if fillIn gets set
   597 // to NULL. */
   598 // fillIn = tryGetByKeyWithFallback(rb, "wrong", &fillIn, ANY, status);
   599 //
   600 // ures_close(fillIn);
   601 // 
   602 static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) {
   603   if (U_FAILURE(status)) {
   604     return NULL;
   605   }
   606   UBool must = (flags & MUST);
   607   if (rb == NULL) {
   608     if (must) {
   609       status = U_MISSING_RESOURCE_ERROR;
   610     }
   611     return NULL;
   612   }
   613   UResourceBundle* result = NULL;
   614   UResourceBundle* ownedByUs = NULL;
   615   if (fillIn == NULL) {
   616     ownedByUs = ures_getByKeyWithFallback(rb, path, NULL, &status);
   617     result = ownedByUs;
   618   } else {
   619     *fillIn = ures_getByKeyWithFallback(rb, path, *fillIn, &status);
   620     result = *fillIn;
   621   }
   622   if (U_FAILURE(status)) {
   623     ures_close(ownedByUs);
   624     if (status == U_MISSING_RESOURCE_ERROR && !must) {
   625       status = U_ZERO_ERROR;
   626     }
   627     return NULL;
   628   }
   629   flags = (FallbackFlags) (flags & ~MUST);
   630   switch (flags) {
   631     case NOT_ROOT:
   632       {
   633         UBool bRoot = isRoot(result, status);
   634         if (bRoot || U_FAILURE(status)) {
   635           ures_close(ownedByUs);
   636           if (must && (status == U_ZERO_ERROR)) {
   637             status = U_MISSING_RESOURCE_ERROR;
   638           }
   639           return NULL;
   640         }
   641         return result;
   642       }
   643     case ANY:
   644       return result;
   645     default:
   646       ures_close(ownedByUs);
   647       status = U_ILLEGAL_ARGUMENT_ERROR;
   648       return NULL;
   649   }
   650 }
   652 static UBool isRoot(const UResourceBundle* rb, UErrorCode& status) {
   653   const char* actualLocale = ures_getLocaleByType(
   654       rb, ULOC_ACTUAL_LOCALE, &status);
   655   if (U_FAILURE(status)) {
   656     return FALSE;
   657   }
   658   return uprv_strcmp(actualLocale, gRoot) == 0;
   659 }
   662 // initCDFLocaleStyleData loads formatting data for a particular style.
   663 // decimalFormatBundle is the "decimalFormat" resource bundle in CLDR.
   664 // Loaded data stored in result.
   665 static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status) {
   666   if (U_FAILURE(status)) {
   667     return;
   668   }
   669   // Iterate through all the powers of 10.
   670   int32_t size = ures_getSize(decimalFormatBundle);
   671   UResourceBundle* power10 = NULL;
   672   for (int32_t i = 0; i < size; ++i) {
   673     power10 = ures_getByIndex(decimalFormatBundle, i, power10, &status);
   674     if (U_FAILURE(status)) {
   675       ures_close(power10);
   676       return;
   677     }
   678     populatePower10(power10, result, status);
   679     if (U_FAILURE(status)) {
   680       ures_close(power10);
   681       return;
   682     }
   683   }
   684   ures_close(power10);
   685   fillInMissing(result);
   686 }
   688 // populatePower10 grabs data for a particular power of 10 from CLDR.
   689 // The loaded data is stored in result.
   690 static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status) {
   691   if (U_FAILURE(status)) {
   692     return;
   693   }
   694   char* endPtr = NULL;
   695   double power10 = uprv_strtod(ures_getKey(power10Bundle), &endPtr);
   696   if (*endPtr != 0) {
   697     status = U_INTERNAL_PROGRAM_ERROR;
   698     return;
   699   }
   700   int32_t log10Value = computeLog10(power10, FALSE);
   701   // Silently ignore divisors that are too big.
   702   if (log10Value == MAX_DIGITS) {
   703     return;
   704   }
   705   int32_t size = ures_getSize(power10Bundle);
   706   int32_t numZeros = 0;
   707   UBool otherVariantDefined = FALSE;
   708   UResourceBundle* variantBundle = NULL;
   709   // Iterate over all the plural variants for the power of 10
   710   for (int32_t i = 0; i < size; ++i) {
   711     variantBundle = ures_getByIndex(power10Bundle, i, variantBundle, &status);
   712     if (U_FAILURE(status)) {
   713       ures_close(variantBundle);
   714       return;
   715     }
   716     const char* variant = ures_getKey(variantBundle);
   717     int32_t resLen;
   718     const UChar* formatStrP = ures_getString(variantBundle, &resLen, &status);
   719     if (U_FAILURE(status)) {
   720       ures_close(variantBundle);
   721       return;
   722     }
   723     UnicodeString formatStr(false, formatStrP, resLen);
   724     if (uprv_strcmp(variant, gOther) == 0) {
   725       otherVariantDefined = TRUE;
   726     }
   727     int32_t nz = populatePrefixSuffix(
   728         variant, log10Value, formatStr, result->unitsByVariant, status);
   729     if (U_FAILURE(status)) {
   730       ures_close(variantBundle);
   731       return;
   732     }
   733     if (nz != numZeros) {
   734       // We expect all format strings to have the same number of 0's
   735       // left of the decimal point.
   736       if (numZeros != 0) {
   737         status = U_INTERNAL_PROGRAM_ERROR;
   738         ures_close(variantBundle);
   739         return;
   740       }
   741       numZeros = nz;
   742     }
   743   }
   744   ures_close(variantBundle);
   745   // We expect to find an OTHER variant for each power of 10.
   746   if (!otherVariantDefined) {
   747     status = U_INTERNAL_PROGRAM_ERROR;
   748     return;
   749   }
   750   double divisor = power10;
   751   for (int32_t i = 1; i < numZeros; ++i) {
   752     divisor /= 10.0;
   753   }
   754   result->divisors[log10Value] = divisor;
   755 }
   757 // populatePrefixSuffix Adds a specific prefix-suffix pair to result for a
   758 // given variant and log10 value.
   759 // variant is 'zero', 'one', 'two', 'few', 'many', or 'other'.
   760 // formatStr is the format string from which the prefix and suffix are
   761 // extracted. It is usually of form 'Pefix 000 suffix'.
   762 // populatePrefixSuffix returns the number of 0's found in formatStr
   763 // before the decimal point.
   764 // In the special case that formatStr contains only spaces for prefix
   765 // and suffix, populatePrefixSuffix returns log10Value + 1.
   766 static int32_t populatePrefixSuffix(
   767     const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status) {
   768   if (U_FAILURE(status)) {
   769     return 0;
   770   }
   771   int32_t firstIdx = formatStr.indexOf(kZero, LENGTHOF(kZero), 0);
   772   // We must have 0's in format string.
   773   if (firstIdx == -1) {
   774     status = U_INTERNAL_PROGRAM_ERROR;
   775     return 0;
   776   }
   777   int32_t lastIdx = formatStr.lastIndexOf(kZero, LENGTHOF(kZero), firstIdx);
   778   CDFUnit* unit = createCDFUnit(variant, log10Value, result, status);
   779   if (U_FAILURE(status)) {
   780     return 0;
   781   }
   782   // Everything up to first 0 is the prefix
   783   unit->prefix = formatStr.tempSubString(0, firstIdx);
   784   fixQuotes(unit->prefix);
   785   // Everything beyond the last 0 is the suffix
   786   unit->suffix = formatStr.tempSubString(lastIdx + 1);
   787   fixQuotes(unit->suffix);
   789   // If there is effectively no prefix or suffix, ignore the actual number of
   790   // 0's and act as if the number of 0's matches the size of the number.
   791   if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) {
   792     return log10Value + 1;
   793   }
   795   // Calculate number of zeros before decimal point
   796   int32_t idx = firstIdx + 1;
   797   while (idx <= lastIdx && formatStr.charAt(idx) == u_0) {
   798     ++idx;
   799   }
   800   return (idx - firstIdx);
   801 }
   803 static UBool onlySpaces(UnicodeString u) {
   804   return u.trim().length() == 0;
   805 }
   807 // fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j.
   808 // Modifies s in place.
   809 static void fixQuotes(UnicodeString& s) {
   810   QuoteState state = OUTSIDE;
   811   int32_t len = s.length();
   812   int32_t dest = 0;
   813   for (int32_t i = 0; i < len; ++i) {
   814     UChar ch = s.charAt(i);
   815     if (ch == u_apos) {
   816       if (state == INSIDE_EMPTY) {
   817         s.setCharAt(dest, ch);
   818         ++dest;
   819       }
   820     } else {
   821       s.setCharAt(dest, ch);
   822       ++dest;
   823     }
   825     // Update state
   826     switch (state) {
   827       case OUTSIDE:
   828         state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE;
   829         break;
   830       case INSIDE_EMPTY:
   831       case INSIDE_FULL:
   832         state = ch == u_apos ? OUTSIDE : INSIDE_FULL;
   833         break;
   834       default:
   835         break;
   836     }
   837   }
   838   s.truncate(dest);
   839 }
   841 // fillInMissing ensures that the data in result is complete.
   842 // result data is complete if for each variant in result, there exists
   843 // a prefix-suffix pair for each log10 value and there also exists
   844 // a divisor for each log10 value.
   845 //
   846 // First this function figures out for which log10 values, the other
   847 // variant already had data. These are the same log10 values defined
   848 // in CLDR. 
   849 //
   850 // For each log10 value not defined in CLDR, it uses the divisor for
   851 // the last defined log10 value or 1.
   852 //
   853 // Then for each variant, it does the following. For each log10
   854 // value not defined in CLDR, copy the prefix-suffix pair from the
   855 // previous log10 value. If log10 value is defined in CLDR but is
   856 // missing from given variant, copy the prefix-suffix pair for that
   857 // log10 value from the 'other' variant.
   858 static void fillInMissing(CDFLocaleStyleData* result) {
   859   const CDFUnit* otherUnits =
   860       (const CDFUnit*) uhash_get(result->unitsByVariant, gOther);
   861   UBool definedInCLDR[MAX_DIGITS];
   862   double lastDivisor = 1.0;
   863   for (int32_t i = 0; i < MAX_DIGITS; ++i) {
   864     if (!otherUnits[i].isSet()) {
   865       result->divisors[i] = lastDivisor;
   866       definedInCLDR[i] = FALSE;
   867     } else {
   868       lastDivisor = result->divisors[i];
   869       definedInCLDR[i] = TRUE;
   870     }
   871   }
   872   // Iterate over each variant.
   873   int32_t pos = -1;
   874   const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos);
   875   for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) {
   876     CDFUnit* units = (CDFUnit*) element->value.pointer;
   877     for (int32_t i = 0; i < MAX_DIGITS; ++i) {
   878       if (definedInCLDR[i]) {
   879         if (!units[i].isSet()) {
   880           units[i] = otherUnits[i];
   881         }
   882       } else {
   883         if (i == 0) {
   884           units[0].markAsSet();
   885         } else {
   886           units[i] = units[i - 1];
   887         }
   888       }
   889     }
   890   }
   891 }
   893 // computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest
   894 // value computeLog10 will return MAX_DIGITS -1 even for
   895 // numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return
   896 // up to MAX_DIGITS.
   897 static int32_t computeLog10(double x, UBool inRange) {
   898   int32_t result = 0;
   899   int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS;
   900   while (x >= 10.0) {
   901     x /= 10.0;
   902     ++result;
   903     if (result == max) {
   904       break;
   905     }
   906   }
   907   return result;
   908 }
   910 // createCDFUnit returns a pointer to the prefix-suffix pair for a given
   911 // variant and log10 value within table. If no such prefix-suffix pair is
   912 // stored in table, one is created within table before returning pointer.
   913 static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) {
   914   if (U_FAILURE(status)) {
   915     return NULL;
   916   }
   917   CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant);
   918   if (cdfUnit == NULL) {
   919     cdfUnit = new CDFUnit[MAX_DIGITS];
   920     if (cdfUnit == NULL) {
   921       status = U_MEMORY_ALLOCATION_ERROR;
   922       return NULL;
   923     }
   924     uhash_put(table, uprv_strdup(variant), cdfUnit, &status);
   925     if (U_FAILURE(status)) {
   926       return NULL;
   927     }
   928   }
   929   CDFUnit* result = &cdfUnit[log10Value];
   930   result->markAsSet();
   931   return result;
   932 }
   934 // getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given
   935 // variant and log10 value within table. If the given variant doesn't exist, it
   936 // falls back to the OTHER variant. Therefore, this method will always return
   937 // some non-NULL value.
   938 static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) {
   939   CharString cvariant;
   940   UErrorCode status = U_ZERO_ERROR;
   941   const CDFUnit *cdfUnit = NULL;
   942   cvariant.appendInvariantChars(variant, status);
   943   if (!U_FAILURE(status)) {
   944     cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data());
   945   }
   946   if (cdfUnit == NULL) {
   947     cdfUnit = (const CDFUnit*) uhash_get(table, gOther);
   948   }
   949   return &cdfUnit[log10Value];
   950 }
   952 U_NAMESPACE_END
   953 #endif

mercurial