1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************** 5 * Copyright (C) 2005-2016, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ******************************************************************************** 8 * 9 * File WINNMFMT.CPP 10 * 11 ******************************************************************************** 12 */ 13 14 #include "unicode/utypes.h" 15 16 #if U_PLATFORM_USES_ONLY_WIN32_API 17 18 #if !UCONFIG_NO_FORMATTING 19 20 #include "winnmfmt.h" 21 22 #include "unicode/format.h" 23 #include "unicode/numfmt.h" 24 #include "unicode/locid.h" 25 #include "unicode/ustring.h" 26 27 #include "cmemory.h" 28 #include "uassert.h" 29 #include "locmap.h" 30 31 #ifndef WIN32_LEAN_AND_MEAN 32 # define WIN32_LEAN_AND_MEAN 33 #endif 34 # define VC_EXTRALEAN 35 # define NOUSER 36 # define NOSERVICE 37 # define NOIME 38 # define NOMCX 39 #include <windows.h> 40 #include <stdio.h> 41 42 U_NAMESPACE_BEGIN 43 44 union FormatInfo 45 { 46 NUMBERFMTW number; 47 CURRENCYFMTW currency; 48 }; 49 50 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Win32NumberFormat) 51 52 #define NEW_ARRAY(type,count) (type *) uprv_malloc((count) * sizeof(type)) 53 #define DELETE_ARRAY(array) uprv_free((void *) (array)) 54 55 #define STACK_BUFFER_SIZE 32 56 57 /* 58 * Turns a string of the form "3;2;0" into the grouping UINT 59 * needed for NUMBERFMT and CURRENCYFMT. If the string does not 60 * end in ";0" then the return value should be multiplied by 10. 61 * (e.g. "3" => 30, "3;2" => 320) 62 */ 63 static UINT getGrouping(const wchar_t *grouping) 64 { 65 UINT g = 0; 66 const wchar_t *s; 67 68 for (s = grouping; *s != L'\0'; s += 1) { 69 if (*s > L'0' && *s < L'9') { 70 g = g * 10 + (*s - L'0'); 71 } else if (*s != L';') { 72 break; 73 } 74 } 75 76 if (*s != L'0') { 77 g *= 10; 78 } 79 80 return g; 81 } 82 83 static void getNumberFormat(NUMBERFMTW *fmt, const wchar_t *windowsLocaleName) 84 { 85 wchar_t buf[10]; 86 87 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_IDIGITS, (LPWSTR) &fmt->NumDigits, sizeof(UINT)); 88 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_ILZERO, (LPWSTR) &fmt->LeadingZero, sizeof(UINT)); 89 90 GetLocaleInfoEx(windowsLocaleName, LOCALE_SGROUPING, (LPWSTR)buf, 10); 91 fmt->Grouping = getGrouping(buf); 92 93 fmt->lpDecimalSep = NEW_ARRAY(wchar_t, 6); 94 GetLocaleInfoEx(windowsLocaleName, LOCALE_SDECIMAL, fmt->lpDecimalSep, 6); 95 96 fmt->lpThousandSep = NEW_ARRAY(wchar_t, 6); 97 GetLocaleInfoEx(windowsLocaleName, LOCALE_STHOUSAND, fmt->lpThousandSep, 6); 98 99 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_INEGNUMBER, (LPWSTR) &fmt->NegativeOrder, sizeof(UINT)); 100 } 101 102 static void freeNumberFormat(NUMBERFMTW *fmt) 103 { 104 if (fmt != NULL) { 105 DELETE_ARRAY(fmt->lpThousandSep); 106 DELETE_ARRAY(fmt->lpDecimalSep); 107 } 108 } 109 110 static void getCurrencyFormat(CURRENCYFMTW *fmt, const wchar_t *windowsLocaleName) 111 { 112 wchar_t buf[10]; 113 114 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_ICURRDIGITS, (LPWSTR) &fmt->NumDigits, sizeof(UINT)); 115 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_ILZERO, (LPWSTR) &fmt->LeadingZero, sizeof(UINT)); 116 117 GetLocaleInfoEx(windowsLocaleName, LOCALE_SMONGROUPING, (LPWSTR)buf, sizeof(buf)); 118 fmt->Grouping = getGrouping(buf); 119 120 fmt->lpDecimalSep = NEW_ARRAY(wchar_t, 6); 121 GetLocaleInfoEx(windowsLocaleName, LOCALE_SMONDECIMALSEP, fmt->lpDecimalSep, 6); 122 123 fmt->lpThousandSep = NEW_ARRAY(wchar_t, 6); 124 GetLocaleInfoEx(windowsLocaleName, LOCALE_SMONTHOUSANDSEP, fmt->lpThousandSep, 6); 125 126 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_INEGCURR, (LPWSTR) &fmt->NegativeOrder, sizeof(UINT)); 127 GetLocaleInfoEx(windowsLocaleName, LOCALE_RETURN_NUMBER|LOCALE_ICURRENCY, (LPWSTR) &fmt->PositiveOrder, sizeof(UINT)); 128 129 fmt->lpCurrencySymbol = NEW_ARRAY(wchar_t, 8); 130 GetLocaleInfoEx(windowsLocaleName, LOCALE_SCURRENCY, (LPWSTR) fmt->lpCurrencySymbol, 8); 131 } 132 133 static void freeCurrencyFormat(CURRENCYFMTW *fmt) 134 { 135 if (fmt != NULL) { 136 DELETE_ARRAY(fmt->lpCurrencySymbol); 137 DELETE_ARRAY(fmt->lpThousandSep); 138 DELETE_ARRAY(fmt->lpDecimalSep); 139 } 140 } 141 142 // TODO: This is copied in both winnmfmt.cpp and windtfmt.cpp, but really should 143 // be factored out into a common helper for both. 144 static UErrorCode GetEquivalentWindowsLocaleName(const Locale& locale, UnicodeString** buffer) 145 { 146 UErrorCode status = U_ZERO_ERROR; 147 char asciiBCP47Tag[LOCALE_NAME_MAX_LENGTH] = {}; 148 149 // Convert from names like "en_CA" and "de_DE@collation=phonebook" to "en-CA" and "de-DE-u-co-phonebk". 150 (void) uloc_toLanguageTag(locale.getName(), asciiBCP47Tag, UPRV_LENGTHOF(asciiBCP47Tag), FALSE, &status); 151 152 if (U_SUCCESS(status)) 153 { 154 // Need it to be UTF-16, not 8-bit 155 // TODO: This seems like a good thing for a helper 156 wchar_t bcp47Tag[LOCALE_NAME_MAX_LENGTH] = {}; 157 int32_t i; 158 for (i = 0; i < UPRV_LENGTHOF(bcp47Tag); i++) 159 { 160 if (asciiBCP47Tag[i] == '\0') 161 { 162 break; 163 } 164 else 165 { 166 // normally just copy the character 167 bcp47Tag[i] = static_cast<wchar_t>(asciiBCP47Tag[i]); 168 } 169 } 170 171 // Ensure it's null terminated 172 if (i < (UPRV_LENGTHOF(bcp47Tag) - 1)) 173 { 174 bcp47Tag[i] = L'\0'; 175 } 176 else 177 { 178 // Ran out of room. 179 bcp47Tag[UPRV_LENGTHOF(bcp47Tag) - 1] = L'\0'; 180 } 181 182 183 wchar_t windowsLocaleName[LOCALE_NAME_MAX_LENGTH] = {}; 184 185 // Note: On Windows versions below 10, there is no support for locale name aliases. 186 // This means that it will fail for locales where ICU has a completely different 187 // name (like ku vs ckb), and it will also not work for alternate sort locale 188 // names like "de-DE-u-co-phonebk". 189 190 // TODO: We could add some sort of exception table for cases like ku vs ckb. 191 192 int length = ResolveLocaleName(bcp47Tag, windowsLocaleName, UPRV_LENGTHOF(windowsLocaleName)); 193 194 if (length > 0) 195 { 196 *buffer = new UnicodeString(windowsLocaleName); 197 } 198 else 199 { 200 status = U_UNSUPPORTED_ERROR; 201 } 202 } 203 return status; 204 } 205 206 Win32NumberFormat::Win32NumberFormat(const Locale &locale, UBool currency, UErrorCode &status) 207 : NumberFormat(), fCurrency(currency), fFormatInfo(NULL), fFractionDigitsSet(FALSE), fWindowsLocaleName(nullptr) 208 { 209 if (!U_FAILURE(status)) { 210 fLCID = locale.getLCID(); 211 212 GetEquivalentWindowsLocaleName(locale, &fWindowsLocaleName); 213 // Note: In the previous code, it would look up the LCID for the locale, and if 214 // the locale was not recognized then it would get an LCID of 0, which is a 215 // synonym for LOCALE_USER_DEFAULT on Windows. 216 // If the above method fails, then fWindowsLocaleName will remain as nullptr, and 217 // then we will pass nullptr to API GetLocaleInfoEx, which is the same as passing 218 // LOCALE_USER_DEFAULT. 219 220 // Resolve actual locale to be used later 221 UErrorCode tmpsts = U_ZERO_ERROR; 222 char tmpLocID[ULOC_FULLNAME_CAPACITY]; 223 int32_t len = uloc_getLocaleForLCID(fLCID, tmpLocID, UPRV_LENGTHOF(tmpLocID) - 1, &tmpsts); 224 if (U_SUCCESS(tmpsts)) { 225 tmpLocID[len] = 0; 226 fLocale = Locale((const char*)tmpLocID); 227 } 228 229 const wchar_t *localeName = nullptr; 230 231 if (fWindowsLocaleName != nullptr) 232 { 233 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 234 } 235 236 fFormatInfo = (FormatInfo*)uprv_malloc(sizeof(FormatInfo)); 237 238 if (fCurrency) { 239 getCurrencyFormat(&fFormatInfo->currency, localeName); 240 } else { 241 getNumberFormat(&fFormatInfo->number, localeName); 242 } 243 } 244 } 245 246 Win32NumberFormat::Win32NumberFormat(const Win32NumberFormat &other) 247 : NumberFormat(other), fFormatInfo((FormatInfo*)uprv_malloc(sizeof(FormatInfo))) 248 { 249 if (fFormatInfo != NULL) { 250 uprv_memset(fFormatInfo, 0, sizeof(*fFormatInfo)); 251 } 252 *this = other; 253 } 254 255 Win32NumberFormat::~Win32NumberFormat() 256 { 257 if (fFormatInfo != NULL) { 258 if (fCurrency) { 259 freeCurrencyFormat(&fFormatInfo->currency); 260 } else { 261 freeNumberFormat(&fFormatInfo->number); 262 } 263 264 uprv_free(fFormatInfo); 265 } 266 delete fWindowsLocaleName; 267 } 268 269 Win32NumberFormat &Win32NumberFormat::operator=(const Win32NumberFormat &other) 270 { 271 NumberFormat::operator=(other); 272 273 this->fCurrency = other.fCurrency; 274 this->fLocale = other.fLocale; 275 this->fLCID = other.fLCID; 276 this->fFractionDigitsSet = other.fFractionDigitsSet; 277 this->fWindowsLocaleName = other.fWindowsLocaleName == NULL ? NULL : new UnicodeString(*other.fWindowsLocaleName); 278 279 const wchar_t *localeName = nullptr; 280 281 if (fWindowsLocaleName != nullptr) 282 { 283 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 284 } 285 286 if (fCurrency) { 287 freeCurrencyFormat(&fFormatInfo->currency); 288 getCurrencyFormat(&fFormatInfo->currency, localeName); 289 } else { 290 freeNumberFormat(&fFormatInfo->number); 291 getNumberFormat(&fFormatInfo->number, localeName); 292 } 293 294 return *this; 295 } 296 297 Format *Win32NumberFormat::clone(void) const 298 { 299 return new Win32NumberFormat(*this); 300 } 301 302 UnicodeString& Win32NumberFormat::format(double number, UnicodeString& appendTo, FieldPosition& /* pos */) const 303 { 304 return format(getMaximumFractionDigits(), appendTo, L"%.16f", number); 305 } 306 307 UnicodeString& Win32NumberFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& /* pos */) const 308 { 309 return format(getMinimumFractionDigits(), appendTo, L"%I32d", number); 310 } 311 312 UnicodeString& Win32NumberFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& /* pos */) const 313 { 314 return format(getMinimumFractionDigits(), appendTo, L"%I64d", number); 315 } 316 317 void Win32NumberFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const 318 { 319 UErrorCode status = U_ZERO_ERROR; 320 NumberFormat *nf = fCurrency? NumberFormat::createCurrencyInstance(fLocale, status) : NumberFormat::createInstance(fLocale, status); 321 322 nf->parse(text, result, parsePosition); 323 delete nf; 324 } 325 void Win32NumberFormat::setMaximumFractionDigits(int32_t newValue) 326 { 327 fFractionDigitsSet = TRUE; 328 NumberFormat::setMaximumFractionDigits(newValue); 329 } 330 331 void Win32NumberFormat::setMinimumFractionDigits(int32_t newValue) 332 { 333 fFractionDigitsSet = TRUE; 334 NumberFormat::setMinimumFractionDigits(newValue); 335 } 336 337 UnicodeString &Win32NumberFormat::format(int32_t numDigits, UnicodeString &appendTo, const wchar_t *fmt, ...) const 338 { 339 wchar_t nStackBuffer[STACK_BUFFER_SIZE]; 340 wchar_t *nBuffer = nStackBuffer; 341 va_list args; 342 int result; 343 344 nBuffer[0] = 0x0000; 345 346 /* Due to the arguments causing a result to be <= 23 characters (+2 for NULL and minus), 347 we don't need to reallocate the buffer. */ 348 va_start(args, fmt); 349 result = _vsnwprintf(nBuffer, STACK_BUFFER_SIZE, fmt, args); 350 va_end(args); 351 352 /* Just to make sure of the above statement, we add this assert */ 353 U_ASSERT(result >=0); 354 // The following code is not used because _vscwprintf isn't available on MinGW at the moment. 355 /*if (result < 0) { 356 int newLength; 357 358 va_start(args, fmt); 359 newLength = _vscwprintf(fmt, args); 360 va_end(args); 361 362 nBuffer = NEW_ARRAY(UChar, newLength + 1); 363 364 va_start(args, fmt); 365 result = _vsnwprintf(nBuffer, newLength + 1, fmt, args); 366 va_end(args); 367 }*/ 368 369 // vswprintf is sensitive to the locale set by setlocale. For some locales 370 // it doesn't use "." as the decimal separator, which is what GetNumberFormatW 371 // and GetCurrencyFormatW both expect to see. 372 // 373 // To fix this, we scan over the string and replace the first non-digits, except 374 // for a leading "-", with a "." 375 // 376 // Note: (nBuffer[0] == L'-') will evaluate to 1 if there is a leading '-' in the 377 // number, and 0 otherwise. 378 for (wchar_t *p = &nBuffer[nBuffer[0] == L'-']; *p != L'\0'; p += 1) { 379 if (*p < L'0' || *p > L'9') { 380 *p = L'.'; 381 break; 382 } 383 } 384 385 wchar_t stackBuffer[STACK_BUFFER_SIZE]; 386 wchar_t *buffer = stackBuffer; 387 FormatInfo formatInfo; 388 389 formatInfo = *fFormatInfo; 390 buffer[0] = 0x0000; 391 392 const wchar_t *localeName = nullptr; 393 394 if (fWindowsLocaleName != nullptr) 395 { 396 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 397 } 398 399 if (fCurrency) { 400 if (fFractionDigitsSet) { 401 formatInfo.currency.NumDigits = (UINT) numDigits; 402 } 403 404 if (!isGroupingUsed()) { 405 formatInfo.currency.Grouping = 0; 406 } 407 408 result = GetCurrencyFormatEx(localeName, 0, nBuffer, &formatInfo.currency, buffer, STACK_BUFFER_SIZE); 409 410 if (result == 0) { 411 DWORD lastError = GetLastError(); 412 413 if (lastError == ERROR_INSUFFICIENT_BUFFER) { 414 int newLength = GetCurrencyFormatEx(localeName, 0, nBuffer, &formatInfo.currency, NULL, 0); 415 416 buffer = NEW_ARRAY(wchar_t, newLength); 417 buffer[0] = 0x0000; 418 GetCurrencyFormatEx(localeName, 0, nBuffer, &formatInfo.currency, buffer, newLength); 419 } 420 } 421 } else { 422 if (fFractionDigitsSet) { 423 formatInfo.number.NumDigits = (UINT) numDigits; 424 } 425 426 if (!isGroupingUsed()) { 427 formatInfo.number.Grouping = 0; 428 } 429 430 result = GetNumberFormatEx(localeName, 0, nBuffer, &formatInfo.number, buffer, STACK_BUFFER_SIZE); 431 432 if (result == 0) { 433 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 434 int newLength = GetNumberFormatEx(localeName, 0, nBuffer, &formatInfo.number, NULL, 0); 435 436 buffer = NEW_ARRAY(wchar_t, newLength); 437 buffer[0] = 0x0000; 438 GetNumberFormatEx(localeName, 0, nBuffer, &formatInfo.number, buffer, newLength); 439 } 440 } 441 } 442 443 appendTo.append((UChar *)buffer, (int32_t) wcslen(buffer)); 444 445 if (buffer != stackBuffer) { 446 DELETE_ARRAY(buffer); 447 } 448 449 /*if (nBuffer != nStackBuffer) { 450 DELETE_ARRAY(nBuffer); 451 }*/ 452 453 return appendTo; 454 } 455 456 U_NAMESPACE_END 457 458 #endif /* #if !UCONFIG_NO_FORMATTING */ 459 460 #endif // U_PLATFORM_USES_ONLY_WIN32_API 461