diff options
author | Steve Harter <sharter@microsoft.com> | 2015-08-27 12:31:25 -0500 |
---|---|---|
committer | Matt Ellis <matell@microsoft.com> | 2015-09-22 11:50:33 -0700 |
commit | 88b91180979f5089f76fae7e94d579eccd1a39e7 (patch) | |
tree | edc565672276f5cd141657418a5b341599a55c36 /src/corefx | |
parent | 0d3a7b3b472e1d181aca666198b05884b54458b7 (diff) | |
download | coreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.tar.gz coreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.tar.bz2 coreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.zip |
add number formatting
Diffstat (limited to 'src/corefx')
-rw-r--r-- | src/corefx/System.Globalization.Native/localeNumberData.cpp | 361 |
1 files changed, 321 insertions, 40 deletions
diff --git a/src/corefx/System.Globalization.Native/localeNumberData.cpp b/src/corefx/System.Globalization.Native/localeNumberData.cpp index 10d1eaf03d..20faa0eb7a 100644 --- a/src/corefx/System.Globalization.Native/localeNumberData.cpp +++ b/src/corefx/System.Globalization.Native/localeNumberData.cpp @@ -4,11 +4,27 @@ // #include <assert.h> +#include <string.h> #include "locale.hpp" +#include "unicode/decimfmt.h" +#include "unicode/numfmt.h" #include "unicode/ulocdata.h" +// invariant character definitions used by ICU +#define chCurrencySign ((UChar)0x00A4) // international currency +#define chSpace ((UChar)0x0020) // space +#define chSpace2 ((UChar)0x00A0) // space +#define chPatternDigit ((UChar)0x0023) // '#' +#define chPatternSeparator ((UChar)0x003B) // ';' +#define chPatternMinus ((UChar)0x002D) // '-' +#define chPercent ((UChar)0x0025) // '%' +#define chOpenParen ((UChar)0x0028) // '(' +#define chCloseParen ((UChar)0x0029) // ')' + +#define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0])) + // Enum that corresponds to managed enum CultureData.LocaleNumberData. // The numeric values of the enum members match their Win32 counterparts. enum LocaleNumberData : int32_t @@ -31,9 +47,259 @@ enum LocaleNumberData : int32_t /* Function: +NormalizePattern + +Returns a numeric string pattern in a format that we can match against the appropriate managed pattern. +*/ +UnicodeString* NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPattern, bool isNegative) +{ + // A srcPattern example: "#,##0.00 C;(#,##0.00 C)" but where C is the international currency symbol (chCurrencySign) + // The positive pattern comes first, then an optional negative pattern separated by a semicolon + // A destPattern example: "(C n)" where C represents the currency symbol, and n is the number + destPattern->remove(); + + int iStart = 0; + int iEnd = srcPattern->length() - 1; + int32_t iNegative = srcPattern->indexOf(chPatternSeparator); + if (iNegative >= 0) + { + if (isNegative) + { + iStart = iNegative + 1; + } + else + { + iEnd = iNegative - 1; + } + } + + bool minusAdded = false; + bool digitAdded = false; + bool currencyAdded = false; + bool spaceAdded = false; + + for (int i = iStart; i <= iEnd; i++) + { + UChar32 ch = srcPattern->char32At(i); + switch (ch) + { + case chPatternDigit: + if (!digitAdded) + { + digitAdded = true; + destPattern->append('n'); + } + break; + + case chCurrencySign: + if (!currencyAdded) + { + currencyAdded = true; + destPattern->append('C'); + } + break; + + case chSpace: + case chSpace2: + if (!spaceAdded) + { + spaceAdded = true; + destPattern->append(chSpace); + } + else + { + assert(false); + } + break; + + case chPatternMinus: + case chOpenParen: + case chCloseParen: + minusAdded = true; + destPattern->append(ch); + break; + + case chPercent: + destPattern->append(ch); + break; + } + } + + // if there is no segative subpattern, the convention is to prefix the minus sign + if (isNegative && !minusAdded) + { + destPattern->insert(0, chPatternMinus); + } + + return destPattern; +} + +/* +Function: +GetPattern + +Determines the pattern from the decimalFormat and returns the matching pattern's index from patterns[]. +Returns index -1 if no pattern is found. +*/ +int GetPattern(DecimalFormat *decimalFormat, const char* patterns[], int patternsCount, bool isNegative) +{ + const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator + char charPattern[MAX_DOTNET_NUMERIC_PATTERN_LENGTH] = { 0 }; + + UnicodeString icuPattern; + icuPattern.remove(); + decimalFormat->toPattern(icuPattern); + + UnicodeString normalizedPattern; + NormalizePattern(&icuPattern, &normalizedPattern, isNegative); + + assert(normalizedPattern.length() > 0); + assert(normalizedPattern.length() < MAX_DOTNET_NUMERIC_PATTERN_LENGTH); + if (normalizedPattern.length() == 0 || normalizedPattern.length() >= MAX_DOTNET_NUMERIC_PATTERN_LENGTH) + { + return -1; + } + + u_UCharsToChars(normalizedPattern.getTerminatedBuffer(), charPattern, normalizedPattern.length() + 1); + + for (int i = 0; i < patternsCount; i++) + { + if (strcmp(charPattern, patterns[i]) == 0) + { + return i; + } + }; + + assert(false); // should have found a valid pattern + return -1; +} + +/* +Function: +GetCurrencyNegativePattern + +Implementation of NumberFormatInfo.CurrencyNegativePattern. +Returns the pattern index. +*/ +int GetCurrencyNegativePattern(const Locale &locale) +{ + const int DEFAULT_VALUE = 0; + static const char* Patterns[] = { "(Cn)", "-Cn", "C-n", "Cn-", "(nC)", "-nC", "n-C", "nC-", "-n C", "-C n", "n C-", "C n-", "C -n", "n- C", "(C n)", "(n C)" }; + UErrorCode status = U_ZERO_ERROR; + + LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status)); + if (U_FAILURE(status)) + { + assert(false); + return DEFAULT_VALUE; + } + + int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); + return (value >= 0) ? value : DEFAULT_VALUE; +} + +/* +Function: +GetCurrencyPositivePattern + +Implementation of NumberFormatInfo.CurrencyPositivePattern. +Returns the pattern index. +*/ +int GetCurrencyPositivePattern(const Locale &locale) +{ + const int DEFAULT_VALUE = 0; + static const char* Patterns[] = { "Cn", "nC", "C n", "n C" }; + UErrorCode status = U_ZERO_ERROR; + + LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status)); + if (U_FAILURE(status)) + { + assert(false); + return DEFAULT_VALUE; + } + + int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false); + return (value >= 0) ? value : DEFAULT_VALUE; +} + +/* +Function: +GetNumberNegativePattern + +Implementation of NumberFormatInfo.NumberNegativePattern. +Returns the pattern index. +*/ +int GetNumberNegativePattern(const Locale &locale) +{ + const int DEFAULT_VALUE = 1; + static const char* Patterns[] = { "(n)", "-n", "- n", "n-", "n -" }; + UErrorCode status = U_ZERO_ERROR; + + LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_DECIMAL, status)); + if (U_FAILURE(status)) + { + assert(false); + return DEFAULT_VALUE; + } + + int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); + return (value >= 0) ? value : DEFAULT_VALUE; +} + +/* +Function: +GetPercentNegativePattern + +Implementation of NumberFormatInfo.PercentNegativePattern. +Returns the pattern index. +*/ +int GetPercentNegativePattern(const Locale &locale) +{ + const int DEFAULT_VALUE = 0; + static const char* Patterns[] = { "-n %", "-n%", "-%n", "%-n", "%n-", "n-%", "n%-", "-% n", "n %-", "% n-", "% -n", "n- %" }; + UErrorCode status = U_ZERO_ERROR; + + LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status)); + if (U_FAILURE(status)) + { + assert(false); + return DEFAULT_VALUE; + } + + int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); + return (value >= 0) ? value : DEFAULT_VALUE; +} + +/* +Function: +GetPercentPositivePattern + +Implementation of NumberFormatInfo.PercentPositivePattern. +Returns the pattern index. +*/ +int GetPercentPositivePattern(const Locale &locale) +{ + const int DEFAULT_VALUE = 0; + static const char* Patterns[] = { "n %", "n%", "%n", "% n" }; + UErrorCode status = U_ZERO_ERROR; + + LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status)); + if (U_FAILURE(status)) + { + assert(false); + return DEFAULT_VALUE; + } + + int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false); + return (value >= 0) ? value : DEFAULT_VALUE; +} + +/* +Function: GetMeasurementSystem -Obtains the measurement system for the local, determining if US or metric +Obtains the measurement system for the local, determining if US or metric. +Returns 1 for US, 0 otherwise. */ UErrorCode GetMeasurementSystem(const char *localeId, int32_t *value) { @@ -64,48 +330,63 @@ extern "C" int32_t GetLocaleInfoInt(const UChar* localeName, LocaleNumberData lo } UErrorCode status = U_ZERO_ERROR; + UNumberFormat* numformat = NULL; switch (localeNumberData) { - case LanguageId: - *value = locale.getLCID(); - break; - case MeasurementSystem: - status = GetMeasurementSystem(locale.getName(), value); - break; - case FractionalDigitsCount: - *value = 2; //TODO: implement - break; - case NegativeNumberFormat: - *value = 1; //TODO: implement - break; - case MonetaryFractionalDigitsCount: - *value = 2; //TODO: implement - break; - case PositiveMonetaryNumberFormat: - *value = 0; //TODO: implement - break; - case NegativeMonetaryNumberFormat: - *value = 0; //TODO: implement - break; - case CalendarType: - *value = 1; //TODO: implement - break; - case FirstWeekOfYear: - *value = 0; //TODO: implement - break; - case ReadingLayout: - *value = 0; //TODO: implement - break; - case NegativePercentFormat: - *value = 0; //TODO: implement - break; - case PositivePercentFormat: - *value = 0; //TODO: implement - break; - default: - status = U_UNSUPPORTED_ERROR; + case LanguageId: + *value = locale.getLCID(); + break; + case MeasurementSystem: + status = GetMeasurementSystem(locale.getName(), value); + break; + case FractionalDigitsCount: + numformat = unum_open(UNUM_DECIMAL, NULL, 0, locale.getName(), NULL, &status); + if (U_FAILURE(status)) + { assert(false); - break; + return 0; + } + *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS); + unum_close(numformat); + break; + case NegativeNumberFormat: + *value = GetNumberNegativePattern(locale); + break; + case MonetaryFractionalDigitsCount: + numformat = unum_open(UNUM_CURRENCY, NULL, 0, locale.getName(), NULL, &status); + if (U_FAILURE(status)) + { + assert(false); + return 0; + } + *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS); + unum_close(numformat); + break; + case PositiveMonetaryNumberFormat: + *value = GetCurrencyPositivePattern(locale); + break; + case NegativeMonetaryNumberFormat: + *value = GetCurrencyNegativePattern(locale); + break; + case CalendarType: + *value = 1; //TODO: implement + break; + case FirstWeekOfYear: + *value = 0; //TODO: implement + break; + case ReadingLayout: + *value = 0; //todo: implement + break; + case NegativePercentFormat: + *value = GetPercentNegativePattern(locale); + break; + case PositivePercentFormat: + *value = GetPercentPositivePattern(locale); + break; + default: + status = U_UNSUPPORTED_ERROR; + assert(false); + break; } assert(status != U_BUFFER_OVERFLOW_ERROR); |