]> git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/ucurr.cpp
ICU-3.13.tar.gz
[apple/icu.git] / icuSources / i18n / ucurr.cpp
1 /*
2 **********************************************************************
3 * Copyright (c) 2002-2003, International Business Machines
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"
14 #include "unicode/resbund.h"
15 #include "unicode/ustring.h"
16 #include "cmemory.h"
17 #include "cstring.h"
18 #include "uassert.h"
19 #include "iculserv.h"
20 #include "ucln_in.h"
21
22 //------------------------------------------------------------
23 // Constants
24
25 // Default currency meta data of last resort. We try to use the
26 // defaults encoded in the meta data resource bundle. If there is a
27 // configuration/build error and these are not available, we use these
28 // hard-coded defaults (which should be identical).
29 static const int32_t LAST_RESORT_DATA[] = { 2, 0 };
30
31 // POW10[i] = 10^i, i=0..MAX_POW10
32 static const int32_t POW10[] = { 1, 10, 100, 1000, 10000, 100000,
33 1000000, 10000000, 100000000, 1000000000 };
34
35 static const int32_t MAX_POW10 = (sizeof(POW10)/sizeof(POW10[0])) - 1;
36
37 #define ISO_COUNTRY_CODE_LENGTH 3
38
39 //------------------------------------------------------------
40 // Resource tags
41
42 // Tag for meta-data, in root.
43 static const char CURRENCY_META[] = "CurrencyMeta";
44
45 // Tag for map from countries to currencies, in root.
46 static const char CURRENCY_MAP[] = "CurrencyMap";
47
48 // Tag for default meta-data, in CURRENCY_META
49 static const char DEFAULT_META[] = "DEFAULT";
50
51 // Variant for legacy pre-euro mapping in CurrencyMap
52 static const char VAR_PRE_EURO[] = "PREEURO";
53
54 // Variant for legacy euro mapping in CurrencyMap
55 static const char VAR_EURO[] = "EURO";
56
57 // Variant delimiter
58 static const char VAR_DELIM[] = "_";
59
60 // Tag for localized display names (symbols) of currencies
61 static const char CURRENCIES[] = "Currencies";
62
63 // Marker character indicating that a display name is a ChoiceFormat
64 // pattern. Strings that start with one mark are ChoiceFormat
65 // patterns. Strings that start with 2 marks are static strings, and
66 // the first mark is deleted.
67 static const UChar CHOICE_FORMAT_MARK = 0x003D; // Equals sign
68
69 //------------------------------------------------------------
70 // Code
71
72 /**
73 * Unfortunately, we have to convert the UChar* currency code to char*
74 * to use it as a resource key.
75 */
76 static inline char*
77 myUCharsToChars(char* resultOfLen4, const UChar* currency) {
78 u_UCharsToChars(currency, resultOfLen4, ISO_COUNTRY_CODE_LENGTH);
79 resultOfLen4[ISO_COUNTRY_CODE_LENGTH] = 0;
80 return resultOfLen4;
81 }
82
83 /**
84 * Internal function to look up currency data. Result is an array of
85 * two integers. The first is the fraction digits. The second is the
86 * rounding increment, or 0 if none. The rounding increment is in
87 * units of 10^(-fraction_digits).
88 */
89 static const int32_t*
90 _findMetaData(const UChar* currency) {
91
92 // Get CurrencyMeta resource out of root locale file. [This may
93 // move out of the root locale file later; if it does, update this
94 // code.]
95 UErrorCode ec = U_ZERO_ERROR;
96 ResourceBundle currencyMeta =
97 ResourceBundle((char*)0, Locale(""), ec).get(CURRENCY_META, ec);
98
99 if (U_FAILURE(ec)) {
100 // Config/build error; return hard-coded defaults
101 return LAST_RESORT_DATA;
102 }
103
104 // Look up our currency, or if that's not available, then DEFAULT
105 char buf[ISO_COUNTRY_CODE_LENGTH+1];
106 ResourceBundle rb = currencyMeta.get(myUCharsToChars(buf, currency), ec);
107 if (U_FAILURE(ec)) {
108 rb = currencyMeta.get(DEFAULT_META, ec);
109 if (U_FAILURE(ec)) {
110 // Config/build error; return hard-coded defaults
111 return LAST_RESORT_DATA;
112 }
113 }
114
115 int32_t len;
116 const int32_t *data = rb.getIntVector(len, ec);
117 if (U_FAILURE(ec) || len < 2) {
118 // Config/build error; return hard-coded defaults
119 return LAST_RESORT_DATA;
120 }
121
122 return data;
123 }
124
125
126 // ------------------------------------------
127 //
128 // Registration
129 //
130 //-------------------------------------------
131
132 // don't use ICUService since we don't need fallback
133
134 struct CReg;
135
136 /* Remember to call umtx_init(&gCRegLock) before using it! */
137 static UMTX gCRegLock = 0;
138 static CReg* gCRegHead = 0;
139
140 struct CReg : public UMemory {
141 CReg *next;
142 UChar iso[ISO_COUNTRY_CODE_LENGTH+1];
143 char id[ULOC_FULLNAME_CAPACITY];
144
145 CReg(const UChar* _iso, const char* _id)
146 : next(0)
147 {
148 int32_t len = uprv_strlen(_id);
149 if (len > (int32_t)(sizeof(id)-1)) {
150 len = (sizeof(id)-1);
151 }
152 uprv_strncpy(id, _id, len);
153 id[len] = 0;
154 uprv_memcpy(iso, _iso, ISO_COUNTRY_CODE_LENGTH * sizeof(const UChar));
155 iso[ISO_COUNTRY_CODE_LENGTH] = 0;
156 }
157
158 static UCurrRegistryKey reg(const UChar* _iso, const char* _id, UErrorCode* status)
159 {
160 if (status && U_SUCCESS(*status) && _iso && _id) {
161 CReg* n = new CReg(_iso, _id);
162 if (n) {
163 umtx_init(&gCRegLock);
164 Mutex mutex(&gCRegLock);
165 if (!gCRegHead) {
166 ucln_i18n_registerCleanup();
167 }
168 n->next = gCRegHead;
169 gCRegHead = n;
170 return n;
171 }
172 *status = U_MEMORY_ALLOCATION_ERROR;
173 }
174 return 0;
175 }
176
177 static UBool unreg(UCurrRegistryKey key) {
178 umtx_init(&gCRegLock);
179 Mutex mutex(&gCRegLock);
180 if (gCRegHead == key) {
181 gCRegHead = gCRegHead->next;
182 delete (CReg*)key;
183 return TRUE;
184 }
185
186 CReg* p = gCRegHead;
187 while (p) {
188 if (p->next == key) {
189 p->next = ((CReg*)key)->next;
190 delete (CReg*)key;
191 return TRUE;
192 }
193 p = p->next;
194 }
195
196 return FALSE;
197 }
198
199 static const UChar* get(const char* id) {
200 umtx_init(&gCRegLock);
201 Mutex mutex(&gCRegLock);
202 CReg* p = gCRegHead;
203 while (p) {
204 if (uprv_strcmp(id, p->id) == 0) {
205 return p->iso;
206 }
207 p = p->next;
208 }
209 return NULL;
210 }
211
212 /* This doesn't need to be thread safe. It's for u_cleanup only. */
213 static void cleanup(void) {
214 while (gCRegHead) {
215 CReg* n = gCRegHead;
216 gCRegHead = gCRegHead->next;
217 delete n;
218 }
219 umtx_destroy(&gCRegLock);
220 }
221 };
222
223 // -------------------------------------
224
225 static void
226 idForLocale(const char* locale, char* buffer, int capacity, UErrorCode* ec)
227 {
228 // !!! this is internal only, assumes buffer is not null and capacity is sufficient
229 // Extract the country name and variant name. We only
230 // recognize two variant names, EURO and PREEURO.
231 char variant[ULOC_FULLNAME_CAPACITY];
232 uloc_getCountry(locale, buffer, capacity, ec);
233 uloc_getVariant(locale, variant, sizeof(variant), ec);
234 if (0 == uprv_strcmp(variant, VAR_PRE_EURO) ||
235 0 == uprv_strcmp(variant, VAR_EURO))
236 {
237 uprv_strcat(buffer, VAR_DELIM);
238 uprv_strcat(buffer, variant);
239 }
240 }
241
242 // -------------------------------------
243
244 U_CAPI UCurrRegistryKey U_EXPORT2
245 ucurr_register(const UChar* isoCode, const char* locale, UErrorCode *status)
246 {
247 if (status && U_SUCCESS(*status)) {
248 char id[ULOC_FULLNAME_CAPACITY];
249 idForLocale(locale, id, sizeof(id), status);
250 return CReg::reg(isoCode, id, status);
251 }
252 return NULL;
253 }
254
255 // -------------------------------------
256
257 U_CAPI UBool U_EXPORT2
258 ucurr_unregister(UCurrRegistryKey key, UErrorCode* status)
259 {
260 if (status && U_SUCCESS(*status)) {
261 return CReg::unreg(key);
262 }
263 return FALSE;
264 }
265
266 // -------------------------------------
267
268 U_CAPI const UChar* U_EXPORT2
269 ucurr_forLocale(const char* locale, UErrorCode* ec) {
270 if (ec != NULL && U_SUCCESS(*ec)) {
271 char id[ULOC_FULLNAME_CAPACITY];
272 idForLocale(locale, id, sizeof(id), ec);
273 if (U_FAILURE(*ec)) {
274 return NULL;
275 }
276
277 const UChar* result = CReg::get(id);
278 if (result) {
279 return result;
280 }
281
282 // Look up the CurrencyMap element in the root bundle.
283 UResourceBundle* rb = ures_open(NULL, "", ec);
284 UResourceBundle* cm = ures_getByKey(rb, CURRENCY_MAP, NULL, ec);
285 int32_t len;
286 const UChar* s = ures_getStringByKey(cm, id, &len, ec);
287 ures_close(cm);
288 ures_close(rb);
289
290 if (U_SUCCESS(*ec)) {
291 return s;
292 }
293 }
294 return NULL;
295 }
296
297 // end registration
298
299 /**
300 * Modify the given locale name by removing the rightmost _-delimited
301 * element. If there is none, empty the string ("" == root).
302 * NOTE: The string "root" is not recognized; do not use it.
303 * @return TRUE if the fallback happened; FALSE if locale is already
304 * root ("").
305 */
306 static UBool fallback(char *loc) {
307 if (!*loc) {
308 return FALSE;
309 }
310 UErrorCode status = U_ZERO_ERROR;
311 uloc_getParent(loc, loc, uprv_strlen(loc), &status);
312 /*
313 char *i = uprv_strrchr(loc, '_');
314 if (i == NULL) {
315 i = loc;
316 }
317 *i = 0;
318 */
319 return TRUE;
320 }
321
322
323 U_CAPI const UChar* U_EXPORT2
324 ucurr_getName(const UChar* currency,
325 const char* locale,
326 UCurrNameStyle nameStyle,
327 UBool* isChoiceFormat, // fillin
328 int32_t* len, // fillin
329 UErrorCode* ec) {
330
331 // Look up the Currencies resource for the given locale. The
332 // Currencies locale data looks like this:
333 //|en {
334 //| Currencies {
335 //| USD { "US$", "US Dollar" }
336 //| CHF { "Sw F", "Swiss Franc" }
337 //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" }
338 //| //...
339 //| }
340 //|}
341
342 if (ec == NULL || U_FAILURE(*ec)) {
343 return 0;
344 }
345
346 int32_t choice = (int32_t) nameStyle;
347 if (choice < 0 || choice > 1) {
348 *ec = U_ILLEGAL_ARGUMENT_ERROR;
349 return 0;
350 }
351
352 // In the future, resource bundles may implement multi-level
353 // fallback. That is, if a currency is not found in the en_US
354 // Currencies data, then the en Currencies data will be searched.
355 // Currently, if a Currencies datum exists in en_US and en, the
356 // en_US entry hides that in en.
357
358 // We want multi-level fallback for this resource, so we implement
359 // it manually.
360
361 // Use a separate UErrorCode here that does not propagate out of
362 // this function.
363 UErrorCode ec2 = U_ZERO_ERROR;
364
365 char loc[ULOC_FULLNAME_CAPACITY];
366 uloc_getName(locale, loc, sizeof(loc), &ec2);
367 if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) {
368 *ec = U_ILLEGAL_ARGUMENT_ERROR;
369 return 0;
370 }
371
372 char buf[ISO_COUNTRY_CODE_LENGTH+1];
373 myUCharsToChars(buf, currency);
374
375 const UChar* s = NULL;
376
377 // Multi-level resource inheritance fallback loop
378 for (;;) {
379 ec2 = U_ZERO_ERROR;
380 UResourceBundle* rb = ures_open(NULL, loc, &ec2);
381 UResourceBundle* curr = ures_getByKey(rb, CURRENCIES, NULL, &ec2);
382 UResourceBundle* names = ures_getByKey(curr, buf, NULL, &ec2);
383 s = ures_getStringByIndex(names, choice, len, &ec2);
384 ures_close(names);
385 ures_close(curr);
386 ures_close(rb);
387
388 // If we've succeeded we're done. Otherwise, try to fallback.
389 // If that fails (because we are already at root) then exit.
390 if (U_SUCCESS(ec2) || !fallback(loc)) {
391 break;
392 }
393 }
394
395 // Determine if this is a ChoiceFormat pattern. One leading mark
396 // indicates a ChoiceFormat. Two indicates a static string that
397 // starts with a mark. In either case, the first mark is ignored,
398 // if present. Marks in the rest of the string have no special
399 // meaning.
400 *isChoiceFormat = FALSE;
401 if (U_SUCCESS(ec2)) {
402 U_ASSERT(s != NULL);
403 int32_t i=0;
404 while (i < *len && s[i] == CHOICE_FORMAT_MARK && i < 2) {
405 ++i;
406 }
407 *isChoiceFormat = (i == 1);
408 if (i != 0) ++s; // Skip over first mark
409 return s;
410 }
411
412 // If we fail to find a match, use the ISO 4217 code
413 *len = u_strlen(currency); // Should == ISO_COUNTRY_CODE_LENGTH, but maybe not...?
414 return currency;
415 }
416
417 //!// This API is now redundant. It predates ucurr_getName, which
418 //!// replaces and extends it.
419 //!U_CAPI const UChar* U_EXPORT2
420 //!ucurr_getSymbol(const UChar* currency,
421 //! const char* locale,
422 //! int32_t* len, // fillin
423 //! UErrorCode* ec) {
424 //! UBool isChoiceFormat;
425 //! const UChar* s = ucurr_getName(currency, locale, UCURR_SYMBOL_NAME,
426 //! &isChoiceFormat, len, ec);
427 //! if (isChoiceFormat) {
428 //! // Don't let ChoiceFormat patterns out through this API
429 //! *len = u_strlen(currency); // Should == 3, but maybe not...?
430 //! return currency;
431 //! }
432 //! return s;
433 //!}
434
435 U_CAPI int32_t U_EXPORT2
436 ucurr_getDefaultFractionDigits(const UChar* currency) {
437 return (_findMetaData(currency))[0];
438 }
439
440 U_CAPI double U_EXPORT2
441 ucurr_getRoundingIncrement(const UChar* currency) {
442 const int32_t *data = _findMetaData(currency);
443
444 // If there is no rounding, or if the meta data is invalid,
445 // return 0.0 to indicate no rounding. A rounding value
446 // (data[1]) of 0 or 1 indicates no rounding.
447 if (data[1] < 2 || data[0] < 0 || data[0] > MAX_POW10) {
448 return 0.0;
449 }
450
451 // Return data[1] / 10^(data[0]). The only actual rounding data,
452 // as of this writing, is CHF { 2, 5 }.
453 return double(data[1]) / POW10[data[0]];
454 }
455
456 /**
457 * Release all static memory held by currency.
458 */
459 U_CFUNC UBool currency_cleanup(void) {
460 CReg::cleanup();
461 return TRUE;
462 }
463
464 #endif /* #if !UCONFIG_NO_FORMATTING */
465
466 //eof