/*
**********************************************************************
-* Copyright (c) 2002-2003, International Business Machines
+* Copyright (c) 2002-2004, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
*/
#include "unicode/ucurr.h"
#include "unicode/locid.h"
-#include "unicode/resbund.h"
+#include "unicode/ures.h"
#include "unicode/ustring.h"
+#include "unicode/choicfmt.h"
+#include "unicode/parsepos.h"
+#include "ustr_imp.h"
#include "cmemory.h"
#include "cstring.h"
#include "uassert.h"
-#include "iculserv.h"
+#include "mutex.h"
#include "ucln_in.h"
//------------------------------------------------------------
//------------------------------------------------------------
// Resource tags
+//
+static const char CURRENCY_DATA[] = "CurrencyData";
// Tag for meta-data, in root.
static const char CURRENCY_META[] = "CurrencyMeta";
// Variant delimiter
static const char VAR_DELIM[] = "_";
+// Variant for legacy euro mapping in CurrencyMap
+static const char VAR_DELIM_EURO[] = "_EURO";
+
+#define VARIANT_IS_EMPTY 0
+#define VARIANT_IS_EURO 0x1
+#define VARIANT_IS_PREEURO 0x2
+
// Tag for localized display names (symbols) of currencies
static const char CURRENCIES[] = "Currencies";
* units of 10^(-fraction_digits).
*/
static const int32_t*
-_findMetaData(const UChar* currency) {
+_findMetaData(const UChar* currency, UErrorCode& ec) {
+
+ if (currency == 0 || *currency == 0) {
+ if (U_SUCCESS(ec)) {
+ ec = U_ILLEGAL_ARGUMENT_ERROR;
+ }
+ return LAST_RESORT_DATA;
+ }
// 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);
-
+ UResourceBundle* currencyData = ures_openDirect(NULL, CURRENCY_DATA, &ec);
+ UResourceBundle* currencyMeta = ures_getByKey(currencyData, CURRENCY_META, currencyData, &ec);
+
if (U_FAILURE(ec)) {
+ ures_close(currencyMeta);
// 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[ISO_COUNTRY_CODE_LENGTH+1];
- ResourceBundle rb = currencyMeta.get(myUCharsToChars(buf, currency), ec);
- if (U_FAILURE(ec)) {
- rb = currencyMeta.get(DEFAULT_META, ec);
+ UErrorCode ec2 = U_ZERO_ERROR; // local error code: soft failure
+ UResourceBundle* rb = ures_getByKey(currencyMeta, myUCharsToChars(buf, currency), NULL, &ec2);
+ if (U_FAILURE(ec2)) {
+ ures_close(rb);
+ rb = ures_getByKey(currencyMeta,DEFAULT_META, NULL, &ec);
if (U_FAILURE(ec)) {
+ ures_close(currencyMeta);
+ ures_close(rb);
// 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) {
+ const int32_t *data = ures_getIntVector(rb, &len, &ec);
+ if (U_FAILURE(ec) || len != 2) {
// Config/build error; return hard-coded defaults
+ if (U_SUCCESS(ec)) {
+ ec = U_INVALID_FORMAT_ERROR;
+ }
+ ures_close(currencyMeta);
+ ures_close(rb);
return LAST_RESORT_DATA;
}
+ ures_close(currencyMeta);
+ ures_close(rb);
return data;
}
+// -------------------------------------
+
+/**
+ * @see VARIANT_IS_EURO
+ * @see VARIANT_IS_PREEURO
+ */
+static uint32_t
+idForLocale(const char* locale, char* countryAndVariant, int capacity, UErrorCode* ec)
+{
+ uint32_t variantType = 0;
+ // !!! 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[ULOC_FULLNAME_CAPACITY];
+ uloc_getCountry(locale, countryAndVariant, capacity, ec);
+ uloc_getVariant(locale, variant, sizeof(variant), ec);
+ if (variant[0] != 0) {
+ variantType = (0 == uprv_strcmp(variant, VAR_EURO))
+ | ((0 == uprv_strcmp(variant, VAR_PRE_EURO)) << 1);
+ if (variantType)
+ {
+ uprv_strcat(countryAndVariant, VAR_DELIM);
+ uprv_strcat(countryAndVariant, variant);
+ }
+ }
+ return variantType;
+}
// ------------------------------------------
//
// don't use ICUService since we don't need fallback
+#if !UCONFIG_NO_SERVICE
+U_CDECL_BEGIN
+static UBool U_CALLCONV currency_cleanup(void);
+U_CDECL_END
struct CReg;
/* Remember to call umtx_init(&gCRegLock) before using it! */
CReg *next;
UChar iso[ISO_COUNTRY_CODE_LENGTH+1];
char id[ULOC_FULLNAME_CAPACITY];
-
+
CReg(const UChar* _iso, const char* _id)
: next(0)
{
- int32_t len = uprv_strlen(_id);
+ int32_t len = (int32_t)uprv_strlen(_id);
if (len > (int32_t)(sizeof(id)-1)) {
len = (sizeof(id)-1);
}
uprv_memcpy(iso, _iso, ISO_COUNTRY_CODE_LENGTH * sizeof(const UChar));
iso[ISO_COUNTRY_CODE_LENGTH] = 0;
}
-
+
static UCurrRegistryKey reg(const UChar* _iso, const char* _id, UErrorCode* status)
{
if (status && U_SUCCESS(*status) && _iso && _id) {
umtx_init(&gCRegLock);
Mutex mutex(&gCRegLock);
if (!gCRegHead) {
- ucln_i18n_registerCleanup();
+ /* register for the first time */
+ ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY, currency_cleanup);
}
n->next = gCRegHead;
gCRegHead = n;
}
return 0;
}
-
+
static UBool unreg(UCurrRegistryKey key) {
umtx_init(&gCRegLock);
Mutex mutex(&gCRegLock);
delete (CReg*)key;
return TRUE;
}
-
+
CReg* p = gCRegHead;
while (p) {
if (p->next == key) {
p->next = ((CReg*)key)->next;
- delete (CReg*)key;
+ delete (CReg*)key;
return TRUE;
}
p = p->next;
}
-
+
return FALSE;
}
-
+
static const UChar* get(const char* id) {
umtx_init(&gCRegLock);
Mutex mutex(&gCRegLock);
CReg* p = gCRegHead;
+
+ /* register cleanup of the mutex */
+ ucln_i18n_registerCleanup(UCLN_I18N_CURRENCY, currency_cleanup);
while (p) {
if (uprv_strcmp(id, p->id) == 0) {
return p->iso;
}
};
-// -------------------------------------
-
-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[ULOC_FULLNAME_CAPACITY];
- 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);
- }
+/**
+ * Release all static memory held by currency.
+ */
+U_CDECL_BEGIN
+static UBool U_CALLCONV currency_cleanup(void) {
+#if !UCONFIG_NO_SERVICE
+ CReg::cleanup();
+#endif
+ return TRUE;
}
+U_CDECL_END
// -------------------------------------
U_CAPI UCurrRegistryKey U_EXPORT2
-ucurr_register(const UChar* isoCode, const char* locale, UErrorCode *status)
+ucurr_register(const UChar* isoCode, const char* locale, UErrorCode *status)
{
if (status && U_SUCCESS(*status)) {
char id[ULOC_FULLNAME_CAPACITY];
// -------------------------------------
U_CAPI UBool U_EXPORT2
-ucurr_unregister(UCurrRegistryKey key, UErrorCode* status)
+ucurr_unregister(UCurrRegistryKey key, UErrorCode* status)
{
if (status && U_SUCCESS(*status)) {
return CReg::unreg(key);
}
return FALSE;
}
+#endif /* UCONFIG_NO_SERVICE */
// -------------------------------------
-U_CAPI const UChar* U_EXPORT2
-ucurr_forLocale(const char* locale, UErrorCode* ec) {
+U_CAPI int32_t U_EXPORT2
+ucurr_forLocale(const char* locale,
+ UChar* buff,
+ int32_t buffCapacity,
+ UErrorCode* ec)
+{
+ int32_t resLen = 0;
+ const UChar* s = NULL;
if (ec != NULL && U_SUCCESS(*ec)) {
- char id[ULOC_FULLNAME_CAPACITY];
- 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;
+ if ((buff && buffCapacity) || !buffCapacity) {
+ UErrorCode localStatus = U_ZERO_ERROR;
+ char id[ULOC_FULLNAME_CAPACITY];
+ if ((resLen = uloc_getKeywordValue(locale, "currency", id, ULOC_FULLNAME_CAPACITY, &localStatus))) {
+ // there is a currency keyword. Try to see if it's valid
+ if(buffCapacity > resLen) {
+ u_charsToUChars(id, buff, resLen);
+ }
+ } else {
+ // get country or country_variant in `id'
+ uint32_t variantType = idForLocale(locale, id, sizeof(id), ec);
+
+ if (U_FAILURE(*ec)) {
+ return 0;
+ }
+
+#if !UCONFIG_NO_SERVICE
+ const UChar* result = CReg::get(id);
+ if (result) {
+ if(buffCapacity > u_strlen(result)) {
+ u_strcpy(buff, result);
+ }
+ return u_strlen(result);
+ }
+#endif
+
+ // Look up the CurrencyMap element in the root bundle.
+ UResourceBundle *rb = ures_openDirect(NULL, CURRENCY_DATA, &localStatus);
+ UResourceBundle *cm = ures_getByKey(rb, CURRENCY_MAP, rb, &localStatus);
+ s = ures_getStringByKey(cm, id, &resLen, &localStatus);
+
+ if ((s == NULL || U_FAILURE(localStatus)) && variantType != VARIANT_IS_EMPTY
+ && (id[0] != 0))
+ {
+ // We don't know about it. Check to see if we support the variant.
+ if (variantType & VARIANT_IS_EURO) {
+ s = ures_getStringByKey(cm, VAR_DELIM_EURO, &resLen, ec);
+ }
+ else {
+ uloc_getParent(locale, id, sizeof(id), ec);
+ *ec = U_USING_FALLBACK_WARNING;
+ ures_close(cm);
+ return ucurr_forLocale(id, buff, buffCapacity, ec);
+ }
+ }
+ else if (*ec == U_ZERO_ERROR || localStatus != U_ZERO_ERROR) {
+ // There is nothing to fallback to. Report the failure/warning if possible.
+ *ec = localStatus;
+ }
+ if (U_SUCCESS(*ec)) {
+ if(buffCapacity > resLen) {
+ u_strcpy(buff, s);
+ }
+ }
+ ures_close(cm);
+ }
+ return u_terminateUChars(buff, buffCapacity, resLen, ec);
+ } else {
+ *ec = U_ILLEGAL_ARGUMENT_ERROR;
}
}
- return NULL;
+ return resLen;
}
// end registration
return FALSE;
}
UErrorCode status = U_ZERO_ERROR;
- uloc_getParent(loc, loc, uprv_strlen(loc), &status);
+ uloc_getParent(loc, loc, (int32_t)uprv_strlen(loc), &status);
/*
char *i = uprv_strrchr(loc, '_');
if (i == NULL) {
// Look up the Currencies resource for the given locale. The
// Currencies locale data looks like this:
//|en {
- //| Currencies {
+ //| 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)) {
+ if (U_FAILURE(*ec)) {
return 0;
}
*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.
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);
+ rb = ures_getByKey(rb, CURRENCIES, rb, &ec2);
+ rb = ures_getByKey(rb, buf, rb, &ec2);
+ s = ures_getStringByIndex(rb, choice, len, &ec2);
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)) {
+ if (ec2 == U_USING_DEFAULT_WARNING
+ || (ec2 == U_USING_FALLBACK_WARNING && *ec != U_USING_DEFAULT_WARNING)) {
+ *ec = ec2;
+ }
break;
+ } else if (strlen(loc) == 0) {
+ *ec = U_USING_DEFAULT_WARNING;
+ } else if (*ec != U_USING_DEFAULT_WARNING) {
+ *ec = U_USING_FALLBACK_WARNING;
}
}
// If we fail to find a match, use the ISO 4217 code
*len = u_strlen(currency); // Should == ISO_COUNTRY_CODE_LENGTH, but maybe not...?
+ *ec = U_USING_DEFAULT_WARNING;
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_NAMESPACE_BEGIN
+
+void
+uprv_parseCurrency(const char* locale,
+ const UnicodeString& text,
+ ParsePosition& pos,
+ UChar* result,
+ UErrorCode& ec) {
+
+ // TODO: There is a slight problem with the pseudo-multi-level
+ // fallback implemented here. More-specific locales don't
+ // properly shield duplicate entries in less-specific locales.
+ // This problem will go away when real multi-level fallback is
+ // implemented. We could also fix this by recording (in a
+ // hash) which codes are used at each level of fallback, but
+ // this doesn't seem warranted.
+
+ if (U_FAILURE(ec)) {
+ return;
+ }
+
+ // 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" }
+ //| //...
+ //| }
+ //|}
+
+ // 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[ULOC_FULLNAME_CAPACITY];
+ uloc_getName(locale, loc, sizeof(loc), &ec2);
+ if (U_FAILURE(ec2) || ec2 == U_STRING_NOT_TERMINATED_WARNING) {
+ ec = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+
+ int32_t start = pos.getIndex();
+ const UChar* s = NULL;
+
+ const char* iso = NULL;
+ int32_t max = 0;
+
+ // 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);
+ int32_t n = ures_getSize(curr);
+ for (int32_t i=0; i<n; ++i) {
+ UResourceBundle* names = ures_getByIndex(curr, i, NULL, &ec2);
+ int32_t len;
+ s = ures_getStringByIndex(names, UCURR_SYMBOL_NAME, &len, &ec2);
+ UBool isChoice = FALSE;
+ if (len > 0 && s[0] == CHOICE_FORMAT_MARK) {
+ ++s;
+ --len;
+ if (len > 0 && s[0] != CHOICE_FORMAT_MARK) {
+ isChoice = TRUE;
+ }
+ }
+ if (isChoice) {
+ Formattable temp;
+ ChoiceFormat fmt(s, ec2);
+ fmt.parse(text, temp, pos);
+ len = pos.getIndex() - start;
+ pos.setIndex(start);
+ } else if (len > max &&
+ text.compare(pos.getIndex(), len, s) != 0) {
+ len = 0;
+ }
+ if (len > max) {
+ iso = ures_getKey(names);
+ max = len;
+ }
+ ures_close(names);
+ }
+ ures_close(curr);
+ ures_close(rb);
+
+ // Try to fallback. If that fails (because we are already at
+ // root) then exit.
+ if (!fallback(loc)) {
+ break;
+ }
+ }
+
+ if (iso != NULL) {
+ u_charsToUChars(iso, result, 4);
+ }
+
+ // If display name parse fails or if it matches fewer than 3
+ // characters, try to parse 3-letter ISO. Do this after the
+ // display name processing so 3-letter display names are
+ // preferred. Consider /[A-Z]{3}/ to be valid ISO, and parse
+ // it manually--UnicodeSet/regex are too slow and heavy.
+ if (max < 3 && (text.length() - start) >= 3) {
+ UBool valid = TRUE;
+ for (int32_t k=0; k<3; ++k) {
+ UChar ch = text.charAt(start + k); // 16-bit ok
+ if (ch < 0x41/*'A'*/ || ch > 0x5A/*'Z'*/) {
+ valid = FALSE;
+ break;
+ }
+ }
+ if (valid) {
+ text.extract(start, 3, result);
+ result[3] = 0;
+ max = 3;
+ }
+ }
+
+ pos.setIndex(start + max);
+}
+
+U_NAMESPACE_END
+
+/**
+ * Internal method. Given a currency ISO code and a locale, return
+ * the "static" currency name. This is usually the same as the
+ * UCURR_SYMBOL_NAME, but if the latter is a choice format, then the
+ * format is applied to the number 2.0 (to yield the more common
+ * plural) to return a static name.
+ *
+ * This is used for backward compatibility with old currency logic in
+ * DecimalFormat and DecimalFormatSymbols.
+ */
+U_CAPI void
+uprv_getStaticCurrencyName(const UChar* iso, const char* loc,
+ UnicodeString& result, UErrorCode& ec)
+{
+ UBool isChoiceFormat;
+ int32_t len;
+ const UChar* currname = ucurr_getName(iso, loc, UCURR_SYMBOL_NAME,
+ &isChoiceFormat, &len, &ec);
+ if (U_SUCCESS(ec)) {
+ // If this is a ChoiceFormat currency, then format an
+ // arbitrary value; pick something != 1; more common.
+ result.truncate(0);
+ if (isChoiceFormat) {
+ ChoiceFormat f(currname, ec);
+ if (U_SUCCESS(ec)) {
+ f.format(2.0, result);
+ } else {
+ result = iso;
+ }
+ } else {
+ result = currname;
+ }
+ }
+}
U_CAPI int32_t U_EXPORT2
-ucurr_getDefaultFractionDigits(const UChar* currency) {
- return (_findMetaData(currency))[0];
+ucurr_getDefaultFractionDigits(const UChar* currency, UErrorCode* ec) {
+ return (_findMetaData(currency, *ec))[0];
}
U_CAPI double U_EXPORT2
-ucurr_getRoundingIncrement(const UChar* currency) {
- const int32_t *data = _findMetaData(currency);
+ucurr_getRoundingIncrement(const UChar* currency, UErrorCode* ec) {
+ const int32_t *data = _findMetaData(currency, *ec);
- // 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) {
+ // If the meta data is invalid, return 0.0.
+ if (data[0] < 0 || data[0] > MAX_POW10) {
+ if (U_SUCCESS(*ec)) {
+ *ec = U_INVALID_FORMAT_ERROR;
+ }
+ return 0.0;
+ }
+
+ // If there is no rounding, return 0.0 to indicate no rounding. A
+ // rounding value (data[1]) of 0 or 1 indicates no rounding.
+ if (data[1] < 2) {
return 0.0;
}
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