Fix MS-Win system locale code to return QString for numeric tokens

QSystemLocale::query() is specified to return a QString (wrapped in a
QVariant) for the various tokens used in formatting numbers (zero
digit, signs, separators) but the MS-Win back-end was returning QChar
(wrapped as QVariant) instead, using the first UCS-2 code-point of the
string (even if this was the first of a surrogate pair). The same
error shall be perpetrated by its caller, but we can at least DTRT in
the back-end, ready for the coming fix (in Qt 6) to its caller.

In the process, eliminate some local variables that shadowed a member
variable and adapt number-conversion to cope with surrogate-pair
digits. Optimised the latter for the case where zero is "0".

Change-Id: Idfb416c301add4c961dde613b3dc28b2e31fd0af
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2020-01-06 17:47:41 +01:00
parent e05511a364
commit c0ea88a677

View File

@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2020 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation. ** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
@ -106,11 +106,11 @@ struct QSystemLocalePrivate
{ {
QSystemLocalePrivate(); QSystemLocalePrivate();
QChar zeroDigit(); QString zeroDigit();
QChar decimalPoint(); QString decimalPoint();
QChar groupSeparator(); QString groupSeparator();
QChar negativeSign(); QString negativeSign();
QChar positiveSign(); QString positiveSign();
QVariant dateFormat(QLocale::FormatType); QVariant dateFormat(QLocale::FormatType);
QVariant timeFormat(QLocale::FormatType); QVariant timeFormat(QLocale::FormatType);
QVariant dateTimeFormat(QLocale::FormatType); QVariant dateTimeFormat(QLocale::FormatType);
@ -147,12 +147,11 @@ private:
WCHAR lcName[LOCALE_NAME_MAX_LENGTH]; WCHAR lcName[LOCALE_NAME_MAX_LENGTH];
#endif #endif
SubstitutionType substitutionType; SubstitutionType substitutionType;
QChar zero; QString zero; // cached value for zeroDigit()
int getLocaleInfo(LCTYPE type, LPWSTR data, int size); int getLocaleInfo(LCTYPE type, LPWSTR data, int size);
QString getLocaleInfo(LCTYPE type, int maxlen = 0); QString getLocaleInfo(LCTYPE type, int maxlen = 0);
int getLocaleInfo_int(LCTYPE type, int maxlen = 0); int getLocaleInfo_int(LCTYPE type, int maxlen = 0);
QChar getLocaleInfo_qchar(LCTYPE type);
int getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size); int getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size);
int getDateFormat(DWORD flags, const SYSTEMTIME * date, LPCWSTR format, LPWSTR data, int size); int getDateFormat(DWORD flags, const SYSTEMTIME * date, LPCWSTR format, LPWSTR data, int size);
@ -236,12 +235,6 @@ int QSystemLocalePrivate::getLocaleInfo_int(LCTYPE type, int maxlen)
return ok ? v : 0; return ok ? v : 0;
} }
QChar QSystemLocalePrivate::getLocaleInfo_qchar(LCTYPE type)
{
QString str = getLocaleInfo(type);
return str.isEmpty() ? QChar() : str.at(0);
}
QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution() QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution()
{ {
if (substitutionType == SUnknown) { if (substitutionType == SUnknown) {
@ -257,13 +250,12 @@ QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution()
else if (buf[0] == '2') else if (buf[0] == '2')
substitutionType = QSystemLocalePrivate::SAlways; substitutionType = QSystemLocalePrivate::SAlways;
else { else {
wchar_t digits[11]; wchar_t digits[11]; // See zeroDigit() for why 11.
if (!getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) { if (!getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) {
substitutionType = QSystemLocalePrivate::SNever; substitutionType = QSystemLocalePrivate::SNever;
return substitutionType; return substitutionType;
} }
const wchar_t zero = digits[0]; if (buf[0] == digits[0] + 2)
if (buf[0] == zero + 2)
substitutionType = QSystemLocalePrivate::SAlways; substitutionType = QSystemLocalePrivate::SAlways;
else else
substitutionType = QSystemLocalePrivate::SNever; substitutionType = QSystemLocalePrivate::SNever;
@ -274,40 +266,75 @@ QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution()
QString &QSystemLocalePrivate::substituteDigits(QString &string) QString &QSystemLocalePrivate::substituteDigits(QString &string)
{ {
ushort zero = zeroDigit().unicode(); zeroDigit(); // Ensure zero is set.
ushort *qch = reinterpret_cast<ushort *>(string.data()); switch (zero.size()) {
for (ushort *end = qch + string.size(); qch != end; ++qch) { case 1: {
if (*qch >= '0' && *qch <= '9') const ushort offset = zero.at(0).unicode() - '0';
*qch = zero + (*qch - '0'); if (!offset) // Nothing to do
break;
Q_ASSERT(offset > 9);
ushort *const qch = reinterpret_cast<ushort *>(string.data());
for (int i = 0, stop = string.size(); i < stop; ++i) {
ushort &ch = qch[i];
if (ch >= '0' && ch <= '9')
ch += offset;
}
break;
}
case 2: {
// Surrogate pair (high, low):
uint digit = QChar::surrogateToUcs4(zero.at(0), zero.at(1));
for (int i = 0; i < 10; i++) {
const QChar s[2] = { QChar::highSurrogate(digit + i), QChar::lowSurrogate(digit + i) };
string.replace(QString(QLatin1Char('0' + i)), QString(s, 2));
}
break;
}
default:
Q_ASSERT(!"Expected zero digit to be a single UCS2 code-point or a surrogate pair");
case 0: // Apparently this locale info was not available.
break;
} }
return string; return string;
} }
QChar QSystemLocalePrivate::zeroDigit() QString QSystemLocalePrivate::zeroDigit()
{ {
if (zero.isNull()) if (zero.isEmpty()) {
zero = getLocaleInfo_qchar(LOCALE_SNATIVEDIGITS); /* Ten digits plus a terminator.
https://docs.microsoft.com/en-us/windows/win32/intl/locale-snative-constants
"Native equivalents of ASCII 0 through 9. The maximum number of
characters allowed for this string is eleven, including a terminating
null character."
*/
wchar_t digits[11];
if (getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) {
// assert all(digits[i] == i + digits[0] for i in range(1, 10)), assumed above
zero = QString::fromWCharArray(digits, 1);
}
}
return zero; return zero;
} }
QChar QSystemLocalePrivate::decimalPoint() QString QSystemLocalePrivate::decimalPoint()
{ {
return getLocaleInfo_qchar(LOCALE_SDECIMAL); return getLocaleInfo(LOCALE_SDECIMAL);
} }
QChar QSystemLocalePrivate::groupSeparator() QString QSystemLocalePrivate::groupSeparator()
{ {
return getLocaleInfo_qchar(LOCALE_STHOUSAND); return getLocaleInfo(LOCALE_STHOUSAND);
} }
QChar QSystemLocalePrivate::negativeSign() QString QSystemLocalePrivate::negativeSign()
{ {
return getLocaleInfo_qchar(LOCALE_SNEGATIVESIGN); return getLocaleInfo(LOCALE_SNEGATIVESIGN);
} }
QChar QSystemLocalePrivate::positiveSign() QString QSystemLocalePrivate::positiveSign()
{ {
return getLocaleInfo_qchar(LOCALE_SPOSITIVESIGN); return getLocaleInfo(LOCALE_SPOSITIVESIGN);
} }
QVariant QSystemLocalePrivate::dateFormat(QLocale::FormatType type) QVariant QSystemLocalePrivate::dateFormat(QLocale::FormatType type)
@ -677,7 +704,7 @@ void QSystemLocalePrivate::update()
GetUserDefaultLocaleName(lcName, LOCALE_NAME_MAX_LENGTH); GetUserDefaultLocaleName(lcName, LOCALE_NAME_MAX_LENGTH);
#endif #endif
substitutionType = SUnknown; substitutionType = SUnknown;
zero = QChar(); zero.resize(0);
} }
QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt) QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt)
@ -749,7 +776,7 @@ QLocale QSystemLocale::fallbackUiLocale() const
return QLocale(QString::fromLatin1(getWinLocaleName())); return QLocale(QString::fromLatin1(getWinLocaleName()));
} }
QVariant QSystemLocale::query(QueryType type, QVariant in = QVariant()) const QVariant QSystemLocale::query(QueryType type, QVariant in) const
{ {
QSystemLocalePrivate *d = systemLocalePrivate(); QSystemLocalePrivate *d = systemLocalePrivate();
switch(type) { switch(type) {