| /* |
| ********************************************************************** |
| * Copyright (c) 2002, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| */ |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "unicode/ucurr.h" |
| #include "unicode/locid.h" |
| #include "unicode/resbund.h" |
| #include "unicode/ustring.h" |
| #include "cmemory.h" |
| #include "cstring.h" |
| #include "uassert.h" |
| #include "iculserv.h" |
| #include "ucln_in.h" |
| |
| //------------------------------------------------------------ |
| // Constants |
| |
| // Default currency meta data of last resort. We try to use the |
| // defaults encoded in the meta data resource bundle. If there is a |
| // configuration/build error and these are not available, we use these |
| // hard-coded defaults (which should be identical). |
| static const int32_t LAST_RESORT_DATA[] = { 2, 0 }; |
| |
| // POW10[i] = 10^i, i=0..MAX_POW10 |
| static const int32_t POW10[] = { 1, 10, 100, 1000, 10000, 100000, |
| 1000000, 10000000, 100000000, 1000000000 }; |
| |
| static const int32_t MAX_POW10 = (sizeof(POW10)/sizeof(POW10[0])) - 1; |
| |
| //------------------------------------------------------------ |
| // Resource tags |
| |
| // Tag for meta-data, in root. |
| static const char CURRENCY_META[] = "CurrencyMeta"; |
| |
| // Tag for map from countries to currencies, in root. |
| static const char CURRENCY_MAP[] = "CurrencyMap"; |
| |
| // Tag for default meta-data, in CURRENCY_META |
| static const char DEFAULT_META[] = "DEFAULT"; |
| |
| // Variant for legacy pre-euro mapping in CurrencyMap |
| static const char VAR_PRE_EURO[] = "PREEURO"; |
| |
| // Variant for legacy euro mapping in CurrencyMap |
| static const char VAR_EURO[] = "EURO"; |
| |
| // Variant delimiter |
| static const char VAR_DELIM[] = "_"; |
| |
| // Tag for localized display names (symbols) of currencies |
| static const char CURRENCIES[] = "Currencies"; |
| |
| // Marker character indicating that a display name is a ChoiceFormat |
| // pattern. Strings that start with one mark are ChoiceFormat |
| // patterns. Strings that start with 2 marks are static strings, and |
| // the first mark is deleted. |
| static const UChar CHOICE_FORMAT_MARK = 0x003D; // Equals sign |
| |
| //------------------------------------------------------------ |
| // Code |
| |
| /** |
| * Unfortunately, we have to convert the UChar* currency code to char* |
| * to use it as a resource key. |
| */ |
| static inline char* |
| myUCharsToChars(char* resultOfLen4, const UChar* currency) { |
| u_UCharsToChars(currency, resultOfLen4, 3); |
| resultOfLen4[3] = 0; |
| return resultOfLen4; |
| } |
| |
| /** |
| * Internal function to look up currency data. Result is an array of |
| * two integers. The first is the fraction digits. The second is the |
| * rounding increment, or 0 if none. The rounding increment is in |
| * units of 10^(-fraction_digits). |
| */ |
| static const int32_t* |
| _findMetaData(const UChar* currency) { |
| |
| // Get CurrencyMeta resource out of root locale file. [This may |
| // move out of the root locale file later; if it does, update this |
| // code.] |
| UErrorCode ec = U_ZERO_ERROR; |
| ResourceBundle currencyMeta = |
| ResourceBundle((char*)0, Locale(""), ec).get(CURRENCY_META, ec); |
| |
| if (U_FAILURE(ec)) { |
| // Config/build error; return hard-coded defaults |
| return LAST_RESORT_DATA; |
| } |
| |
| // Look up our currency, or if that's not available, then DEFAULT |
| char buf[4]; |
| ResourceBundle rb = currencyMeta.get(myUCharsToChars(buf, currency), ec); |
| if (U_FAILURE(ec)) { |
| rb = currencyMeta.get(DEFAULT_META, ec); |
| if (U_FAILURE(ec)) { |
| // Config/build error; return hard-coded defaults |
| return LAST_RESORT_DATA; |
| } |
| } |
| |
| int32_t len; |
| const int32_t *data = rb.getIntVector(len, ec); |
| if (U_FAILURE(ec) || len < 2) { |
| // Config/build error; return hard-coded defaults |
| return LAST_RESORT_DATA; |
| } |
| |
| return data; |
| } |
| |
| |
| // ------------------------------------------ |
| // |
| // Registration |
| // |
| //------------------------------------------- |
| |
| // don't use ICUService since we don't need fallback |
| |
| struct CReg { |
| CReg *next; |
| UChar iso[4]; |
| char id[12]; |
| |
| static UMTX gLock; |
| static CReg* gHead; |
| |
| CReg(const UChar* _iso, const char* _id) |
| : next(0) |
| { |
| uprv_strcpy(id, _id); |
| uprv_memcpy(iso, _iso, 3 * sizeof(const UChar)); |
| iso[3] = 0; |
| } |
| |
| static UCurrRegistryKey reg(const UChar* _iso, const char* _id, UErrorCode* status) |
| { |
| if (status && U_SUCCESS(*status)) { |
| CReg* n = new CReg(_iso, _id); |
| if (n) { |
| Mutex mutex(&gLock); |
| if (!gHead) { |
| ucln_i18n_registerCleanup(); |
| } |
| n->next = gHead; |
| gHead = n; |
| return n; |
| } |
| *status = U_MEMORY_ALLOCATION_ERROR; |
| } |
| return 0; |
| } |
| |
| static UBool unreg(UCurrRegistryKey key) { |
| Mutex mutex(&gLock); |
| if (gHead == key) { |
| gHead = gHead->next; |
| return TRUE; |
| } |
| |
| CReg* p = gHead; |
| while (p) { |
| if (p->next == key) { |
| p->next = ((CReg*)key)->next; |
| delete (CReg*)key; |
| return TRUE; |
| } |
| p = p->next; |
| } |
| |
| return FALSE; |
| } |
| |
| static const UChar* get(const char* id) { |
| Mutex mutex(&gLock); |
| CReg* p = gHead; |
| while (p) { |
| if (uprv_strcmp(id, p->id) == 0) { |
| return p->iso; |
| } |
| p = p->next; |
| } |
| return NULL; |
| } |
| |
| static void cleanup(void) { |
| while (gHead) { |
| CReg* n = gHead; |
| gHead = gHead->next; |
| delete n; |
| } |
| umtx_destroy(&gLock); |
| } |
| }; |
| |
| UMTX CReg::gLock = 0; |
| CReg* CReg::gHead = 0; |
| |
| // ------------------------------------- |
| |
| static void |
| idForLocale(const char* locale, char* buffer, int capacity, UErrorCode* ec) { |
| // !!! this is internal only, assumes buffer is not null and capacity is sufficient |
| // Extract the country name and variant name. We only |
| // recognize two variant names, EURO and PREEURO. |
| char variant[8]; |
| uloc_getCountry(locale, buffer, capacity, ec); |
| uloc_getVariant(locale, variant, sizeof(variant), ec); |
| if (0 == uprv_strcmp(variant, VAR_PRE_EURO) || |
| 0 == uprv_strcmp(variant, VAR_EURO)) { |
| uprv_strcat(buffer, VAR_DELIM); |
| uprv_strcat(buffer, variant); |
| } |
| } |
| |
| // ------------------------------------- |
| |
| U_CAPI UCurrRegistryKey U_EXPORT2 |
| ucurr_register(const UChar* isoCode, const char* locale, UErrorCode *status) |
| { |
| if (status && U_SUCCESS(*status)) { |
| char id[12]; |
| idForLocale(locale, id, sizeof(id), status); |
| return CReg::reg(isoCode, id, status); |
| } |
| return NULL; |
| } |
| |
| // ------------------------------------- |
| |
| U_CAPI UBool U_EXPORT2 |
| ucurr_unregister(UCurrRegistryKey key, UErrorCode* status) |
| { |
| if (status && U_SUCCESS(*status)) { |
| return CReg::unreg(key); |
| } |
| return FALSE; |
| } |
| |
| // ------------------------------------- |
| |
| U_CAPI const UChar* U_EXPORT2 |
| ucurr_forLocale(const char* locale, UErrorCode* ec) { |
| if (ec != NULL && U_SUCCESS(*ec)) { |
| char id[12]; |
| idForLocale(locale, id, sizeof(id), ec); |
| if (U_FAILURE(*ec)) { |
| return NULL; |
| } |
| |
| const UChar* result = CReg::get(id); |
| if (result) { |
| return result; |
| } |
| |
| // Look up the CurrencyMap element in the root bundle. |
| UResourceBundle* rb = ures_open(NULL, "", ec); |
| UResourceBundle* cm = ures_getByKey(rb, CURRENCY_MAP, NULL, ec); |
| int32_t len; |
| const UChar* s = ures_getStringByKey(cm, id, &len, ec); |
| ures_close(cm); |
| ures_close(rb); |
| |
| if (U_SUCCESS(*ec)) { |
| return s; |
| } |
| } |
| return NULL; |
| } |
| |
| // end registration |
| |
| /** |
| * Modify the given locale name by removing the rightmost _-delimited |
| * element. If there is none, empty the string ("" == root). |
| * NOTE: The string "root" is not recognized; do not use it. |
| * @return TRUE if the fallback happened; FALSE if locale is already |
| * root (""). |
| */ |
| static UBool fallback(char *loc) { |
| if (!*loc) { |
| return FALSE; |
| } |
| char *i = uprv_strrchr(loc, '_'); |
| if (i == NULL) { |
| i = loc; |
| } |
| *i = 0; |
| return TRUE; |
| } |
| |
| U_CAPI const UChar* U_EXPORT2 |
| ucurr_getName(const UChar* currency, |
| const char* locale, |
| UCurrNameStyle nameStyle, |
| UBool* isChoiceFormat, // fillin |
| int32_t* len, // fillin |
| UErrorCode* ec) { |
| |
| // Look up the Currencies resource for the given locale. The |
| // Currencies locale data looks like this: |
| //|en { |
| //| Currencies { |
| //| USD { "US$", "US Dollar" } |
| //| CHF { "Sw F", "Swiss Franc" } |
| //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" } |
| //| //... |
| //| } |
| //|} |
| |
| if (ec == NULL || U_FAILURE(*ec)) { |
| return 0; |
| } |
| |
| int32_t choice = (int32_t) nameStyle; |
| if (choice < 0 || choice > 1) { |
| *ec = U_ILLEGAL_ARGUMENT_ERROR; |
| return 0; |
| } |
| |
| // In the future, resource bundles may implement multi-level |
| // fallback. That is, if a currency is not found in the en_US |
| // Currencies data, then the en Currencies data will be searched. |
| // Currently, if a Currencies datum exists in en_US and en, the |
| // en_US entry hides that in en. |
| |
| // We want multi-level fallback for this resource, so we implement |
| // it manually. |
| |
| // Use a separate UErrorCode here that does not propagate out of |
| // this function. |
| UErrorCode ec2 = U_ZERO_ERROR; |
| |
| char loc[100]; |
| int32_t loclen = uloc_getName(locale, loc, sizeof(loc), &ec2); |
| if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) { |
| *ec = U_ILLEGAL_ARGUMENT_ERROR; |
| return 0; |
| } |
| |
| char buf[4]; |
| myUCharsToChars(buf, currency); |
| |
| const UChar* s = NULL; |
| |
| // Multi-level resource inheritance fallback loop |
| for (;;) { |
| ec2 = U_ZERO_ERROR; |
| UResourceBundle* rb = ures_open(NULL, loc, &ec2); |
| UResourceBundle* curr = ures_getByKey(rb, CURRENCIES, NULL, &ec2); |
| UResourceBundle* names = ures_getByKey(curr, buf, NULL, &ec2); |
| s = ures_getStringByIndex(names, choice, len, &ec2); |
| ures_close(names); |
| ures_close(curr); |
| ures_close(rb); |
| |
| // If we've succeeded we're done. Otherwise, try to fallback. |
| // If that fails (because we are already at root) then exit. |
| if (U_SUCCESS(ec2) || !fallback(loc)) { |
| break; |
| } |
| } |
| |
| // Determine if this is a ChoiceFormat pattern. One leading mark |
| // indicates a ChoiceFormat. Two indicates a static string that |
| // starts with a mark. In either case, the first mark is ignored, |
| // if present. Marks in the rest of the string have no special |
| // meaning. |
| *isChoiceFormat = FALSE; |
| if (U_SUCCESS(ec2)) { |
| U_ASSERT(s != NULL); |
| int32_t i=0; |
| while (i < *len && s[i] == CHOICE_FORMAT_MARK && i < 2) { |
| ++i; |
| } |
| *isChoiceFormat = (i == 1); |
| if (i != 0) ++s; // Skip over first mark |
| return s; |
| } |
| |
| // If we fail to find a match, use the ISO 4217 code |
| *len = u_strlen(currency); // Should == 3, but maybe not...? |
| return currency; |
| } |
| |
| //!// This API is now redundant. It predates ucurr_getName, which |
| //!// replaces and extends it. |
| //!U_CAPI const UChar* U_EXPORT2 |
| //!ucurr_getSymbol(const UChar* currency, |
| //! const char* locale, |
| //! int32_t* len, // fillin |
| //! UErrorCode* ec) { |
| //! UBool isChoiceFormat; |
| //! const UChar* s = ucurr_getName(currency, locale, UCURR_SYMBOL_NAME, |
| //! &isChoiceFormat, len, ec); |
| //! if (isChoiceFormat) { |
| //! // Don't let ChoiceFormat patterns out through this API |
| //! *len = u_strlen(currency); // Should == 3, but maybe not...? |
| //! return currency; |
| //! } |
| //! return s; |
| //!} |
| |
| U_CAPI int32_t U_EXPORT2 |
| ucurr_getDefaultFractionDigits(const UChar* currency) { |
| return (_findMetaData(currency))[0]; |
| } |
| |
| U_CAPI double U_EXPORT2 |
| ucurr_getRoundingIncrement(const UChar* currency) { |
| const int32_t *data = _findMetaData(currency); |
| |
| // If there is no rounding, or if the meta data is invalid, |
| // return 0.0 to indicate no rounding. A rounding value |
| // (data[1]) of 0 or 1 indicates no rounding. |
| if (data[1] < 2 || data[0] < 0 || data[0] > MAX_POW10) { |
| return 0.0; |
| } |
| |
| // Return data[1] / 10^(data[0]). The only actual rounding data, |
| // as of this writing, is CHF { 2, 5 }. |
| return double(data[1]) / POW10[data[0]]; |
| } |
| |
| /** |
| * Release all static memory held by currency. |
| */ |
| U_CFUNC UBool currency_cleanup(void) { |
| CReg::cleanup(); |
| return TRUE; |
| } |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |
| |
| //eof |