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