QLocaleData: change validateChars() to return validation State
Instead of returning just bool, return a result struct {State,
CharBuff}, a State is useful as it can have an Intermediate state
where the input isn't Acceptable yet, but not Invalid as such. The
example from the linked bug is in tst_QIntValidator::validateFrench(), a
string "1 ", which can be interpretted as a number with a group
separator, but the input shouldn't end with a group separator (changing
the unittest will be done as part of a separate commit).
CharBuff (QVarLengthArray<char, 256>) replaces the QByteArray input
parameter; a QVarLengthArray means no heap allocation in typical
use-cases with input text < 256 characters to validate. This required
minimum changes (QVLA doesn't have startsWith, replaced by comparing
with buff[0]; and for converting to double, wrapped it in a QBAV).
Task-number: QTBUG-111371
Change-Id: I4e0eb612d470ef03faf52031ddfe9c4bdb31e1e1
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
bb10
parent
6bb61f7e89
commit
67129ef9cd
|
|
@ -4205,11 +4205,12 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o
|
|||
return true;
|
||||
}
|
||||
|
||||
bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray *buff,
|
||||
int decDigits, QLocale::NumberOptions number_options) const
|
||||
ParsingResult
|
||||
QLocaleData::validateChars(QStringView str, NumberMode numMode, int decDigits,
|
||||
QLocale::NumberOptions number_options) const
|
||||
{
|
||||
buff->clear();
|
||||
buff->reserve(str.size());
|
||||
ParsingResult result;
|
||||
result.buff.reserve(str.size());
|
||||
|
||||
enum { Whole, Fractional, Exponent } state = Whole;
|
||||
const bool scientific = numMode == DoubleScientificMode;
|
||||
|
|
@ -4227,14 +4228,14 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
|
|||
case Fractional:
|
||||
// If a double has too many digits in its fractional part it is Invalid.
|
||||
if (decDigits-- == 0)
|
||||
return false;
|
||||
return {};
|
||||
break;
|
||||
case Exponent:
|
||||
if (!isAsciiDigit(last)) {
|
||||
// This is the first digit in the exponent (there may have beena '+'
|
||||
// or '-' in before). If it's a zero, the exponent is zero-padded.
|
||||
if (c == '0' && (number_options & QLocale::RejectLeadingZeroInExponent))
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -4245,7 +4246,7 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
|
|||
// If an integer has a decimal point, it is Invalid.
|
||||
// A double can only have one, at the end of its whole-number part.
|
||||
if (numMode == IntegerMode || state != Whole)
|
||||
return false;
|
||||
return {};
|
||||
// Even when decDigits is 0, we do allow the decimal point to be
|
||||
// present - just as long as no digits follow it.
|
||||
|
||||
|
|
@ -4256,14 +4257,14 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
|
|||
case '-':
|
||||
// A sign can only appear at the start or after the e of scientific:
|
||||
if (last != '\0' && !(scientific && last == 'e'))
|
||||
return false;
|
||||
return {};
|
||||
break;
|
||||
|
||||
case ',':
|
||||
// Grouping is only allowed after a digit in the whole-number portion:
|
||||
if ((number_options & QLocale::RejectGroupSeparator) || state != Whole
|
||||
|| !isAsciiDigit(last)) {
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
// We could check grouping sizes are correct, but fixup()s are
|
||||
// probably better off correcting any misplacement instead.
|
||||
|
|
@ -4272,7 +4273,7 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
|
|||
case 'e':
|
||||
// Only one e is allowed and only in scientific:
|
||||
if (!scientific || state == Exponent)
|
||||
return false;
|
||||
return {};
|
||||
state = Exponent;
|
||||
break;
|
||||
|
||||
|
|
@ -4282,16 +4283,17 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
|
|||
// validators don't accept those values.
|
||||
// For anything else, tokens.nextToken() must have returned 0.
|
||||
Q_ASSERT(!c || c == 'a' || c == 'f' || c == 'i' || c == 'n');
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
last = c;
|
||||
if (c != ',') // Skip grouping
|
||||
buff->append(c);
|
||||
result.buff.append(c);
|
||||
}
|
||||
|
||||
return true;
|
||||
result.state = ParsingResult::Acceptable;
|
||||
return result;
|
||||
}
|
||||
|
||||
double QLocaleData::stringToDouble(QStringView str, bool *ok,
|
||||
|
|
|
|||
|
|
@ -214,6 +214,18 @@ Q_DECLARE_TYPEINFO(QLocaleId, Q_PRIMITIVE_TYPE);
|
|||
|
||||
using CharBuff = QVarLengthArray<char, 256>;
|
||||
|
||||
struct ParsingResult
|
||||
{
|
||||
enum State { // A duplicate of QValidator::State
|
||||
Invalid,
|
||||
Intermediate,
|
||||
Acceptable
|
||||
};
|
||||
|
||||
State state = Invalid;
|
||||
CharBuff buff;
|
||||
};
|
||||
|
||||
struct QLocaleData
|
||||
{
|
||||
public:
|
||||
|
|
@ -361,9 +373,9 @@ public:
|
|||
[[nodiscard]] inline NumericData numericData(NumberMode mode) const;
|
||||
|
||||
// this function is used in QIntValidator (QtGui)
|
||||
[[nodiscard]] Q_CORE_EXPORT bool validateChars(
|
||||
QStringView str, NumberMode numMode, QByteArray *buff, int decDigits = -1,
|
||||
QLocale::NumberOptions number_options = QLocale::DefaultNumberOptions) const;
|
||||
[[nodiscard]] Q_CORE_EXPORT ParsingResult
|
||||
validateChars(QStringView str, NumberMode numMode, int decDigits = -1,
|
||||
QLocale::NumberOptions number_options = QLocale::DefaultNumberOptions) const;
|
||||
|
||||
// Access to assorted data members:
|
||||
[[nodiscard]] QLocaleId id() const
|
||||
|
|
|
|||
|
|
@ -363,8 +363,12 @@ static qlonglong pow10(int exp)
|
|||
}
|
||||
|
||||
template <typename T> static inline
|
||||
std::optional<QValidator::State> initialResultCheck(T min, T max, const QByteArray &buff)
|
||||
std::optional<QValidator::State> initialResultCheck(T min, T max, const ParsingResult &result)
|
||||
{
|
||||
if (result.state == ParsingResult::Invalid)
|
||||
return QValidator::Invalid;
|
||||
|
||||
const CharBuff &buff = result.buff;
|
||||
if (buff.isEmpty())
|
||||
return QValidator::Intermediate;
|
||||
|
||||
|
|
@ -376,21 +380,23 @@ std::optional<QValidator::State> initialResultCheck(T min, T max, const QByteArr
|
|||
if (buff.size() == 1 && (ch == '-' || ch == '+'))
|
||||
return QValidator::Intermediate;
|
||||
|
||||
if (result.state == ParsingResult::Intermediate)
|
||||
return QValidator::Intermediate;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QValidator::State QIntValidator::validate(QString & input, int&) const
|
||||
{
|
||||
QByteArray buff;
|
||||
if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1,
|
||||
locale().numberOptions())) {
|
||||
return Invalid;
|
||||
}
|
||||
ParsingResult result =
|
||||
locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1,
|
||||
locale().numberOptions());
|
||||
|
||||
std::optional<QValidator::State> opt = initialResultCheck(b, t, buff);
|
||||
std::optional<State> opt = initialResultCheck(b, t, result);
|
||||
if (opt)
|
||||
return *opt;
|
||||
|
||||
const CharBuff &buff = result.buff;
|
||||
bool ok;
|
||||
qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok);
|
||||
if (!ok)
|
||||
|
|
@ -421,11 +427,12 @@ QValidator::State QIntValidator::validate(QString & input, int&) const
|
|||
/*! \reimp */
|
||||
void QIntValidator::fixup(QString &input) const
|
||||
{
|
||||
QByteArray buff;
|
||||
if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1,
|
||||
locale().numberOptions())) {
|
||||
auto [parseState, buff] =
|
||||
locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1,
|
||||
locale().numberOptions());
|
||||
if (parseState == ParsingResult::Invalid)
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok);
|
||||
if (ok)
|
||||
|
|
@ -653,12 +660,10 @@ QValidator::State QDoubleValidator::validate(QString & input, int &) const
|
|||
QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QLocaleData::NumberMode numMode, const QLocale &locale) const
|
||||
{
|
||||
Q_Q(const QDoubleValidator);
|
||||
QByteArray buff;
|
||||
if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec, locale.numberOptions())) {
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
ParsingResult result =
|
||||
locale.d->m_data->validateChars(input, numMode, q->dec, locale.numberOptions());
|
||||
|
||||
std::optional<QValidator::State> opt = initialResultCheck(q->b, q->t, buff);
|
||||
std::optional<QValidator::State> opt = initialResultCheck(q->b, q->t, result);
|
||||
if (opt)
|
||||
return *opt;
|
||||
|
||||
|
|
@ -739,15 +744,16 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe
|
|||
const QLocale &locale) const
|
||||
{
|
||||
Q_Q(const QDoubleValidator);
|
||||
QByteArray buff;
|
||||
// Passing -1 as the number of decimals, because fixup() exists to improve
|
||||
// an Intermediate value, if it can.
|
||||
if (!locale.d->m_data->validateChars(input, numMode, &buff, -1, locale.numberOptions()))
|
||||
auto [parseState, buff] =
|
||||
locale.d->m_data->validateChars(input, numMode, -1, locale.numberOptions());
|
||||
if (parseState == ParsingResult::Invalid)
|
||||
return;
|
||||
|
||||
// buff now contains data in C locale.
|
||||
// buff contains data in C locale.
|
||||
bool ok = false;
|
||||
const double entered = buff.toDouble(&ok);
|
||||
const double entered = QByteArrayView(buff).toDouble(&ok);
|
||||
if (ok) {
|
||||
// Here we need to adjust the output format accordingly
|
||||
char mode;
|
||||
|
|
@ -771,7 +777,7 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe
|
|||
if (eIndex < 0)
|
||||
eIndex = buff.size();
|
||||
precision = eIndex - (buff.contains('.') ? 1 : 0)
|
||||
- (buff.startsWith('-') || buff.startsWith('+') ? 1 : 0);
|
||||
- (buff[0] == '-' || buff[0] == '+' ? 1 : 0);
|
||||
}
|
||||
// Use q->dec to limit the number of decimals, because we want the
|
||||
// fixup() result to pass validate().
|
||||
|
|
|
|||
Loading…
Reference in New Issue