Home | History | Annotate | Download | only in i18n
      1 /*
      2 ********************************************************************************
      3 *   Copyright (C) 2005-2007, International Business Machines
      4 *   Corporation and others.  All Rights Reserved.
      5 ********************************************************************************
      6 *
      7 * File WINNMFMT.CPP
      8 *
      9 ********************************************************************************
     10 */
     11 
     12 #include "unicode/utypes.h"
     13 
     14 #ifdef U_WINDOWS
     15 
     16 #if !UCONFIG_NO_FORMATTING
     17 
     18 #include "winnmfmt.h"
     19 
     20 #include "unicode/format.h"
     21 #include "unicode/numfmt.h"
     22 #include "unicode/locid.h"
     23 #include "unicode/ustring.h"
     24 
     25 #include "cmemory.h"
     26 #include "uassert.h"
     27 #include "locmap.h"
     28 
     29 #   define WIN32_LEAN_AND_MEAN
     30 #   define VC_EXTRALEAN
     31 #   define NOUSER
     32 #   define NOSERVICE
     33 #   define NOIME
     34 #   define NOMCX
     35 #include <windows.h>
     36 #include <stdio.h>
     37 
     38 U_NAMESPACE_BEGIN
     39 
     40 union FormatInfo
     41 {
     42     NUMBERFMTW   number;
     43     CURRENCYFMTW currency;
     44 };
     45 
     46 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Win32NumberFormat)
     47 
     48 #define NEW_ARRAY(type,count) (type *) uprv_malloc((count) * sizeof(type))
     49 #define DELETE_ARRAY(array) uprv_free((void *) (array))
     50 
     51 #define STACK_BUFFER_SIZE 32
     52 
     53 /*
     54  * Turns a string of the form "3;2;0" into the grouping UINT
     55  * needed for NUMBERFMT and CURRENCYFMT. If the string does not
     56  * end in ";0" then the return value should be multiplied by 10.
     57  * (e.g. "3" => 30, "3;2" => 320)
     58  */
     59 static UINT getGrouping(const char *grouping)
     60 {
     61     UINT g = 0;
     62 	const char *s;
     63 
     64     for (s = grouping; *s != '\0'; s += 1) {
     65         if (*s > '0' && *s < '9') {
     66             g = g * 10 + (*s - '0');
     67         } else if (*s != ';') {
     68             break;
     69         }
     70     }
     71 
     72     if (*s != '0') {
     73         g *= 10;
     74     }
     75 
     76     return g;
     77 }
     78 
     79 static void getNumberFormat(NUMBERFMTW *fmt, int32_t lcid)
     80 {
     81     char buf[10];
     82 
     83     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_IDIGITS, (LPWSTR) &fmt->NumDigits, sizeof(UINT));
     84     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_ILZERO,  (LPWSTR) &fmt->LeadingZero, sizeof(UINT));
     85 
     86     GetLocaleInfoA(lcid, LOCALE_SGROUPING, buf, 10);
     87     fmt->Grouping = getGrouping(buf);
     88 
     89     fmt->lpDecimalSep = NEW_ARRAY(UChar, 6);
     90     GetLocaleInfoW(lcid, LOCALE_SDECIMAL,  fmt->lpDecimalSep,  6);
     91 
     92     fmt->lpThousandSep = NEW_ARRAY(UChar, 6);
     93     GetLocaleInfoW(lcid, LOCALE_STHOUSAND, fmt->lpThousandSep, 6);
     94 
     95     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_INEGNUMBER, (LPWSTR) &fmt->NegativeOrder, sizeof(UINT));
     96 }
     97 
     98 static void freeNumberFormat(NUMBERFMTW *fmt)
     99 {
    100     if (fmt != NULL) {
    101         DELETE_ARRAY(fmt->lpThousandSep);
    102         DELETE_ARRAY(fmt->lpDecimalSep);
    103     }
    104 }
    105 
    106 static void getCurrencyFormat(CURRENCYFMTW *fmt, int32_t lcid)
    107 {
    108     char buf[10];
    109 
    110     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_ICURRDIGITS, (LPWSTR) &fmt->NumDigits, sizeof(UINT));
    111     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_ILZERO, (LPWSTR) &fmt->LeadingZero, sizeof(UINT));
    112 
    113     GetLocaleInfoA(lcid, LOCALE_SMONGROUPING, buf, sizeof(buf));
    114     fmt->Grouping = getGrouping(buf);
    115 
    116     fmt->lpDecimalSep = NEW_ARRAY(UChar, 6);
    117     GetLocaleInfoW(lcid, LOCALE_SMONDECIMALSEP,  fmt->lpDecimalSep,  6);
    118 
    119     fmt->lpThousandSep = NEW_ARRAY(UChar, 6);
    120     GetLocaleInfoW(lcid, LOCALE_SMONTHOUSANDSEP, fmt->lpThousandSep, 6);
    121 
    122     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_INEGCURR,  (LPWSTR) &fmt->NegativeOrder, sizeof(UINT));
    123     GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_ICURRENCY, (LPWSTR) &fmt->PositiveOrder, sizeof(UINT));
    124 
    125     fmt->lpCurrencySymbol = NEW_ARRAY(UChar, 8);
    126     GetLocaleInfoW(lcid, LOCALE_SCURRENCY, (LPWSTR) fmt->lpCurrencySymbol, 8);
    127 }
    128 
    129 static void freeCurrencyFormat(CURRENCYFMTW *fmt)
    130 {
    131     if (fmt != NULL) {
    132         DELETE_ARRAY(fmt->lpCurrencySymbol);
    133         DELETE_ARRAY(fmt->lpThousandSep);
    134         DELETE_ARRAY(fmt->lpDecimalSep);
    135     }
    136 }
    137 
    138 // TODO: keep locale too?
    139 Win32NumberFormat::Win32NumberFormat(const Locale &locale, UBool currency, UErrorCode &status)
    140   : NumberFormat(), fCurrency(currency), fFractionDigitsSet(FALSE), fFormatInfo(NULL)
    141 {
    142     if (!U_FAILURE(status)) {
    143         fLCID = locale.getLCID();
    144 
    145         fFormatInfo = (FormatInfo*)uprv_malloc(sizeof(FormatInfo));
    146 
    147         if (fCurrency) {
    148             getCurrencyFormat(&fFormatInfo->currency, fLCID);
    149         } else {
    150             getNumberFormat(&fFormatInfo->number, fLCID);
    151         }
    152     }
    153 }
    154 
    155 Win32NumberFormat::Win32NumberFormat(const Win32NumberFormat &other)
    156   : NumberFormat(other), fFormatInfo((FormatInfo*)uprv_malloc(sizeof(FormatInfo)))
    157 {
    158     if (fFormatInfo != NULL) {
    159         uprv_memset(fFormatInfo, 0, sizeof(*fFormatInfo));
    160     }
    161     *this = other;
    162 }
    163 
    164 Win32NumberFormat::~Win32NumberFormat()
    165 {
    166     if (fFormatInfo != NULL) {
    167         if (fCurrency) {
    168             freeCurrencyFormat(&fFormatInfo->currency);
    169         } else {
    170             freeNumberFormat(&fFormatInfo->number);
    171         }
    172 
    173         uprv_free(fFormatInfo);
    174     }
    175 }
    176 
    177 Win32NumberFormat &Win32NumberFormat::operator=(const Win32NumberFormat &other)
    178 {
    179     NumberFormat::operator=(other);
    180 
    181     this->fCurrency          = other.fCurrency;
    182     this->fLCID              = other.fLCID;
    183     this->fFractionDigitsSet = other.fFractionDigitsSet;
    184 
    185     if (fCurrency) {
    186         freeCurrencyFormat(&fFormatInfo->currency);
    187         getCurrencyFormat(&fFormatInfo->currency, fLCID);
    188     } else {
    189         freeNumberFormat(&fFormatInfo->number);
    190         getNumberFormat(&fFormatInfo->number, fLCID);
    191     }
    192 
    193     return *this;
    194 }
    195 
    196 Format *Win32NumberFormat::clone(void) const
    197 {
    198     return new Win32NumberFormat(*this);
    199 }
    200 
    201 UnicodeString& Win32NumberFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const
    202 {
    203     return format(getMaximumFractionDigits(), appendTo, L"%.16f", number);
    204 }
    205 
    206 UnicodeString& Win32NumberFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const
    207 {
    208     return format(getMinimumFractionDigits(), appendTo, L"%I32d", number);
    209 }
    210 
    211 UnicodeString& Win32NumberFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const
    212 {
    213     return format(getMinimumFractionDigits(), appendTo, L"%I64d", number);
    214 }
    215 
    216 // TODO: cache Locale and NumberFormat? Could keep locale passed to constructor...
    217 void Win32NumberFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const
    218 {
    219     UErrorCode status = U_ZERO_ERROR;
    220     Locale loc(uprv_convertToPosix(fLCID, &status));
    221     NumberFormat *nf = fCurrency? NumberFormat::createCurrencyInstance(loc, status) : NumberFormat::createInstance(loc, status);
    222 
    223     nf->parse(text, result, parsePosition);
    224     delete nf;
    225 }
    226 void Win32NumberFormat::setMaximumFractionDigits(int32_t newValue)
    227 {
    228     fFractionDigitsSet = TRUE;
    229     NumberFormat::setMaximumFractionDigits(newValue);
    230 }
    231 
    232 void Win32NumberFormat::setMinimumFractionDigits(int32_t newValue)
    233 {
    234     fFractionDigitsSet = TRUE;
    235     NumberFormat::setMinimumFractionDigits(newValue);
    236 }
    237 
    238 UnicodeString &Win32NumberFormat::format(int32_t numDigits, UnicodeString &appendTo, wchar_t *fmt, ...) const
    239 {
    240     wchar_t nStackBuffer[STACK_BUFFER_SIZE];
    241     wchar_t *nBuffer = nStackBuffer;
    242     va_list args;
    243     int result;
    244 
    245     nBuffer[0] = 0x0000;
    246 
    247     /* Due to the arguments causing a result to be <= 23 characters (+2 for NULL and minus),
    248     we don't need to reallocate the buffer. */
    249     va_start(args, fmt);
    250     result = _vsnwprintf(nBuffer, STACK_BUFFER_SIZE, fmt, args);
    251     va_end(args);
    252 
    253     /* Just to make sure of the above statement, we add this assert */
    254     U_ASSERT(result >=0);
    255     // The following code is not used because _vscwprintf isn't available on MinGW at the moment.
    256     /*if (result < 0) {
    257         int newLength;
    258 
    259         va_start(args, fmt);
    260         newLength = _vscwprintf(fmt, args);
    261         va_end(args);
    262 
    263         nBuffer = NEW_ARRAY(UChar, newLength + 1);
    264 
    265         va_start(args, fmt);
    266         result = _vsnwprintf(nBuffer, newLength + 1, fmt, args);
    267         va_end(args);
    268     }*/
    269 
    270     // vswprintf is sensitive to the locale set by setlocale. For some locales
    271     // it doesn't use "." as the decimal separator, which is what GetNumberFormatW
    272     // and GetCurrencyFormatW both expect to see.
    273     //
    274     // To fix this, we scan over the string and replace the first non-digits, except
    275     // for a leading "-", with a "."
    276     //
    277     // Note: (nBuffer[0] == L'-') will evaluate to 1 if there is a leading '-' in the
    278     // number, and 0 otherwise.
    279     for (wchar_t *p = &nBuffer[nBuffer[0] == L'-']; *p != L'\0'; p += 1) {
    280         if (*p < L'0' || *p > L'9') {
    281             *p = L'.';
    282             break;
    283         }
    284     }
    285 
    286     UChar stackBuffer[STACK_BUFFER_SIZE];
    287     UChar *buffer = stackBuffer;
    288     FormatInfo formatInfo;
    289 
    290     formatInfo = *fFormatInfo;
    291     buffer[0] = 0x0000;
    292 
    293     if (fCurrency) {
    294         if (fFractionDigitsSet) {
    295             formatInfo.currency.NumDigits = (UINT) numDigits;
    296         }
    297 
    298         if (!isGroupingUsed()) {
    299             formatInfo.currency.Grouping = 0;
    300         }
    301 
    302         result = GetCurrencyFormatW(fLCID, 0, nBuffer, &formatInfo.currency, buffer, STACK_BUFFER_SIZE);
    303 
    304         if (result == 0) {
    305             DWORD lastError = GetLastError();
    306 
    307             if (lastError == ERROR_INSUFFICIENT_BUFFER) {
    308                 int newLength = GetCurrencyFormatW(fLCID, 0, nBuffer, &formatInfo.currency, NULL, 0);
    309 
    310                 buffer = NEW_ARRAY(UChar, newLength);
    311                 buffer[0] = 0x0000;
    312                 GetCurrencyFormatW(fLCID, 0, nBuffer,  &formatInfo.currency, buffer, newLength);
    313             }
    314         }
    315     } else {
    316         if (fFractionDigitsSet) {
    317             formatInfo.number.NumDigits = (UINT) numDigits;
    318         }
    319 
    320         if (!isGroupingUsed()) {
    321             formatInfo.number.Grouping = 0;
    322         }
    323 
    324         result = GetNumberFormatW(fLCID, 0, nBuffer, &formatInfo.number, buffer, STACK_BUFFER_SIZE);
    325 
    326         if (result == 0) {
    327             if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
    328                 int newLength = GetNumberFormatW(fLCID, 0, nBuffer, &formatInfo.number, NULL, 0);
    329 
    330                 buffer = NEW_ARRAY(UChar, newLength);
    331                 buffer[0] = 0x0000;
    332                 GetNumberFormatW(fLCID, 0, nBuffer, &formatInfo.number, buffer, newLength);
    333             }
    334         }
    335     }
    336 
    337     appendTo.append(buffer, (int32_t) wcslen(buffer));
    338 
    339     if (buffer != stackBuffer) {
    340         DELETE_ARRAY(buffer);
    341     }
    342 
    343     /*if (nBuffer != nStackBuffer) {
    344         DELETE_ARRAY(nBuffer);
    345     }*/
    346 
    347     return appendTo;
    348 }
    349 
    350 U_NAMESPACE_END
    351 
    352 #endif /* #if !UCONFIG_NO_FORMATTING */
    353 
    354 #endif // #ifdef U_WINDOWS
    355