|
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 |