]>
Commit | Line | Data |
---|---|---|
b75a7d8f A |
1 | /* |
2 | ********************************************************************** | |
374ca955 | 3 | * Copyright (c) 2002-2004, International Business Machines |
b75a7d8f A |
4 | * Corporation and others. All Rights Reserved. |
5 | ********************************************************************** | |
6 | */ | |
7 | ||
8 | #include "unicode/utypes.h" | |
9 | ||
10 | #if !UCONFIG_NO_FORMATTING | |
11 | ||
12 | #include "unicode/ucurr.h" | |
13 | #include "unicode/locid.h" | |
374ca955 | 14 | #include "unicode/ures.h" |
b75a7d8f | 15 | #include "unicode/ustring.h" |
374ca955 A |
16 | #include "unicode/choicfmt.h" |
17 | #include "unicode/parsepos.h" | |
18 | #include "ustr_imp.h" | |
b75a7d8f A |
19 | #include "cmemory.h" |
20 | #include "cstring.h" | |
21 | #include "uassert.h" | |
374ca955 | 22 | #include "mutex.h" |
b75a7d8f A |
23 | #include "ucln_in.h" |
24 | ||
25 | //------------------------------------------------------------ | |
26 | // Constants | |
27 | ||
28 | // Default currency meta data of last resort. We try to use the | |
29 | // defaults encoded in the meta data resource bundle. If there is a | |
30 | // configuration/build error and these are not available, we use these | |
31 | // hard-coded defaults (which should be identical). | |
32 | static const int32_t LAST_RESORT_DATA[] = { 2, 0 }; | |
33 | ||
34 | // POW10[i] = 10^i, i=0..MAX_POW10 | |
35 | static const int32_t POW10[] = { 1, 10, 100, 1000, 10000, 100000, | |
36 | 1000000, 10000000, 100000000, 1000000000 }; | |
37 | ||
38 | static const int32_t MAX_POW10 = (sizeof(POW10)/sizeof(POW10[0])) - 1; | |
39 | ||
40 | #define ISO_COUNTRY_CODE_LENGTH 3 | |
41 | ||
42 | //------------------------------------------------------------ | |
43 | // Resource tags | |
374ca955 | 44 | // |
b75a7d8f | 45 | |
374ca955 | 46 | static const char CURRENCY_DATA[] = "CurrencyData"; |
b75a7d8f A |
47 | // Tag for meta-data, in root. |
48 | static const char CURRENCY_META[] = "CurrencyMeta"; | |
49 | ||
50 | // Tag for map from countries to currencies, in root. | |
51 | static const char CURRENCY_MAP[] = "CurrencyMap"; | |
52 | ||
53 | // Tag for default meta-data, in CURRENCY_META | |
54 | static const char DEFAULT_META[] = "DEFAULT"; | |
55 | ||
56 | // Variant for legacy pre-euro mapping in CurrencyMap | |
57 | static const char VAR_PRE_EURO[] = "PREEURO"; | |
58 | ||
59 | // Variant for legacy euro mapping in CurrencyMap | |
60 | static const char VAR_EURO[] = "EURO"; | |
61 | ||
62 | // Variant delimiter | |
63 | static const char VAR_DELIM[] = "_"; | |
64 | ||
374ca955 A |
65 | // Variant for legacy euro mapping in CurrencyMap |
66 | static const char VAR_DELIM_EURO[] = "_EURO"; | |
67 | ||
68 | #define VARIANT_IS_EMPTY 0 | |
69 | #define VARIANT_IS_EURO 0x1 | |
70 | #define VARIANT_IS_PREEURO 0x2 | |
71 | ||
b75a7d8f A |
72 | // Tag for localized display names (symbols) of currencies |
73 | static const char CURRENCIES[] = "Currencies"; | |
74 | ||
75 | // Marker character indicating that a display name is a ChoiceFormat | |
76 | // pattern. Strings that start with one mark are ChoiceFormat | |
77 | // patterns. Strings that start with 2 marks are static strings, and | |
78 | // the first mark is deleted. | |
79 | static const UChar CHOICE_FORMAT_MARK = 0x003D; // Equals sign | |
80 | ||
81 | //------------------------------------------------------------ | |
82 | // Code | |
83 | ||
84 | /** | |
85 | * Unfortunately, we have to convert the UChar* currency code to char* | |
86 | * to use it as a resource key. | |
87 | */ | |
88 | static inline char* | |
89 | myUCharsToChars(char* resultOfLen4, const UChar* currency) { | |
90 | u_UCharsToChars(currency, resultOfLen4, ISO_COUNTRY_CODE_LENGTH); | |
91 | resultOfLen4[ISO_COUNTRY_CODE_LENGTH] = 0; | |
92 | return resultOfLen4; | |
93 | } | |
94 | ||
95 | /** | |
96 | * Internal function to look up currency data. Result is an array of | |
97 | * two integers. The first is the fraction digits. The second is the | |
98 | * rounding increment, or 0 if none. The rounding increment is in | |
99 | * units of 10^(-fraction_digits). | |
100 | */ | |
101 | static const int32_t* | |
374ca955 A |
102 | _findMetaData(const UChar* currency, UErrorCode& ec) { |
103 | ||
104 | if (currency == 0 || *currency == 0) { | |
105 | if (U_SUCCESS(ec)) { | |
106 | ec = U_ILLEGAL_ARGUMENT_ERROR; | |
107 | } | |
108 | return LAST_RESORT_DATA; | |
109 | } | |
b75a7d8f A |
110 | |
111 | // Get CurrencyMeta resource out of root locale file. [This may | |
112 | // move out of the root locale file later; if it does, update this | |
113 | // code.] | |
374ca955 A |
114 | UResourceBundle* currencyData = ures_openDirect(NULL, CURRENCY_DATA, &ec); |
115 | UResourceBundle* currencyMeta = ures_getByKey(currencyData, CURRENCY_META, currencyData, &ec); | |
116 | ||
b75a7d8f | 117 | if (U_FAILURE(ec)) { |
374ca955 | 118 | ures_close(currencyMeta); |
b75a7d8f A |
119 | // Config/build error; return hard-coded defaults |
120 | return LAST_RESORT_DATA; | |
121 | } | |
122 | ||
123 | // Look up our currency, or if that's not available, then DEFAULT | |
124 | char buf[ISO_COUNTRY_CODE_LENGTH+1]; | |
374ca955 A |
125 | UErrorCode ec2 = U_ZERO_ERROR; // local error code: soft failure |
126 | UResourceBundle* rb = ures_getByKey(currencyMeta, myUCharsToChars(buf, currency), NULL, &ec2); | |
127 | if (U_FAILURE(ec2)) { | |
128 | ures_close(rb); | |
129 | rb = ures_getByKey(currencyMeta,DEFAULT_META, NULL, &ec); | |
b75a7d8f | 130 | if (U_FAILURE(ec)) { |
374ca955 A |
131 | ures_close(currencyMeta); |
132 | ures_close(rb); | |
b75a7d8f A |
133 | // Config/build error; return hard-coded defaults |
134 | return LAST_RESORT_DATA; | |
135 | } | |
136 | } | |
137 | ||
138 | int32_t len; | |
374ca955 A |
139 | const int32_t *data = ures_getIntVector(rb, &len, &ec); |
140 | if (U_FAILURE(ec) || len != 2) { | |
b75a7d8f | 141 | // Config/build error; return hard-coded defaults |
374ca955 A |
142 | if (U_SUCCESS(ec)) { |
143 | ec = U_INVALID_FORMAT_ERROR; | |
144 | } | |
145 | ures_close(currencyMeta); | |
146 | ures_close(rb); | |
b75a7d8f A |
147 | return LAST_RESORT_DATA; |
148 | } | |
149 | ||
374ca955 A |
150 | ures_close(currencyMeta); |
151 | ures_close(rb); | |
b75a7d8f A |
152 | return data; |
153 | } | |
154 | ||
374ca955 A |
155 | // ------------------------------------- |
156 | ||
157 | /** | |
158 | * @see VARIANT_IS_EURO | |
159 | * @see VARIANT_IS_PREEURO | |
160 | */ | |
161 | static uint32_t | |
162 | idForLocale(const char* locale, char* countryAndVariant, int capacity, UErrorCode* ec) | |
163 | { | |
164 | uint32_t variantType = 0; | |
165 | // !!! this is internal only, assumes buffer is not null and capacity is sufficient | |
166 | // Extract the country name and variant name. We only | |
167 | // recognize two variant names, EURO and PREEURO. | |
168 | char variant[ULOC_FULLNAME_CAPACITY]; | |
169 | uloc_getCountry(locale, countryAndVariant, capacity, ec); | |
170 | uloc_getVariant(locale, variant, sizeof(variant), ec); | |
171 | if (variant[0] != 0) { | |
172 | variantType = (0 == uprv_strcmp(variant, VAR_EURO)) | |
173 | | ((0 == uprv_strcmp(variant, VAR_PRE_EURO)) << 1); | |
174 | if (variantType) | |
175 | { | |
176 | uprv_strcat(countryAndVariant, VAR_DELIM); | |
177 | uprv_strcat(countryAndVariant, variant); | |
178 | } | |
179 | } | |
180 | return variantType; | |
181 | } | |
b75a7d8f A |
182 | |
183 | // ------------------------------------------ | |
184 | // | |
185 | // Registration | |
186 | // | |
187 | //------------------------------------------- | |
188 | ||
189 | // don't use ICUService since we don't need fallback | |
190 | ||
374ca955 A |
191 | #if !UCONFIG_NO_SERVICE |
192 | U_CDECL_BEGIN | |
193 | static UBool U_CALLCONV currency_cleanup(void); | |
194 | U_CDECL_END | |
b75a7d8f A |
195 | struct CReg; |
196 | ||
197 | /* Remember to call umtx_init(&gCRegLock) before using it! */ | |
198 | static UMTX gCRegLock = 0; | |
199 | static CReg* gCRegHead = 0; | |
200 | ||
201 | struct CReg : public UMemory { | |
202 | CReg *next; | |
203 | UChar iso[ISO_COUNTRY_CODE_LENGTH+1]; | |
204 | char id[ULOC_FULLNAME_CAPACITY]; | |
374ca955 | 205 | |
b75a7d8f A |
206 | CReg(const UChar* _iso, const char* _id) |
207 | : next(0) | |
208 | { | |
374ca955 | 209 | int32_t len = (int32_t)uprv_strlen(_id); |
b75a7d8f A |
210 | if (len > (int32_t)(sizeof(id)-1)) { |
211 | len = (sizeof(id)-1); | |
212 | } | |
213 | uprv_strncpy(id, _id, len); | |
214 | id[len] = 0; | |
215 | uprv_memcpy(iso, _iso, ISO_COUNTRY_CODE_LENGTH * sizeof(const UChar)); | |
216 | iso[ISO_COUNTRY_CODE_LENGTH] = 0; | |
217 | } | |
374ca955 | 218 | |
b75a7d8f A |
219 | static UCurrRegistryKey reg(const UChar* _iso, const char* _id, UErrorCode* status) |
220 | { | |
221 | if (status && U_SUCCESS(*status) && _iso && _id) { | |
222 | CReg* n = new CReg(_iso, _id); | |
223 | if (n) { | |
224 | umtx_init(&gCRegLock); | |
225 | Mutex mutex(&gCRegLock); | |
226 | if (!gCRegHead) { | |
374ca955 A |
227 | /* register for the first time */ |
228 | ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY, currency_cleanup); | |
b75a7d8f A |
229 | } |
230 | n->next = gCRegHead; | |
231 | gCRegHead = n; | |
232 | return n; | |
233 | } | |
234 | *status = U_MEMORY_ALLOCATION_ERROR; | |
235 | } | |
236 | return 0; | |
237 | } | |
374ca955 | 238 | |
b75a7d8f A |
239 | static UBool unreg(UCurrRegistryKey key) { |
240 | umtx_init(&gCRegLock); | |
241 | Mutex mutex(&gCRegLock); | |
242 | if (gCRegHead == key) { | |
243 | gCRegHead = gCRegHead->next; | |
244 | delete (CReg*)key; | |
245 | return TRUE; | |
246 | } | |
374ca955 | 247 | |
b75a7d8f A |
248 | CReg* p = gCRegHead; |
249 | while (p) { | |
250 | if (p->next == key) { | |
251 | p->next = ((CReg*)key)->next; | |
374ca955 | 252 | delete (CReg*)key; |
b75a7d8f A |
253 | return TRUE; |
254 | } | |
255 | p = p->next; | |
256 | } | |
374ca955 | 257 | |
b75a7d8f A |
258 | return FALSE; |
259 | } | |
374ca955 | 260 | |
b75a7d8f A |
261 | static const UChar* get(const char* id) { |
262 | umtx_init(&gCRegLock); | |
263 | Mutex mutex(&gCRegLock); | |
264 | CReg* p = gCRegHead; | |
374ca955 A |
265 | |
266 | /* register cleanup of the mutex */ | |
267 | ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY, currency_cleanup); | |
b75a7d8f A |
268 | while (p) { |
269 | if (uprv_strcmp(id, p->id) == 0) { | |
270 | return p->iso; | |
271 | } | |
272 | p = p->next; | |
273 | } | |
274 | return NULL; | |
275 | } | |
276 | ||
277 | /* This doesn't need to be thread safe. It's for u_cleanup only. */ | |
278 | static void cleanup(void) { | |
279 | while (gCRegHead) { | |
280 | CReg* n = gCRegHead; | |
281 | gCRegHead = gCRegHead->next; | |
282 | delete n; | |
283 | } | |
284 | umtx_destroy(&gCRegLock); | |
285 | } | |
286 | }; | |
287 | ||
374ca955 A |
288 | /** |
289 | * Release all static memory held by currency. | |
290 | */ | |
291 | U_CDECL_BEGIN | |
292 | static UBool U_CALLCONV currency_cleanup(void) { | |
293 | #if !UCONFIG_NO_SERVICE | |
294 | CReg::cleanup(); | |
295 | #endif | |
296 | return TRUE; | |
b75a7d8f | 297 | } |
374ca955 | 298 | U_CDECL_END |
b75a7d8f A |
299 | |
300 | // ------------------------------------- | |
301 | ||
302 | U_CAPI UCurrRegistryKey U_EXPORT2 | |
374ca955 | 303 | ucurr_register(const UChar* isoCode, const char* locale, UErrorCode *status) |
b75a7d8f A |
304 | { |
305 | if (status && U_SUCCESS(*status)) { | |
306 | char id[ULOC_FULLNAME_CAPACITY]; | |
307 | idForLocale(locale, id, sizeof(id), status); | |
308 | return CReg::reg(isoCode, id, status); | |
309 | } | |
310 | return NULL; | |
311 | } | |
312 | ||
313 | // ------------------------------------- | |
314 | ||
315 | U_CAPI UBool U_EXPORT2 | |
374ca955 | 316 | ucurr_unregister(UCurrRegistryKey key, UErrorCode* status) |
b75a7d8f A |
317 | { |
318 | if (status && U_SUCCESS(*status)) { | |
319 | return CReg::unreg(key); | |
320 | } | |
321 | return FALSE; | |
322 | } | |
374ca955 | 323 | #endif /* UCONFIG_NO_SERVICE */ |
b75a7d8f A |
324 | |
325 | // ------------------------------------- | |
326 | ||
374ca955 A |
327 | U_CAPI int32_t U_EXPORT2 |
328 | ucurr_forLocale(const char* locale, | |
329 | UChar* buff, | |
330 | int32_t buffCapacity, | |
331 | UErrorCode* ec) | |
332 | { | |
333 | int32_t resLen = 0; | |
334 | const UChar* s = NULL; | |
b75a7d8f | 335 | if (ec != NULL && U_SUCCESS(*ec)) { |
374ca955 A |
336 | if ((buff && buffCapacity) || !buffCapacity) { |
337 | UErrorCode localStatus = U_ZERO_ERROR; | |
338 | char id[ULOC_FULLNAME_CAPACITY]; | |
339 | if ((resLen = uloc_getKeywordValue(locale, "currency", id, ULOC_FULLNAME_CAPACITY, &localStatus))) { | |
340 | // there is a currency keyword. Try to see if it's valid | |
341 | if(buffCapacity > resLen) { | |
342 | u_charsToUChars(id, buff, resLen); | |
343 | } | |
344 | } else { | |
345 | // get country or country_variant in `id' | |
346 | uint32_t variantType = idForLocale(locale, id, sizeof(id), ec); | |
347 | ||
348 | if (U_FAILURE(*ec)) { | |
349 | return 0; | |
350 | } | |
351 | ||
352 | #if !UCONFIG_NO_SERVICE | |
353 | const UChar* result = CReg::get(id); | |
354 | if (result) { | |
355 | if(buffCapacity > u_strlen(result)) { | |
356 | u_strcpy(buff, result); | |
357 | } | |
358 | return u_strlen(result); | |
359 | } | |
360 | #endif | |
361 | ||
362 | // Look up the CurrencyMap element in the root bundle. | |
363 | UResourceBundle *rb = ures_openDirect(NULL, CURRENCY_DATA, &localStatus); | |
364 | UResourceBundle *cm = ures_getByKey(rb, CURRENCY_MAP, rb, &localStatus); | |
365 | s = ures_getStringByKey(cm, id, &resLen, &localStatus); | |
366 | ||
367 | if ((s == NULL || U_FAILURE(localStatus)) && variantType != VARIANT_IS_EMPTY | |
368 | && (id[0] != 0)) | |
369 | { | |
370 | // We don't know about it. Check to see if we support the variant. | |
371 | if (variantType & VARIANT_IS_EURO) { | |
372 | s = ures_getStringByKey(cm, VAR_DELIM_EURO, &resLen, ec); | |
373 | } | |
374 | else { | |
375 | uloc_getParent(locale, id, sizeof(id), ec); | |
376 | *ec = U_USING_FALLBACK_WARNING; | |
377 | ures_close(cm); | |
378 | return ucurr_forLocale(id, buff, buffCapacity, ec); | |
379 | } | |
380 | } | |
381 | else if (*ec == U_ZERO_ERROR || localStatus != U_ZERO_ERROR) { | |
382 | // There is nothing to fallback to. Report the failure/warning if possible. | |
383 | *ec = localStatus; | |
384 | } | |
385 | if (U_SUCCESS(*ec)) { | |
386 | if(buffCapacity > resLen) { | |
387 | u_strcpy(buff, s); | |
388 | } | |
389 | } | |
390 | ures_close(cm); | |
391 | } | |
392 | return u_terminateUChars(buff, buffCapacity, resLen, ec); | |
393 | } else { | |
394 | *ec = U_ILLEGAL_ARGUMENT_ERROR; | |
b75a7d8f A |
395 | } |
396 | } | |
374ca955 | 397 | return resLen; |
b75a7d8f A |
398 | } |
399 | ||
400 | // end registration | |
401 | ||
402 | /** | |
403 | * Modify the given locale name by removing the rightmost _-delimited | |
404 | * element. If there is none, empty the string ("" == root). | |
405 | * NOTE: The string "root" is not recognized; do not use it. | |
406 | * @return TRUE if the fallback happened; FALSE if locale is already | |
407 | * root (""). | |
408 | */ | |
409 | static UBool fallback(char *loc) { | |
410 | if (!*loc) { | |
411 | return FALSE; | |
412 | } | |
413 | UErrorCode status = U_ZERO_ERROR; | |
374ca955 | 414 | uloc_getParent(loc, loc, (int32_t)uprv_strlen(loc), &status); |
b75a7d8f A |
415 | /* |
416 | char *i = uprv_strrchr(loc, '_'); | |
417 | if (i == NULL) { | |
418 | i = loc; | |
419 | } | |
420 | *i = 0; | |
421 | */ | |
422 | return TRUE; | |
423 | } | |
424 | ||
425 | ||
426 | U_CAPI const UChar* U_EXPORT2 | |
427 | ucurr_getName(const UChar* currency, | |
428 | const char* locale, | |
429 | UCurrNameStyle nameStyle, | |
430 | UBool* isChoiceFormat, // fillin | |
431 | int32_t* len, // fillin | |
432 | UErrorCode* ec) { | |
433 | ||
434 | // Look up the Currencies resource for the given locale. The | |
435 | // Currencies locale data looks like this: | |
436 | //|en { | |
374ca955 | 437 | //| Currencies { |
b75a7d8f A |
438 | //| USD { "US$", "US Dollar" } |
439 | //| CHF { "Sw F", "Swiss Franc" } | |
440 | //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" } | |
441 | //| //... | |
442 | //| } | |
443 | //|} | |
444 | ||
374ca955 | 445 | if (U_FAILURE(*ec)) { |
b75a7d8f A |
446 | return 0; |
447 | } | |
448 | ||
449 | int32_t choice = (int32_t) nameStyle; | |
450 | if (choice < 0 || choice > 1) { | |
451 | *ec = U_ILLEGAL_ARGUMENT_ERROR; | |
452 | return 0; | |
453 | } | |
374ca955 | 454 | |
b75a7d8f A |
455 | // In the future, resource bundles may implement multi-level |
456 | // fallback. That is, if a currency is not found in the en_US | |
457 | // Currencies data, then the en Currencies data will be searched. | |
458 | // Currently, if a Currencies datum exists in en_US and en, the | |
459 | // en_US entry hides that in en. | |
460 | ||
461 | // We want multi-level fallback for this resource, so we implement | |
462 | // it manually. | |
463 | ||
464 | // Use a separate UErrorCode here that does not propagate out of | |
465 | // this function. | |
466 | UErrorCode ec2 = U_ZERO_ERROR; | |
467 | ||
468 | char loc[ULOC_FULLNAME_CAPACITY]; | |
469 | uloc_getName(locale, loc, sizeof(loc), &ec2); | |
470 | if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) { | |
471 | *ec = U_ILLEGAL_ARGUMENT_ERROR; | |
472 | return 0; | |
473 | } | |
474 | ||
475 | char buf[ISO_COUNTRY_CODE_LENGTH+1]; | |
476 | myUCharsToChars(buf, currency); | |
477 | ||
478 | const UChar* s = NULL; | |
479 | ||
480 | // Multi-level resource inheritance fallback loop | |
481 | for (;;) { | |
482 | ec2 = U_ZERO_ERROR; | |
483 | UResourceBundle* rb = ures_open(NULL, loc, &ec2); | |
374ca955 A |
484 | rb = ures_getByKey(rb, CURRENCIES, rb, &ec2); |
485 | rb = ures_getByKey(rb, buf, rb, &ec2); | |
486 | s = ures_getStringByIndex(rb, choice, len, &ec2); | |
b75a7d8f A |
487 | ures_close(rb); |
488 | ||
489 | // If we've succeeded we're done. Otherwise, try to fallback. | |
490 | // If that fails (because we are already at root) then exit. | |
491 | if (U_SUCCESS(ec2) || !fallback(loc)) { | |
374ca955 A |
492 | if (ec2 == U_USING_DEFAULT_WARNING |
493 | || (ec2 == U_USING_FALLBACK_WARNING && *ec != U_USING_DEFAULT_WARNING)) { | |
494 | *ec = ec2; | |
495 | } | |
b75a7d8f | 496 | break; |
374ca955 A |
497 | } else if (strlen(loc) == 0) { |
498 | *ec = U_USING_DEFAULT_WARNING; | |
499 | } else if (*ec != U_USING_DEFAULT_WARNING) { | |
500 | *ec = U_USING_FALLBACK_WARNING; | |
b75a7d8f A |
501 | } |
502 | } | |
503 | ||
504 | // Determine if this is a ChoiceFormat pattern. One leading mark | |
505 | // indicates a ChoiceFormat. Two indicates a static string that | |
506 | // starts with a mark. In either case, the first mark is ignored, | |
507 | // if present. Marks in the rest of the string have no special | |
508 | // meaning. | |
509 | *isChoiceFormat = FALSE; | |
510 | if (U_SUCCESS(ec2)) { | |
511 | U_ASSERT(s != NULL); | |
512 | int32_t i=0; | |
513 | while (i < *len && s[i] == CHOICE_FORMAT_MARK && i < 2) { | |
514 | ++i; | |
515 | } | |
516 | *isChoiceFormat = (i == 1); | |
517 | if (i != 0) ++s; // Skip over first mark | |
518 | return s; | |
519 | } | |
520 | ||
521 | // If we fail to find a match, use the ISO 4217 code | |
522 | *len = u_strlen(currency); // Should == ISO_COUNTRY_CODE_LENGTH, but maybe not...? | |
374ca955 | 523 | *ec = U_USING_DEFAULT_WARNING; |
b75a7d8f A |
524 | return currency; |
525 | } | |
526 | ||
374ca955 A |
527 | U_NAMESPACE_BEGIN |
528 | ||
529 | void | |
530 | uprv_parseCurrency(const char* locale, | |
531 | const UnicodeString& text, | |
532 | ParsePosition& pos, | |
533 | UChar* result, | |
534 | UErrorCode& ec) { | |
535 | ||
536 | // TODO: There is a slight problem with the pseudo-multi-level | |
537 | // fallback implemented here. More-specific locales don't | |
538 | // properly shield duplicate entries in less-specific locales. | |
539 | // This problem will go away when real multi-level fallback is | |
540 | // implemented. We could also fix this by recording (in a | |
541 | // hash) which codes are used at each level of fallback, but | |
542 | // this doesn't seem warranted. | |
543 | ||
544 | if (U_FAILURE(ec)) { | |
545 | return; | |
546 | } | |
547 | ||
548 | // Look up the Currencies resource for the given locale. The | |
549 | // Currencies locale data looks like this: | |
550 | //|en { | |
551 | //| Currencies { | |
552 | //| USD { "US$", "US Dollar" } | |
553 | //| CHF { "Sw F", "Swiss Franc" } | |
554 | //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" } | |
555 | //| //... | |
556 | //| } | |
557 | //|} | |
558 | ||
559 | // In the future, resource bundles may implement multi-level | |
560 | // fallback. That is, if a currency is not found in the en_US | |
561 | // Currencies data, then the en Currencies data will be searched. | |
562 | // Currently, if a Currencies datum exists in en_US and en, the | |
563 | // en_US entry hides that in en. | |
564 | ||
565 | // We want multi-level fallback for this resource, so we implement | |
566 | // it manually. | |
567 | ||
568 | // Use a separate UErrorCode here that does not propagate out of | |
569 | // this function. | |
570 | UErrorCode ec2 = U_ZERO_ERROR; | |
571 | ||
572 | char loc[ULOC_FULLNAME_CAPACITY]; | |
573 | uloc_getName(locale, loc, sizeof(loc), &ec2); | |
574 | if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) { | |
575 | ec = U_ILLEGAL_ARGUMENT_ERROR; | |
576 | return; | |
577 | } | |
578 | ||
579 | int32_t start = pos.getIndex(); | |
580 | const UChar* s = NULL; | |
581 | ||
582 | const char* iso = NULL; | |
583 | int32_t max = 0; | |
584 | ||
585 | // Multi-level resource inheritance fallback loop | |
586 | for (;;) { | |
587 | ec2 = U_ZERO_ERROR; | |
588 | UResourceBundle* rb = ures_open(NULL, loc, &ec2); | |
589 | UResourceBundle* curr = ures_getByKey(rb, CURRENCIES, NULL, &ec2); | |
590 | int32_t n = ures_getSize(curr); | |
591 | for (int32_t i=0; i<n; ++i) { | |
592 | UResourceBundle* names = ures_getByIndex(curr, i, NULL, &ec2); | |
593 | int32_t len; | |
594 | s = ures_getStringByIndex(names, UCURR_SYMBOL_NAME, &len, &ec2); | |
595 | UBool isChoice = FALSE; | |
596 | if (len > 0 && s[0] == CHOICE_FORMAT_MARK) { | |
597 | ++s; | |
598 | --len; | |
599 | if (len > 0 && s[0] != CHOICE_FORMAT_MARK) { | |
600 | isChoice = TRUE; | |
601 | } | |
602 | } | |
603 | if (isChoice) { | |
604 | Formattable temp; | |
605 | ChoiceFormat fmt(s, ec2); | |
606 | fmt.parse(text, temp, pos); | |
607 | len = pos.getIndex() - start; | |
608 | pos.setIndex(start); | |
609 | } else if (len > max && | |
610 | text.compare(pos.getIndex(), len, s) != 0) { | |
611 | len = 0; | |
612 | } | |
613 | if (len > max) { | |
614 | iso = ures_getKey(names); | |
615 | max = len; | |
616 | } | |
617 | ures_close(names); | |
618 | } | |
619 | ures_close(curr); | |
620 | ures_close(rb); | |
621 | ||
622 | // Try to fallback. If that fails (because we are already at | |
623 | // root) then exit. | |
624 | if (!fallback(loc)) { | |
625 | break; | |
626 | } | |
627 | } | |
628 | ||
629 | if (iso != NULL) { | |
630 | u_charsToUChars(iso, result, 4); | |
631 | } | |
632 | ||
633 | // If display name parse fails or if it matches fewer than 3 | |
634 | // characters, try to parse 3-letter ISO. Do this after the | |
635 | // display name processing so 3-letter display names are | |
636 | // preferred. Consider /[A-Z]{3}/ to be valid ISO, and parse | |
637 | // it manually--UnicodeSet/regex are too slow and heavy. | |
638 | if (max < 3 && (text.length() - start) >= 3) { | |
639 | UBool valid = TRUE; | |
640 | for (int32_t k=0; k<3; ++k) { | |
641 | UChar ch = text.charAt(start + k); // 16-bit ok | |
642 | if (ch < 0x41/*'A'*/ || ch > 0x5A/*'Z'*/) { | |
643 | valid = FALSE; | |
644 | break; | |
645 | } | |
646 | } | |
647 | if (valid) { | |
648 | text.extract(start, 3, result); | |
649 | result[3] = 0; | |
650 | max = 3; | |
651 | } | |
652 | } | |
653 | ||
654 | pos.setIndex(start + max); | |
655 | } | |
656 | ||
657 | U_NAMESPACE_END | |
658 | ||
659 | /** | |
660 | * Internal method. Given a currency ISO code and a locale, return | |
661 | * the "static" currency name. This is usually the same as the | |
662 | * UCURR_SYMBOL_NAME, but if the latter is a choice format, then the | |
663 | * format is applied to the number 2.0 (to yield the more common | |
664 | * plural) to return a static name. | |
665 | * | |
666 | * This is used for backward compatibility with old currency logic in | |
667 | * DecimalFormat and DecimalFormatSymbols. | |
668 | */ | |
669 | U_CAPI void | |
670 | uprv_getStaticCurrencyName(const UChar* iso, const char* loc, | |
671 | UnicodeString& result, UErrorCode& ec) | |
672 | { | |
673 | UBool isChoiceFormat; | |
674 | int32_t len; | |
675 | const UChar* currname = ucurr_getName(iso, loc, UCURR_SYMBOL_NAME, | |
676 | &isChoiceFormat, &len, &ec); | |
677 | if (U_SUCCESS(ec)) { | |
678 | // If this is a ChoiceFormat currency, then format an | |
679 | // arbitrary value; pick something != 1; more common. | |
680 | result.truncate(0); | |
681 | if (isChoiceFormat) { | |
682 | ChoiceFormat f(currname, ec); | |
683 | if (U_SUCCESS(ec)) { | |
684 | f.format(2.0, result); | |
685 | } else { | |
686 | result = iso; | |
687 | } | |
688 | } else { | |
689 | result = currname; | |
690 | } | |
691 | } | |
692 | } | |
b75a7d8f A |
693 | |
694 | U_CAPI int32_t U_EXPORT2 | |
374ca955 A |
695 | ucurr_getDefaultFractionDigits(const UChar* currency, UErrorCode* ec) { |
696 | return (_findMetaData(currency, *ec))[0]; | |
b75a7d8f A |
697 | } |
698 | ||
699 | U_CAPI double U_EXPORT2 | |
374ca955 A |
700 | ucurr_getRoundingIncrement(const UChar* currency, UErrorCode* ec) { |
701 | const int32_t *data = _findMetaData(currency, *ec); | |
b75a7d8f | 702 | |
374ca955 A |
703 | // If the meta data is invalid, return 0.0. |
704 | if (data[0] < 0 || data[0] > MAX_POW10) { | |
705 | if (U_SUCCESS(*ec)) { | |
706 | *ec = U_INVALID_FORMAT_ERROR; | |
707 | } | |
708 | return 0.0; | |
709 | } | |
710 | ||
711 | // If there is no rounding, return 0.0 to indicate no rounding. A | |
712 | // rounding value (data[1]) of 0 or 1 indicates no rounding. | |
713 | if (data[1] < 2) { | |
b75a7d8f A |
714 | return 0.0; |
715 | } | |
716 | ||
717 | // Return data[1] / 10^(data[0]). The only actual rounding data, | |
718 | // as of this writing, is CHF { 2, 5 }. | |
719 | return double(data[1]) / POW10[data[0]]; | |
720 | } | |
721 | ||
b75a7d8f A |
722 | #endif /* #if !UCONFIG_NO_FORMATTING */ |
723 | ||
724 | //eof |