2 **********************************************************************
3 * Copyright (c) 2002-2004, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
12 #include "unicode/ucurr.h"
13 #include "unicode/locid.h"
14 #include "unicode/ures.h"
15 #include "unicode/ustring.h"
16 #include "unicode/choicfmt.h"
17 #include "unicode/parsepos.h"
25 //------------------------------------------------------------
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 };
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 };
38 static const int32_t MAX_POW10
= (sizeof(POW10
)/sizeof(POW10
[0])) - 1;
40 #define ISO_COUNTRY_CODE_LENGTH 3
42 //------------------------------------------------------------
46 static const char CURRENCY_DATA
[] = "CurrencyData";
47 // Tag for meta-data, in root.
48 static const char CURRENCY_META
[] = "CurrencyMeta";
50 // Tag for map from countries to currencies, in root.
51 static const char CURRENCY_MAP
[] = "CurrencyMap";
53 // Tag for default meta-data, in CURRENCY_META
54 static const char DEFAULT_META
[] = "DEFAULT";
56 // Variant for legacy pre-euro mapping in CurrencyMap
57 static const char VAR_PRE_EURO
[] = "PREEURO";
59 // Variant for legacy euro mapping in CurrencyMap
60 static const char VAR_EURO
[] = "EURO";
63 static const char VAR_DELIM
[] = "_";
65 // Variant for legacy euro mapping in CurrencyMap
66 static const char VAR_DELIM_EURO
[] = "_EURO";
68 #define VARIANT_IS_EMPTY 0
69 #define VARIANT_IS_EURO 0x1
70 #define VARIANT_IS_PREEURO 0x2
72 // Tag for localized display names (symbols) of currencies
73 static const char CURRENCIES
[] = "Currencies";
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
81 //------------------------------------------------------------
85 * Unfortunately, we have to convert the UChar* currency code to char*
86 * to use it as a resource key.
89 myUCharsToChars(char* resultOfLen4
, const UChar
* currency
) {
90 u_UCharsToChars(currency
, resultOfLen4
, ISO_COUNTRY_CODE_LENGTH
);
91 resultOfLen4
[ISO_COUNTRY_CODE_LENGTH
] = 0;
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).
101 static const int32_t*
102 _findMetaData(const UChar
* currency
, UErrorCode
& ec
) {
104 if (currency
== 0 || *currency
== 0) {
106 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
108 return LAST_RESORT_DATA
;
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
114 UResourceBundle
* currencyData
= ures_openDirect(NULL
, CURRENCY_DATA
, &ec
);
115 UResourceBundle
* currencyMeta
= ures_getByKey(currencyData
, CURRENCY_META
, currencyData
, &ec
);
118 ures_close(currencyMeta
);
119 // Config/build error; return hard-coded defaults
120 return LAST_RESORT_DATA
;
123 // Look up our currency, or if that's not available, then DEFAULT
124 char buf
[ISO_COUNTRY_CODE_LENGTH
+1];
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
)) {
129 rb
= ures_getByKey(currencyMeta
,DEFAULT_META
, NULL
, &ec
);
131 ures_close(currencyMeta
);
133 // Config/build error; return hard-coded defaults
134 return LAST_RESORT_DATA
;
139 const int32_t *data
= ures_getIntVector(rb
, &len
, &ec
);
140 if (U_FAILURE(ec
) || len
!= 2) {
141 // Config/build error; return hard-coded defaults
143 ec
= U_INVALID_FORMAT_ERROR
;
145 ures_close(currencyMeta
);
147 return LAST_RESORT_DATA
;
150 ures_close(currencyMeta
);
155 // -------------------------------------
158 * @see VARIANT_IS_EURO
159 * @see VARIANT_IS_PREEURO
162 idForLocale(const char* locale
, char* countryAndVariant
, int capacity
, UErrorCode
* ec
)
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);
176 uprv_strcat(countryAndVariant
, VAR_DELIM
);
177 uprv_strcat(countryAndVariant
, variant
);
183 // ------------------------------------------
187 //-------------------------------------------
189 // don't use ICUService since we don't need fallback
191 #if !UCONFIG_NO_SERVICE
193 static UBool U_CALLCONV
currency_cleanup(void);
197 /* Remember to call umtx_init(&gCRegLock) before using it! */
198 static UMTX gCRegLock
= 0;
199 static CReg
* gCRegHead
= 0;
201 struct CReg
: public UMemory
{
203 UChar iso
[ISO_COUNTRY_CODE_LENGTH
+1];
204 char id
[ULOC_FULLNAME_CAPACITY
];
206 CReg(const UChar
* _iso
, const char* _id
)
209 int32_t len
= (int32_t)uprv_strlen(_id
);
210 if (len
> (int32_t)(sizeof(id
)-1)) {
211 len
= (sizeof(id
)-1);
213 uprv_strncpy(id
, _id
, len
);
215 uprv_memcpy(iso
, _iso
, ISO_COUNTRY_CODE_LENGTH
* sizeof(const UChar
));
216 iso
[ISO_COUNTRY_CODE_LENGTH
] = 0;
219 static UCurrRegistryKey
reg(const UChar
* _iso
, const char* _id
, UErrorCode
* status
)
221 if (status
&& U_SUCCESS(*status
) && _iso
&& _id
) {
222 CReg
* n
= new CReg(_iso
, _id
);
224 umtx_init(&gCRegLock
);
225 Mutex
mutex(&gCRegLock
);
227 /* register for the first time */
228 ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY
, currency_cleanup
);
234 *status
= U_MEMORY_ALLOCATION_ERROR
;
239 static UBool
unreg(UCurrRegistryKey key
) {
240 umtx_init(&gCRegLock
);
241 Mutex
mutex(&gCRegLock
);
242 if (gCRegHead
== key
) {
243 gCRegHead
= gCRegHead
->next
;
250 if (p
->next
== key
) {
251 p
->next
= ((CReg
*)key
)->next
;
261 static const UChar
* get(const char* id
) {
262 umtx_init(&gCRegLock
);
263 Mutex
mutex(&gCRegLock
);
266 /* register cleanup of the mutex */
267 ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY
, currency_cleanup
);
269 if (uprv_strcmp(id
, p
->id
) == 0) {
277 /* This doesn't need to be thread safe. It's for u_cleanup only. */
278 static void cleanup(void) {
281 gCRegHead
= gCRegHead
->next
;
284 umtx_destroy(&gCRegLock
);
289 * Release all static memory held by currency.
292 static UBool U_CALLCONV
currency_cleanup(void) {
293 #if !UCONFIG_NO_SERVICE
300 // -------------------------------------
302 U_CAPI UCurrRegistryKey U_EXPORT2
303 ucurr_register(const UChar
* isoCode
, const char* locale
, UErrorCode
*status
)
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
);
313 // -------------------------------------
315 U_CAPI UBool U_EXPORT2
316 ucurr_unregister(UCurrRegistryKey key
, UErrorCode
* status
)
318 if (status
&& U_SUCCESS(*status
)) {
319 return CReg::unreg(key
);
323 #endif /* UCONFIG_NO_SERVICE */
325 // -------------------------------------
327 U_CAPI
int32_t U_EXPORT2
328 ucurr_forLocale(const char* locale
,
330 int32_t buffCapacity
,
334 const UChar
* s
= NULL
;
335 if (ec
!= NULL
&& U_SUCCESS(*ec
)) {
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
);
345 // get country or country_variant in `id'
346 uint32_t variantType
= idForLocale(locale
, id
, sizeof(id
), ec
);
348 if (U_FAILURE(*ec
)) {
352 #if !UCONFIG_NO_SERVICE
353 const UChar
* result
= CReg::get(id
);
355 if(buffCapacity
> u_strlen(result
)) {
356 u_strcpy(buff
, result
);
358 return u_strlen(result
);
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
);
367 if ((s
== NULL
|| U_FAILURE(localStatus
)) && variantType
!= VARIANT_IS_EMPTY
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
);
375 uloc_getParent(locale
, id
, sizeof(id
), ec
);
376 *ec
= U_USING_FALLBACK_WARNING
;
378 return ucurr_forLocale(id
, buff
, buffCapacity
, ec
);
381 else if (*ec
== U_ZERO_ERROR
|| localStatus
!= U_ZERO_ERROR
) {
382 // There is nothing to fallback to. Report the failure/warning if possible.
385 if (U_SUCCESS(*ec
)) {
386 if(buffCapacity
> resLen
) {
392 return u_terminateUChars(buff
, buffCapacity
, resLen
, ec
);
394 *ec
= U_ILLEGAL_ARGUMENT_ERROR
;
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
409 static UBool
fallback(char *loc
) {
413 UErrorCode status
= U_ZERO_ERROR
;
414 uloc_getParent(loc
, loc
, (int32_t)uprv_strlen(loc
), &status
);
416 char *i = uprv_strrchr(loc, '_');
426 U_CAPI
const UChar
* U_EXPORT2
427 ucurr_getName(const UChar
* currency
,
429 UCurrNameStyle nameStyle
,
430 UBool
* isChoiceFormat
, // fillin
431 int32_t* len
, // fillin
434 // Look up the Currencies resource for the given locale. The
435 // Currencies locale data looks like this:
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" }
445 if (U_FAILURE(*ec
)) {
449 int32_t choice
= (int32_t) nameStyle
;
450 if (choice
< 0 || choice
> 1) {
451 *ec
= U_ILLEGAL_ARGUMENT_ERROR
;
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.
461 // We want multi-level fallback for this resource, so we implement
464 // Use a separate UErrorCode here that does not propagate out of
466 UErrorCode ec2
= U_ZERO_ERROR
;
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
;
475 char buf
[ISO_COUNTRY_CODE_LENGTH
+1];
476 myUCharsToChars(buf
, currency
);
478 const UChar
* s
= NULL
;
480 // Multi-level resource inheritance fallback loop
483 UResourceBundle
* rb
= ures_open(NULL
, loc
, &ec2
);
484 rb
= ures_getByKey(rb
, CURRENCIES
, rb
, &ec2
);
485 rb
= ures_getByKey(rb
, buf
, rb
, &ec2
);
486 s
= ures_getStringByIndex(rb
, choice
, len
, &ec2
);
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
)) {
492 if (ec2
== U_USING_DEFAULT_WARNING
493 || (ec2
== U_USING_FALLBACK_WARNING
&& *ec
!= U_USING_DEFAULT_WARNING
)) {
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
;
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
509 *isChoiceFormat
= FALSE
;
510 if (U_SUCCESS(ec2
)) {
513 while (i
< *len
&& s
[i
] == CHOICE_FORMAT_MARK
&& i
< 2) {
516 *isChoiceFormat
= (i
== 1);
517 if (i
!= 0) ++s
; // Skip over first mark
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...?
523 *ec
= U_USING_DEFAULT_WARNING
;
530 uprv_parseCurrency(const char* locale
,
531 const UnicodeString
& text
,
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.
548 // Look up the Currencies resource for the given locale. The
549 // Currencies locale data looks like this:
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" }
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.
565 // We want multi-level fallback for this resource, so we implement
568 // Use a separate UErrorCode here that does not propagate out of
570 UErrorCode ec2
= U_ZERO_ERROR
;
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
;
579 int32_t start
= pos
.getIndex();
580 const UChar
* s
= NULL
;
582 const char* iso
= NULL
;
585 // Multi-level resource inheritance fallback loop
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
);
594 s
= ures_getStringByIndex(names
, UCURR_SYMBOL_NAME
, &len
, &ec2
);
595 UBool isChoice
= FALSE
;
596 if (len
> 0 && s
[0] == CHOICE_FORMAT_MARK
) {
599 if (len
> 0 && s
[0] != CHOICE_FORMAT_MARK
) {
605 ChoiceFormat
fmt(s
, ec2
);
606 fmt
.parse(text
, temp
, pos
);
607 len
= pos
.getIndex() - start
;
609 } else if (len
> max
&&
610 text
.compare(pos
.getIndex(), len
, s
) != 0) {
614 iso
= ures_getKey(names
);
622 // Try to fallback. If that fails (because we are already at
624 if (!fallback(loc
)) {
630 u_charsToUChars(iso
, result
, 4);
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) {
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'*/) {
648 text
.extract(start
, 3, result
);
654 pos
.setIndex(start
+ max
);
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.
666 * This is used for backward compatibility with old currency logic in
667 * DecimalFormat and DecimalFormatSymbols.
670 uprv_getStaticCurrencyName(const UChar
* iso
, const char* loc
,
671 UnicodeString
& result
, UErrorCode
& ec
)
673 UBool isChoiceFormat
;
675 const UChar
* currname
= ucurr_getName(iso
, loc
, UCURR_SYMBOL_NAME
,
676 &isChoiceFormat
, &len
, &ec
);
678 // If this is a ChoiceFormat currency, then format an
679 // arbitrary value; pick something != 1; more common.
681 if (isChoiceFormat
) {
682 ChoiceFormat
f(currname
, ec
);
684 f
.format(2.0, result
);
694 U_CAPI
int32_t U_EXPORT2
695 ucurr_getDefaultFractionDigits(const UChar
* currency
, UErrorCode
* ec
) {
696 return (_findMetaData(currency
, *ec
))[0];
699 U_CAPI
double U_EXPORT2
700 ucurr_getRoundingIncrement(const UChar
* currency
, UErrorCode
* ec
) {
701 const int32_t *data
= _findMetaData(currency
, *ec
);
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
;
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.
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]];
722 #endif /* #if !UCONFIG_NO_FORMATTING */