]>
Commit | Line | Data |
---|---|---|
51004dcb A |
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" | |
12 | ||
13 | #if !UCONFIG_NO_FORMATTING | |
14 | ||
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" | |
28 | ||
29 | #define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) | |
30 | ||
31 | // Maps locale name to CDFLocaleData struct. | |
32 | static UHashtable* gCompactDecimalData = NULL; | |
33 | static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER; | |
34 | ||
35 | U_NAMESPACE_BEGIN | |
36 | ||
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"; | |
45 | ||
46 | static const UChar u_0 = 0x30; | |
47 | static const UChar u_apos = 0x27; | |
48 | ||
49 | static const UChar kZero[] = {u_0}; | |
50 | ||
51 | // Used to unescape single quotes. | |
52 | enum QuoteState { | |
53 | OUTSIDE, | |
54 | INSIDE_EMPTY, | |
55 | INSIDE_FULL | |
56 | }; | |
57 | ||
58 | enum FallbackFlags { | |
59 | ANY = 0, | |
60 | MUST = 1, | |
61 | NOT_ROOT = 2 | |
62 | // Next one will be 4 then 6 etc. | |
63 | }; | |
64 | ||
65 | ||
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 | }; | |
82 | ||
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 | }; | |
111 | ||
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 | }; | |
121 | ||
122 | U_NAMESPACE_END | |
123 | ||
124 | U_CDECL_BEGIN | |
125 | ||
126 | static UBool U_CALLCONV cdf_cleanup(void) { | |
127 | if (gCompactDecimalData != NULL) { | |
128 | uhash_close(gCompactDecimalData); | |
129 | gCompactDecimalData = NULL; | |
130 | } | |
131 | return TRUE; | |
132 | } | |
133 | ||
134 | static void U_CALLCONV deleteCDFUnits(void* ptr) { | |
135 | delete [] (icu::CDFUnit*) ptr; | |
136 | } | |
137 | ||
138 | static void U_CALLCONV deleteCDFLocaleData(void* ptr) { | |
139 | delete (icu::CDFLocaleData*) ptr; | |
140 | } | |
141 | ||
142 | U_CDECL_END | |
143 | ||
144 | U_NAMESPACE_BEGIN | |
145 | ||
146 | static UBool divisors_equal(const double* lhs, const double* rhs); | |
147 | static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); | |
148 | ||
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); | |
164 | ||
165 | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) | |
166 | ||
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 | } | |
174 | ||
175 | CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) | |
176 | : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) { | |
177 | } | |
178 | ||
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 | } | |
206 | ||
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 | } | |
218 | ||
219 | CompactDecimalFormat::~CompactDecimalFormat() { | |
220 | delete _pluralRules; | |
221 | } | |
222 | ||
223 | ||
224 | Format* | |
225 | CompactDecimalFormat::clone(void) const { | |
226 | return new CompactDecimalFormat(*this); | |
227 | } | |
228 | ||
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 | } | |
236 | ||
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 | } | |
241 | ||
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 | } | |
271 | ||
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 | } | |
281 | ||
282 | UnicodeString& | |
283 | CompactDecimalFormat::format( | |
284 | int64_t number, | |
285 | UnicodeString& appendTo, | |
286 | FieldPosition& pos) const { | |
287 | return format((double) number, appendTo, pos); | |
288 | } | |
289 | ||
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 | } | |
299 | ||
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 | } | |
309 | ||
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 | } | |
319 | ||
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 | } | |
328 | ||
329 | void | |
330 | CompactDecimalFormat::parse( | |
331 | const UnicodeString& /* text */, | |
332 | Formattable& /* result */, | |
333 | ParsePosition& /* parsePosition */) const { | |
334 | } | |
335 | ||
336 | void | |
337 | CompactDecimalFormat::parse( | |
338 | const UnicodeString& /* text */, | |
339 | Formattable& /* result */, | |
340 | UErrorCode& status) const { | |
341 | status = U_UNSUPPORTED_ERROR; | |
342 | } | |
343 | ||
344 | CurrencyAmount* | |
345 | CompactDecimalFormat::parseCurrency( | |
346 | const UnicodeString& /* text */, | |
347 | ParsePosition& /* pos */) const { | |
348 | return NULL; | |
349 | } | |
350 | ||
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 | } | |
362 | ||
363 | CDFLocaleStyleData::~CDFLocaleStyleData() { | |
364 | setToBogus(); | |
365 | } | |
366 | ||
367 | void CDFLocaleStyleData::setToBogus() { | |
368 | if (unitsByVariant != NULL) { | |
369 | uhash_close(unitsByVariant); | |
370 | unitsByVariant = NULL; | |
371 | } | |
372 | } | |
373 | ||
374 | void CDFLocaleData::Init(UErrorCode& status) { | |
375 | shortData.Init(status); | |
376 | if (U_FAILURE(status)) { | |
377 | return; | |
378 | } | |
379 | longData.Init(status); | |
380 | } | |
381 | ||
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 | } | |
391 | ||
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 | } | |
421 | ||
422 | result = loadCDFLocaleData(inLocale, status); | |
423 | if (U_FAILURE(status)) { | |
424 | return NULL; | |
425 | } | |
426 | ||
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 | } | |
442 | ||
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 | } | |
457 | ||
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 | } | |
474 | ||
475 | initCDFLocaleData(inLocale, result, status); | |
476 | if (U_FAILURE(status)) { | |
477 | delete result; | |
478 | return NULL; | |
479 | } | |
480 | return result; | |
481 | } | |
482 | ||
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; | |
507 | ||
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 | } | |
522 | ||
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 | } | |
541 | ||
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 | } | |
550 | ||
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 | } | |
564 | ||
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 | } | |
651 | ||
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 | } | |
660 | ||
661 | ||
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 | } | |
687 | ||
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 | } | |
756 | ||
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); | |
788 | ||
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 | } | |
794 | ||
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 | } | |
802 | ||
803 | static UBool onlySpaces(UnicodeString u) { | |
804 | return u.trim().length() == 0; | |
805 | } | |
806 | ||
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 | } | |
824 | ||
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 | } | |
840 | ||
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 | } | |
892 | ||
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 | } | |
909 | ||
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 | } | |
933 | ||
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 | } | |
951 | ||
952 | U_NAMESPACE_END | |
953 | #endif |