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