]>
Commit | Line | Data |
---|---|---|
51004dcb A |
1 | /* |
2 | ******************************************************************************* | |
b331163b | 3 | * Copyright (C) 1997-2014, 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 { | |
245 | DigitList orig, rounded; | |
246 | orig.set(number); | |
247 | UBool isNegative; | |
248 | UErrorCode status = U_ZERO_ERROR; | |
249 | _round(orig, rounded, isNegative, status); | |
250 | if (U_FAILURE(status)) { | |
251 | return appendTo; | |
252 | } | |
253 | double roundedDouble = rounded.getDouble(); | |
254 | if (isNegative) { | |
255 | roundedDouble = -roundedDouble; | |
256 | } | |
257 | int32_t baseIdx = computeLog10(roundedDouble, TRUE); | |
258 | double numberToFormat = roundedDouble / _divisors[baseIdx]; | |
259 | UnicodeString variant = _pluralRules->select(numberToFormat); | |
260 | if (isNegative) { | |
261 | numberToFormat = -numberToFormat; | |
262 | } | |
263 | const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx); | |
264 | appendTo += unit->prefix; | |
265 | DecimalFormat::format(numberToFormat, appendTo, pos); | |
266 | appendTo += unit->suffix; | |
267 | return appendTo; | |
268 | } | |
269 | ||
270 | UnicodeString& | |
271 | CompactDecimalFormat::format( | |
272 | double /* number */, | |
273 | UnicodeString& appendTo, | |
274 | FieldPositionIterator* /* posIter */, | |
275 | UErrorCode& status) const { | |
276 | status = U_UNSUPPORTED_ERROR; | |
277 | return appendTo; | |
278 | } | |
279 | ||
280 | UnicodeString& | |
281 | CompactDecimalFormat::format( | |
282 | int64_t number, | |
283 | UnicodeString& appendTo, | |
284 | FieldPosition& pos) const { | |
285 | return format((double) number, appendTo, pos); | |
286 | } | |
287 | ||
288 | UnicodeString& | |
289 | CompactDecimalFormat::format( | |
290 | int64_t /* number */, | |
291 | UnicodeString& appendTo, | |
292 | FieldPositionIterator* /* posIter */, | |
293 | UErrorCode& status) const { | |
294 | status = U_UNSUPPORTED_ERROR; | |
295 | return appendTo; | |
296 | } | |
297 | ||
298 | UnicodeString& | |
299 | CompactDecimalFormat::format( | |
300 | const StringPiece& /* number */, | |
301 | UnicodeString& appendTo, | |
302 | FieldPositionIterator* /* posIter */, | |
303 | UErrorCode& status) const { | |
304 | status = U_UNSUPPORTED_ERROR; | |
305 | return appendTo; | |
306 | } | |
307 | ||
308 | UnicodeString& | |
309 | CompactDecimalFormat::format( | |
310 | const DigitList& /* number */, | |
311 | UnicodeString& appendTo, | |
312 | FieldPositionIterator* /* posIter */, | |
313 | UErrorCode& status) const { | |
314 | status = U_UNSUPPORTED_ERROR; | |
315 | return appendTo; | |
316 | } | |
317 | ||
318 | UnicodeString& | |
319 | CompactDecimalFormat::format(const DigitList& /* number */, | |
320 | UnicodeString& appendTo, | |
321 | FieldPosition& /* pos */, | |
322 | UErrorCode& status) const { | |
323 | status = U_UNSUPPORTED_ERROR; | |
324 | return appendTo; | |
325 | } | |
326 | ||
327 | void | |
328 | CompactDecimalFormat::parse( | |
329 | const UnicodeString& /* text */, | |
330 | Formattable& /* result */, | |
331 | ParsePosition& /* parsePosition */) const { | |
332 | } | |
333 | ||
334 | void | |
335 | CompactDecimalFormat::parse( | |
336 | const UnicodeString& /* text */, | |
337 | Formattable& /* result */, | |
338 | UErrorCode& status) const { | |
339 | status = U_UNSUPPORTED_ERROR; | |
340 | } | |
341 | ||
342 | CurrencyAmount* | |
343 | CompactDecimalFormat::parseCurrency( | |
344 | const UnicodeString& /* text */, | |
345 | ParsePosition& /* pos */) const { | |
346 | return NULL; | |
347 | } | |
348 | ||
349 | void CDFLocaleStyleData::Init(UErrorCode& status) { | |
350 | if (unitsByVariant != NULL) { | |
351 | return; | |
352 | } | |
353 | unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); | |
354 | if (U_FAILURE(status)) { | |
355 | return; | |
356 | } | |
357 | uhash_setKeyDeleter(unitsByVariant, uprv_free); | |
358 | uhash_setValueDeleter(unitsByVariant, deleteCDFUnits); | |
359 | } | |
360 | ||
361 | CDFLocaleStyleData::~CDFLocaleStyleData() { | |
362 | setToBogus(); | |
363 | } | |
364 | ||
365 | void CDFLocaleStyleData::setToBogus() { | |
366 | if (unitsByVariant != NULL) { | |
367 | uhash_close(unitsByVariant); | |
368 | unitsByVariant = NULL; | |
369 | } | |
370 | } | |
371 | ||
372 | void CDFLocaleData::Init(UErrorCode& status) { | |
373 | shortData.Init(status); | |
374 | if (U_FAILURE(status)) { | |
375 | return; | |
376 | } | |
377 | longData.Init(status); | |
378 | } | |
379 | ||
380 | // Helper method for operator= | |
381 | static UBool divisors_equal(const double* lhs, const double* rhs) { | |
382 | for (int32_t i = 0; i < MAX_DIGITS; ++i) { | |
383 | if (lhs[i] != rhs[i]) { | |
384 | return FALSE; | |
385 | } | |
386 | } | |
387 | return TRUE; | |
388 | } | |
389 | ||
390 | // getCDFLocaleStyleData returns pointer to formatting data for given locale and | |
391 | // style within the global cache. On cache miss, getCDFLocaleStyleData loads | |
392 | // the data from CLDR into the global cache before returning the pointer. If a | |
393 | // UNUM_LONG data is requested for a locale, and that locale does not have | |
394 | // UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for | |
395 | // that locale. | |
396 | static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { | |
397 | if (U_FAILURE(status)) { | |
398 | return NULL; | |
399 | } | |
400 | CDFLocaleData* result = NULL; | |
401 | const char* key = inLocale.getName(); | |
402 | { | |
403 | Mutex lock(&gCompactDecimalMetaLock); | |
404 | if (gCompactDecimalData == NULL) { | |
405 | gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); | |
406 | if (U_FAILURE(status)) { | |
407 | return NULL; | |
408 | } | |
409 | uhash_setKeyDeleter(gCompactDecimalData, uprv_free); | |
410 | uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData); | |
411 | ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup); | |
412 | } else { | |
413 | result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); | |
414 | } | |
415 | } | |
416 | if (result != NULL) { | |
417 | return extractDataByStyleEnum(*result, style, status); | |
418 | } | |
419 | ||
420 | result = loadCDFLocaleData(inLocale, status); | |
421 | if (U_FAILURE(status)) { | |
422 | return NULL; | |
423 | } | |
424 | ||
425 | { | |
426 | Mutex lock(&gCompactDecimalMetaLock); | |
427 | CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); | |
428 | if (temp != NULL) { | |
429 | delete result; | |
430 | result = temp; | |
431 | } else { | |
432 | uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status); | |
433 | if (U_FAILURE(status)) { | |
434 | return NULL; | |
435 | } | |
436 | } | |
437 | } | |
438 | return extractDataByStyleEnum(*result, style, status); | |
439 | } | |
440 | ||
441 | static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) { | |
442 | switch (style) { | |
443 | case UNUM_SHORT: | |
444 | return &data.shortData; | |
445 | case UNUM_LONG: | |
446 | if (!data.longData.isBogus()) { | |
447 | return &data.longData; | |
448 | } | |
449 | return &data.shortData; | |
450 | default: | |
451 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
452 | return NULL; | |
453 | } | |
454 | } | |
455 | ||
456 | // loadCDFLocaleData loads formatting data from CLDR for a given locale. The | |
457 | // caller owns the returned pointer. | |
458 | static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) { | |
459 | if (U_FAILURE(status)) { | |
460 | return NULL; | |
461 | } | |
462 | CDFLocaleData* result = new CDFLocaleData; | |
463 | if (result == NULL) { | |
464 | status = U_MEMORY_ALLOCATION_ERROR; | |
465 | return NULL; | |
466 | } | |
467 | result->Init(status); | |
468 | if (U_FAILURE(status)) { | |
469 | delete result; | |
470 | return NULL; | |
471 | } | |
472 | ||
473 | initCDFLocaleData(inLocale, result, status); | |
474 | if (U_FAILURE(status)) { | |
475 | delete result; | |
476 | return NULL; | |
477 | } | |
478 | return result; | |
479 | } | |
480 | ||
481 | // initCDFLocaleData initializes result with data from CLDR. | |
482 | // inLocale is the locale, the CLDR data is stored in result. | |
483 | // We load the UNUM_SHORT and UNUM_LONG data looking first in local numbering | |
484 | // system and not including root locale in fallback. Next we try in the latn | |
485 | // numbering system where we fallback all the way to root. If we don't find | |
486 | // UNUM_SHORT data in these three places, we report an error. If we find | |
487 | // UNUM_SHORT data before finding UNUM_LONG data we make UNUM_LONG data fall | |
488 | // back to UNUM_SHORT data. | |
489 | static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) { | |
490 | LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(inLocale, status)); | |
491 | if (U_FAILURE(status)) { | |
492 | return; | |
493 | } | |
494 | const char* numberingSystemName = ns->getName(); | |
495 | UResourceBundle* rb = ures_open(NULL, inLocale.getName(), &status); | |
496 | rb = ures_getByKeyWithFallback(rb, gNumberElementsTag, rb, &status); | |
497 | if (U_FAILURE(status)) { | |
498 | ures_close(rb); | |
499 | return; | |
500 | } | |
501 | UResourceBundle* shortDataFillIn = NULL; | |
502 | UResourceBundle* longDataFillIn = NULL; | |
503 | UResourceBundle* shortData = NULL; | |
504 | UResourceBundle* longData = NULL; | |
505 | ||
506 | if (uprv_strcmp(numberingSystemName, gLatnTag) != 0) { | |
507 | LocalUResourceBundlePointer localResource( | |
508 | tryGetByKeyWithFallback(rb, numberingSystemName, NULL, NOT_ROOT, status)); | |
509 | shortData = tryGetDecimalFallback( | |
510 | localResource.getAlias(), gPatternsShort, &shortDataFillIn, NOT_ROOT, status); | |
511 | longData = tryGetDecimalFallback( | |
512 | localResource.getAlias(), gPatternsLong, &longDataFillIn, NOT_ROOT, status); | |
513 | } | |
514 | if (U_FAILURE(status)) { | |
515 | ures_close(shortDataFillIn); | |
516 | ures_close(longDataFillIn); | |
517 | ures_close(rb); | |
518 | return; | |
519 | } | |
520 | ||
521 | // If we haven't found UNUM_SHORT look in latn numbering system. We must | |
522 | // succeed at finding UNUM_SHORT here. | |
523 | if (shortData == NULL) { | |
524 | LocalUResourceBundlePointer latnResource(tryGetByKeyWithFallback(rb, gLatnTag, NULL, MUST, status)); | |
525 | shortData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsShort, &shortDataFillIn, MUST, status); | |
526 | if (longData == NULL) { | |
527 | longData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsLong, &longDataFillIn, ANY, status); | |
528 | if (longData != NULL && isRoot(longData, status) && !isRoot(shortData, status)) { | |
529 | longData = NULL; | |
530 | } | |
531 | } | |
532 | } | |
533 | initCDFLocaleStyleData(shortData, &result->shortData, status); | |
534 | ures_close(shortDataFillIn); | |
535 | if (U_FAILURE(status)) { | |
536 | ures_close(longDataFillIn); | |
537 | ures_close(rb); | |
538 | } | |
539 | ||
540 | if (longData == NULL) { | |
541 | result->longData.setToBogus(); | |
542 | } else { | |
543 | initCDFLocaleStyleData(longData, &result->longData, status); | |
544 | } | |
545 | ures_close(longDataFillIn); | |
546 | ures_close(rb); | |
547 | } | |
548 | ||
549 | /** | |
550 | * tryGetDecimalFallback attempts to fetch the "decimalFormat" resource bundle | |
551 | * with a particular style. style is either "patternsShort" or "patternsLong." | |
552 | * FillIn, flags, and status work in the same way as in tryGetByKeyWithFallback. | |
553 | */ | |
554 | static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { | |
555 | UResourceBundle* first = tryGetByKeyWithFallback(numberSystemResource, style, fillIn, flags, status); | |
556 | UResourceBundle* second = tryGetByKeyWithFallback(first, gDecimalFormatTag, fillIn, flags, status); | |
557 | if (fillIn == NULL) { | |
558 | ures_close(first); | |
559 | } | |
560 | return second; | |
561 | } | |
562 | ||
563 | // tryGetByKeyWithFallback returns a sub-resource bundle that matches given | |
564 | // criteria or NULL if none found. rb is the resource bundle that we are | |
565 | // searching. If rb == NULL then this function behaves as if no sub-resource | |
566 | // is found; path is the key of the sub-resource, | |
567 | // (i.e "foo" but not "foo/bar"); If fillIn is NULL, caller must always call | |
568 | // ures_close() on returned resource. See below for example when fillIn is | |
569 | // not NULL. flags is ANY or NOT_ROOT. Optionally, these values | |
570 | // can be ored with MUST. MUST by itself is the same as ANY | MUST. | |
571 | // The locale of the returned sub-resource will either match the | |
572 | // flags or the returned sub-resouce will be NULL. If MUST is included in | |
573 | // flags, and not suitable sub-resource is found then in addition to returning | |
574 | // NULL, this function also sets status to U_MISSING_RESOURCE_ERROR. If MUST | |
575 | // is not included in flags, then this function just returns NULL if no | |
576 | // such sub-resource is found and will never set status to | |
577 | // U_MISSING_RESOURCE_ERROR. | |
578 | // | |
579 | // Example: This code first searches for "foo/bar" sub-resource without falling | |
580 | // back to ROOT. Then searches for "baz" sub-resource as last resort. | |
581 | // | |
582 | // UResourcebundle* fillIn = NULL; | |
583 | // UResourceBundle* data = tryGetByKeyWithFallback(rb, "foo", &fillIn, NON_ROOT, status); | |
584 | // data = tryGetByKeyWithFallback(data, "bar", &fillIn, NON_ROOT, status); | |
585 | // if (!data) { | |
586 | // data = tryGetbyKeyWithFallback(rb, "baz", &fillIn, MUST, status); | |
587 | // } | |
588 | // if (U_FAILURE(status)) { | |
589 | // ures_close(fillIn); | |
590 | // return; | |
591 | // } | |
592 | // doStuffWithNonNullSubresource(data); | |
593 | // | |
594 | // /* Wrong! don't do the following as it can leak memory if fillIn gets set | |
595 | // to NULL. */ | |
596 | // fillIn = tryGetByKeyWithFallback(rb, "wrong", &fillIn, ANY, status); | |
597 | // | |
598 | // ures_close(fillIn); | |
599 | // | |
600 | static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) { | |
601 | if (U_FAILURE(status)) { | |
602 | return NULL; | |
603 | } | |
604 | UBool must = (flags & MUST); | |
605 | if (rb == NULL) { | |
606 | if (must) { | |
607 | status = U_MISSING_RESOURCE_ERROR; | |
608 | } | |
609 | return NULL; | |
610 | } | |
611 | UResourceBundle* result = NULL; | |
612 | UResourceBundle* ownedByUs = NULL; | |
613 | if (fillIn == NULL) { | |
614 | ownedByUs = ures_getByKeyWithFallback(rb, path, NULL, &status); | |
615 | result = ownedByUs; | |
616 | } else { | |
617 | *fillIn = ures_getByKeyWithFallback(rb, path, *fillIn, &status); | |
618 | result = *fillIn; | |
619 | } | |
620 | if (U_FAILURE(status)) { | |
621 | ures_close(ownedByUs); | |
622 | if (status == U_MISSING_RESOURCE_ERROR && !must) { | |
623 | status = U_ZERO_ERROR; | |
624 | } | |
625 | return NULL; | |
626 | } | |
627 | flags = (FallbackFlags) (flags & ~MUST); | |
628 | switch (flags) { | |
629 | case NOT_ROOT: | |
630 | { | |
631 | UBool bRoot = isRoot(result, status); | |
632 | if (bRoot || U_FAILURE(status)) { | |
633 | ures_close(ownedByUs); | |
634 | if (must && (status == U_ZERO_ERROR)) { | |
635 | status = U_MISSING_RESOURCE_ERROR; | |
636 | } | |
637 | return NULL; | |
638 | } | |
639 | return result; | |
640 | } | |
641 | case ANY: | |
642 | return result; | |
643 | default: | |
644 | ures_close(ownedByUs); | |
645 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
646 | return NULL; | |
647 | } | |
648 | } | |
649 | ||
650 | static UBool isRoot(const UResourceBundle* rb, UErrorCode& status) { | |
651 | const char* actualLocale = ures_getLocaleByType( | |
652 | rb, ULOC_ACTUAL_LOCALE, &status); | |
653 | if (U_FAILURE(status)) { | |
654 | return FALSE; | |
655 | } | |
656 | return uprv_strcmp(actualLocale, gRoot) == 0; | |
657 | } | |
658 | ||
659 | ||
660 | // initCDFLocaleStyleData loads formatting data for a particular style. | |
661 | // decimalFormatBundle is the "decimalFormat" resource bundle in CLDR. | |
662 | // Loaded data stored in result. | |
663 | static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status) { | |
664 | if (U_FAILURE(status)) { | |
665 | return; | |
666 | } | |
667 | // Iterate through all the powers of 10. | |
668 | int32_t size = ures_getSize(decimalFormatBundle); | |
669 | UResourceBundle* power10 = NULL; | |
670 | for (int32_t i = 0; i < size; ++i) { | |
671 | power10 = ures_getByIndex(decimalFormatBundle, i, power10, &status); | |
672 | if (U_FAILURE(status)) { | |
673 | ures_close(power10); | |
674 | return; | |
675 | } | |
676 | populatePower10(power10, result, status); | |
677 | if (U_FAILURE(status)) { | |
678 | ures_close(power10); | |
679 | return; | |
680 | } | |
681 | } | |
682 | ures_close(power10); | |
683 | fillInMissing(result); | |
684 | } | |
685 | ||
686 | // populatePower10 grabs data for a particular power of 10 from CLDR. | |
687 | // The loaded data is stored in result. | |
688 | static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status) { | |
689 | if (U_FAILURE(status)) { | |
690 | return; | |
691 | } | |
692 | char* endPtr = NULL; | |
693 | double power10 = uprv_strtod(ures_getKey(power10Bundle), &endPtr); | |
694 | if (*endPtr != 0) { | |
695 | status = U_INTERNAL_PROGRAM_ERROR; | |
696 | return; | |
697 | } | |
698 | int32_t log10Value = computeLog10(power10, FALSE); | |
699 | // Silently ignore divisors that are too big. | |
700 | if (log10Value == MAX_DIGITS) { | |
701 | return; | |
702 | } | |
703 | int32_t size = ures_getSize(power10Bundle); | |
704 | int32_t numZeros = 0; | |
705 | UBool otherVariantDefined = FALSE; | |
706 | UResourceBundle* variantBundle = NULL; | |
707 | // Iterate over all the plural variants for the power of 10 | |
708 | for (int32_t i = 0; i < size; ++i) { | |
709 | variantBundle = ures_getByIndex(power10Bundle, i, variantBundle, &status); | |
710 | if (U_FAILURE(status)) { | |
711 | ures_close(variantBundle); | |
712 | return; | |
713 | } | |
714 | const char* variant = ures_getKey(variantBundle); | |
715 | int32_t resLen; | |
716 | const UChar* formatStrP = ures_getString(variantBundle, &resLen, &status); | |
717 | if (U_FAILURE(status)) { | |
718 | ures_close(variantBundle); | |
719 | return; | |
720 | } | |
721 | UnicodeString formatStr(false, formatStrP, resLen); | |
722 | if (uprv_strcmp(variant, gOther) == 0) { | |
723 | otherVariantDefined = TRUE; | |
724 | } | |
725 | int32_t nz = populatePrefixSuffix( | |
726 | variant, log10Value, formatStr, result->unitsByVariant, status); | |
727 | if (U_FAILURE(status)) { | |
728 | ures_close(variantBundle); | |
729 | return; | |
730 | } | |
731 | if (nz != numZeros) { | |
732 | // We expect all format strings to have the same number of 0's | |
733 | // left of the decimal point. | |
734 | if (numZeros != 0) { | |
735 | status = U_INTERNAL_PROGRAM_ERROR; | |
736 | ures_close(variantBundle); | |
737 | return; | |
738 | } | |
739 | numZeros = nz; | |
740 | } | |
741 | } | |
742 | ures_close(variantBundle); | |
743 | // We expect to find an OTHER variant for each power of 10. | |
744 | if (!otherVariantDefined) { | |
745 | status = U_INTERNAL_PROGRAM_ERROR; | |
746 | return; | |
747 | } | |
748 | double divisor = power10; | |
749 | for (int32_t i = 1; i < numZeros; ++i) { | |
750 | divisor /= 10.0; | |
751 | } | |
752 | result->divisors[log10Value] = divisor; | |
753 | } | |
754 | ||
755 | // populatePrefixSuffix Adds a specific prefix-suffix pair to result for a | |
756 | // given variant and log10 value. | |
757 | // variant is 'zero', 'one', 'two', 'few', 'many', or 'other'. | |
758 | // formatStr is the format string from which the prefix and suffix are | |
759 | // extracted. It is usually of form 'Pefix 000 suffix'. | |
760 | // populatePrefixSuffix returns the number of 0's found in formatStr | |
761 | // before the decimal point. | |
762 | // In the special case that formatStr contains only spaces for prefix | |
763 | // and suffix, populatePrefixSuffix returns log10Value + 1. | |
764 | static int32_t populatePrefixSuffix( | |
765 | const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status) { | |
766 | if (U_FAILURE(status)) { | |
767 | return 0; | |
768 | } | |
b331163b | 769 | int32_t firstIdx = formatStr.indexOf(kZero, UPRV_LENGTHOF(kZero), 0); |
51004dcb A |
770 | // We must have 0's in format string. |
771 | if (firstIdx == -1) { | |
772 | status = U_INTERNAL_PROGRAM_ERROR; | |
773 | return 0; | |
774 | } | |
b331163b | 775 | int32_t lastIdx = formatStr.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx); |
51004dcb A |
776 | CDFUnit* unit = createCDFUnit(variant, log10Value, result, status); |
777 | if (U_FAILURE(status)) { | |
778 | return 0; | |
779 | } | |
780 | // Everything up to first 0 is the prefix | |
781 | unit->prefix = formatStr.tempSubString(0, firstIdx); | |
782 | fixQuotes(unit->prefix); | |
783 | // Everything beyond the last 0 is the suffix | |
784 | unit->suffix = formatStr.tempSubString(lastIdx + 1); | |
785 | fixQuotes(unit->suffix); | |
786 | ||
787 | // If there is effectively no prefix or suffix, ignore the actual number of | |
788 | // 0's and act as if the number of 0's matches the size of the number. | |
789 | if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) { | |
790 | return log10Value + 1; | |
791 | } | |
792 | ||
793 | // Calculate number of zeros before decimal point | |
794 | int32_t idx = firstIdx + 1; | |
795 | while (idx <= lastIdx && formatStr.charAt(idx) == u_0) { | |
796 | ++idx; | |
797 | } | |
798 | return (idx - firstIdx); | |
799 | } | |
800 | ||
801 | static UBool onlySpaces(UnicodeString u) { | |
802 | return u.trim().length() == 0; | |
803 | } | |
804 | ||
805 | // fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j. | |
806 | // Modifies s in place. | |
807 | static void fixQuotes(UnicodeString& s) { | |
808 | QuoteState state = OUTSIDE; | |
809 | int32_t len = s.length(); | |
810 | int32_t dest = 0; | |
811 | for (int32_t i = 0; i < len; ++i) { | |
812 | UChar ch = s.charAt(i); | |
813 | if (ch == u_apos) { | |
814 | if (state == INSIDE_EMPTY) { | |
815 | s.setCharAt(dest, ch); | |
816 | ++dest; | |
817 | } | |
818 | } else { | |
819 | s.setCharAt(dest, ch); | |
820 | ++dest; | |
821 | } | |
822 | ||
823 | // Update state | |
824 | switch (state) { | |
825 | case OUTSIDE: | |
826 | state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE; | |
827 | break; | |
828 | case INSIDE_EMPTY: | |
829 | case INSIDE_FULL: | |
830 | state = ch == u_apos ? OUTSIDE : INSIDE_FULL; | |
831 | break; | |
832 | default: | |
833 | break; | |
834 | } | |
835 | } | |
836 | s.truncate(dest); | |
837 | } | |
838 | ||
839 | // fillInMissing ensures that the data in result is complete. | |
840 | // result data is complete if for each variant in result, there exists | |
841 | // a prefix-suffix pair for each log10 value and there also exists | |
842 | // a divisor for each log10 value. | |
843 | // | |
844 | // First this function figures out for which log10 values, the other | |
845 | // variant already had data. These are the same log10 values defined | |
846 | // in CLDR. | |
847 | // | |
848 | // For each log10 value not defined in CLDR, it uses the divisor for | |
849 | // the last defined log10 value or 1. | |
850 | // | |
851 | // Then for each variant, it does the following. For each log10 | |
852 | // value not defined in CLDR, copy the prefix-suffix pair from the | |
853 | // previous log10 value. If log10 value is defined in CLDR but is | |
854 | // missing from given variant, copy the prefix-suffix pair for that | |
855 | // log10 value from the 'other' variant. | |
856 | static void fillInMissing(CDFLocaleStyleData* result) { | |
857 | const CDFUnit* otherUnits = | |
858 | (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); | |
859 | UBool definedInCLDR[MAX_DIGITS]; | |
860 | double lastDivisor = 1.0; | |
861 | for (int32_t i = 0; i < MAX_DIGITS; ++i) { | |
862 | if (!otherUnits[i].isSet()) { | |
863 | result->divisors[i] = lastDivisor; | |
864 | definedInCLDR[i] = FALSE; | |
865 | } else { | |
866 | lastDivisor = result->divisors[i]; | |
867 | definedInCLDR[i] = TRUE; | |
868 | } | |
869 | } | |
870 | // Iterate over each variant. | |
b331163b | 871 | int32_t pos = UHASH_FIRST; |
51004dcb A |
872 | const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos); |
873 | for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) { | |
874 | CDFUnit* units = (CDFUnit*) element->value.pointer; | |
875 | for (int32_t i = 0; i < MAX_DIGITS; ++i) { | |
876 | if (definedInCLDR[i]) { | |
877 | if (!units[i].isSet()) { | |
878 | units[i] = otherUnits[i]; | |
879 | } | |
880 | } else { | |
881 | if (i == 0) { | |
882 | units[0].markAsSet(); | |
883 | } else { | |
884 | units[i] = units[i - 1]; | |
885 | } | |
886 | } | |
887 | } | |
888 | } | |
889 | } | |
890 | ||
891 | // computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest | |
892 | // value computeLog10 will return MAX_DIGITS -1 even for | |
893 | // numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return | |
894 | // up to MAX_DIGITS. | |
895 | static int32_t computeLog10(double x, UBool inRange) { | |
896 | int32_t result = 0; | |
897 | int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS; | |
898 | while (x >= 10.0) { | |
899 | x /= 10.0; | |
900 | ++result; | |
901 | if (result == max) { | |
902 | break; | |
903 | } | |
904 | } | |
905 | return result; | |
906 | } | |
907 | ||
908 | // createCDFUnit returns a pointer to the prefix-suffix pair for a given | |
909 | // variant and log10 value within table. If no such prefix-suffix pair is | |
910 | // stored in table, one is created within table before returning pointer. | |
911 | static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) { | |
912 | if (U_FAILURE(status)) { | |
913 | return NULL; | |
914 | } | |
915 | CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant); | |
916 | if (cdfUnit == NULL) { | |
917 | cdfUnit = new CDFUnit[MAX_DIGITS]; | |
918 | if (cdfUnit == NULL) { | |
919 | status = U_MEMORY_ALLOCATION_ERROR; | |
920 | return NULL; | |
921 | } | |
922 | uhash_put(table, uprv_strdup(variant), cdfUnit, &status); | |
923 | if (U_FAILURE(status)) { | |
924 | return NULL; | |
925 | } | |
926 | } | |
927 | CDFUnit* result = &cdfUnit[log10Value]; | |
928 | result->markAsSet(); | |
929 | return result; | |
930 | } | |
931 | ||
932 | // getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given | |
933 | // variant and log10 value within table. If the given variant doesn't exist, it | |
934 | // falls back to the OTHER variant. Therefore, this method will always return | |
935 | // some non-NULL value. | |
936 | static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) { | |
937 | CharString cvariant; | |
938 | UErrorCode status = U_ZERO_ERROR; | |
939 | const CDFUnit *cdfUnit = NULL; | |
940 | cvariant.appendInvariantChars(variant, status); | |
941 | if (!U_FAILURE(status)) { | |
942 | cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data()); | |
943 | } | |
944 | if (cdfUnit == NULL) { | |
945 | cdfUnit = (const CDFUnit*) uhash_get(table, gOther); | |
946 | } | |
947 | return &cdfUnit[log10Value]; | |
948 | } | |
949 | ||
950 | U_NAMESPACE_END | |
951 | #endif |