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