2017-01-03 10:14:32 +01:00
|
|
|
/***************************************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2016 Roman Telezhynskyi <dismine(at)gmail.com>
|
|
|
|
**
|
|
|
|
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
|
|
** software and associated documentation files (the "Software"), to deal in the Software
|
|
|
|
** without restriction, including without limitation the rights to use, copy, modify,
|
|
|
|
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
|
|
**
|
|
|
|
** The above copyright notice and this permission notice shall be included in all copies or
|
|
|
|
** substantial portions of the Software.
|
|
|
|
**
|
|
|
|
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
|
|
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
**
|
|
|
|
******************************************************************************************************/
|
|
|
|
|
|
|
|
#include "qmudef.h"
|
|
|
|
|
|
|
|
#include <QLocale>
|
|
|
|
#include <QSet>
|
|
|
|
|
|
|
|
enum State
|
|
|
|
{
|
|
|
|
Init = 0,
|
|
|
|
Sign = 1,
|
|
|
|
Thousand = 2,
|
|
|
|
Mantissa = 3,
|
|
|
|
Dot = 4,
|
|
|
|
Abscissa = 5,
|
|
|
|
ExpMark = 6,
|
|
|
|
ExpSign = 7,
|
|
|
|
Exponent = 8,
|
|
|
|
Done = 9
|
|
|
|
};
|
|
|
|
|
|
|
|
enum InputToken
|
|
|
|
{
|
|
|
|
InputSign = 1,
|
|
|
|
InputThousand = 2,
|
|
|
|
InputDigit = 3,
|
|
|
|
InputDot = 4,
|
|
|
|
InputExp = 5
|
|
|
|
};
|
|
|
|
|
|
|
|
static const QChar QmuEOF = QChar(static_cast<ushort>(0xffff)); //guaranteed not to be a character.
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
static QChar GetChar(const QString &formula, int &index)
|
|
|
|
{
|
|
|
|
if (index >= formula.size())
|
|
|
|
{
|
|
|
|
return QmuEOF;
|
|
|
|
}
|
|
|
|
|
|
|
|
return formula.at(index++);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
static QChar EatWhiteSpace(const QString &formula, int &index)
|
|
|
|
{
|
|
|
|
QChar c;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
c = GetChar(formula, index);
|
|
|
|
}
|
|
|
|
while ( c != QmuEOF && c.isSpace() );
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
static int CheckChar(QChar &c, const QLocale &locale, const QChar &decimal, const QChar &thousand)
|
|
|
|
{
|
|
|
|
INIT_LOCALE_VARIABLES(locale);
|
2017-07-26 18:28:26 +02:00
|
|
|
Q_UNUSED(decimalPoint)
|
|
|
|
Q_UNUSED(groupSeparator)
|
2017-01-03 10:14:32 +01:00
|
|
|
|
|
|
|
if (c == positiveSign)
|
|
|
|
{
|
|
|
|
c = '+';
|
|
|
|
return InputToken::InputSign;
|
|
|
|
}
|
|
|
|
else if (c == negativeSign)
|
|
|
|
{
|
|
|
|
c = '-';
|
|
|
|
return InputToken::InputSign;
|
|
|
|
}
|
|
|
|
else if (c == sign0)
|
|
|
|
{
|
|
|
|
c = '0';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign1)
|
|
|
|
{
|
|
|
|
c = '1';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign2)
|
|
|
|
{
|
|
|
|
c = '2';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign3)
|
|
|
|
{
|
|
|
|
c = '3';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign4)
|
|
|
|
{
|
|
|
|
c = '4';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign5)
|
|
|
|
{
|
|
|
|
c = '5';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign6)
|
|
|
|
{
|
|
|
|
c = '6';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign7)
|
|
|
|
{
|
|
|
|
c = '7';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign8)
|
|
|
|
{
|
|
|
|
c = '8';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == sign9)
|
|
|
|
{
|
|
|
|
c = '9';
|
|
|
|
return InputToken::InputDigit;
|
|
|
|
}
|
|
|
|
else if (c == decimal)
|
|
|
|
{
|
|
|
|
return InputToken::InputDot;
|
|
|
|
}
|
|
|
|
else if (c == thousand)
|
|
|
|
{
|
|
|
|
return InputToken::InputThousand;
|
|
|
|
}
|
|
|
|
else if (c == expLower)
|
|
|
|
{
|
|
|
|
c = 'e';
|
|
|
|
return InputToken::InputExp;
|
|
|
|
}
|
|
|
|
else if (c == expUpper)
|
|
|
|
{
|
|
|
|
c = 'E';
|
|
|
|
return InputToken::InputExp;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
int ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QChar &decimal, const QChar &thousand)
|
|
|
|
{
|
|
|
|
// Must not be equal
|
|
|
|
if (decimal == thousand || formula.isEmpty())
|
|
|
|
{
|
|
|
|
val = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LOCALE_VARIABLES(locale);
|
2017-07-26 18:28:26 +02:00
|
|
|
Q_UNUSED(decimalPoint)
|
|
|
|
Q_UNUSED(groupSeparator)
|
2017-01-03 10:14:32 +01:00
|
|
|
|
2018-04-22 17:32:55 +02:00
|
|
|
QSet<QChar> reserved
|
|
|
|
{
|
|
|
|
positiveSign,
|
|
|
|
negativeSign,
|
|
|
|
sign0,
|
|
|
|
sign1,
|
|
|
|
sign2,
|
|
|
|
sign3,
|
|
|
|
sign4,
|
|
|
|
sign5,
|
|
|
|
sign6,
|
|
|
|
sign7,
|
|
|
|
sign8,
|
|
|
|
sign9,
|
|
|
|
expUpper,
|
|
|
|
expLower
|
|
|
|
};
|
2017-01-03 10:14:32 +01:00
|
|
|
|
|
|
|
if (reserved.contains(decimal) || reserved.contains(thousand))
|
|
|
|
{
|
|
|
|
val = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// row - current state, column - new state
|
|
|
|
static uchar table[9][6] =
|
|
|
|
{
|
|
|
|
/* None InputSign InputThousand InputDigit InputDot InputExp */
|
|
|
|
{ 0, State::Sign, 0, State::Mantissa, State::Dot, 0, }, // Init
|
|
|
|
{ 0, 0, 0, State::Mantissa, State::Dot, 0, }, // Sign
|
|
|
|
{ 0, 0, 0, State::Mantissa, 0, 0, }, // Thousand
|
|
|
|
{ State::Done, State::Done, State::Thousand, State::Mantissa, State::Dot, State::ExpMark,}, // Mantissa
|
|
|
|
{ 0, 0, 0, State::Abscissa, 0, 0, }, // Dot
|
|
|
|
{ State::Done, State::Done, 0, State::Abscissa, 0, State::ExpMark,}, // Abscissa
|
|
|
|
{ 0, State::ExpSign, 0, State::Exponent, 0, 0, }, // ExpMark
|
|
|
|
{ 0, 0, 0, State::Exponent, 0, 0, }, // ExpSign
|
|
|
|
{ State::Done, 0, 0, State::Exponent, 0, State::Done } // Exponent
|
|
|
|
};
|
|
|
|
|
|
|
|
int state = State::Init; // parse state
|
|
|
|
QString buf;
|
|
|
|
|
|
|
|
int index = 0; // start position
|
|
|
|
QChar c = EatWhiteSpace(formula, index);
|
|
|
|
|
|
|
|
while ( true )
|
|
|
|
{
|
2017-01-03 10:32:19 +01:00
|
|
|
const int input = CheckChar(c, locale, decimal, thousand);// input token
|
2017-01-03 10:14:32 +01:00
|
|
|
|
|
|
|
state = table[state][input];
|
|
|
|
|
|
|
|
if (state == 0)
|
|
|
|
{
|
|
|
|
val = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (state == Done)
|
|
|
|
{
|
|
|
|
// Convert to C locale
|
|
|
|
QLocale cLocale(QLocale::C);
|
|
|
|
const QChar cDecimal = cLocale.decimalPoint();
|
|
|
|
const QChar cThousand = cLocale.groupSeparator();
|
|
|
|
if (locale != cLocale && (cDecimal != decimal || cThousand != thousand))
|
|
|
|
{
|
|
|
|
if (decimal == cThousand)
|
|
|
|
{// Handle reverse to C locale case: thousand '.', decimal ','
|
2018-04-22 17:32:55 +02:00
|
|
|
const QChar tmpThousand = QLatin1Char('@');
|
2017-01-03 10:14:32 +01:00
|
|
|
buf.replace(thousand, tmpThousand);
|
|
|
|
buf.replace(decimal, cDecimal);
|
|
|
|
buf.replace(tmpThousand, cThousand);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf.replace(thousand, cThousand);
|
|
|
|
buf.replace(decimal, cDecimal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
const double d = cLocale.toDouble(buf, &ok);
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
val = d;
|
|
|
|
return buf.size();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
val = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.append(c);
|
|
|
|
c = GetChar(formula, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
2017-07-26 18:28:26 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
QString NameRegExp()
|
|
|
|
{
|
|
|
|
static QString regex;
|
|
|
|
|
|
|
|
if (regex.isEmpty())
|
|
|
|
{
|
|
|
|
const QList<QLocale> allLocales =
|
|
|
|
QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
|
|
|
|
|
|
|
|
QString positiveSigns;
|
|
|
|
QString negativeSigns;
|
|
|
|
QString decimalPoints;
|
|
|
|
QString groupSeparators;
|
|
|
|
|
2018-04-02 19:38:56 +02:00
|
|
|
for(auto &locale : allLocales)
|
2017-07-26 18:28:26 +02:00
|
|
|
{
|
2018-04-02 19:38:56 +02:00
|
|
|
if (not positiveSigns.contains(locale.positiveSign()))
|
2017-07-26 18:28:26 +02:00
|
|
|
{
|
2018-04-02 19:38:56 +02:00
|
|
|
positiveSigns.append(locale.positiveSign());
|
2017-07-26 18:28:26 +02:00
|
|
|
}
|
|
|
|
|
2018-04-02 19:38:56 +02:00
|
|
|
if (not negativeSigns.contains(locale.negativeSign()))
|
2017-07-26 18:28:26 +02:00
|
|
|
{
|
2018-04-02 19:38:56 +02:00
|
|
|
negativeSigns.append(locale.negativeSign());
|
2017-07-26 18:28:26 +02:00
|
|
|
}
|
|
|
|
|
2018-04-02 19:38:56 +02:00
|
|
|
if (not decimalPoints.contains(locale.decimalPoint()))
|
2017-07-26 18:28:26 +02:00
|
|
|
{
|
2018-04-02 19:38:56 +02:00
|
|
|
decimalPoints.append(locale.decimalPoint());
|
2017-07-26 18:28:26 +02:00
|
|
|
}
|
|
|
|
|
2018-04-02 19:38:56 +02:00
|
|
|
if (not groupSeparators.contains(locale.groupSeparator()))
|
2017-07-26 18:28:26 +02:00
|
|
|
{
|
2018-04-02 19:38:56 +02:00
|
|
|
groupSeparators.append(locale.groupSeparator());
|
2017-07-26 18:28:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
negativeSigns.replace('-', "\\-");
|
2017-07-26 19:58:37 +02:00
|
|
|
groupSeparators.remove('\'');
|
2017-07-26 18:28:26 +02:00
|
|
|
|
|
|
|
//Same regexp in pattern.xsd shema file. Don't forget to synchronize.
|
|
|
|
// \p{Nd} - \p{Decimal_Digit_Number}
|
|
|
|
// \p{Zs} - \p{Space_Separator}
|
|
|
|
regex = QString("^([^\\p{Nd}\\p{Zs}*/&|!<>^\\()%1%2%3%4=?:;'\"]){1,1}"
|
|
|
|
"([^\\p{Zs}*/&|!<>^\\()%1%2%3%4=?:;\"]){0,}$")
|
2018-03-14 14:39:15 +01:00
|
|
|
.arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators);
|
2017-07-26 18:28:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return regex;
|
|
|
|
}
|
2018-04-22 17:32:38 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
int FindFirstNotOf(const QString &string, const QString &chars, int pos)
|
|
|
|
{
|
|
|
|
int chPos = pos;
|
|
|
|
QString::const_iterator it = string.constBegin() + pos;
|
|
|
|
QString::const_iterator end = string.constEnd();
|
|
|
|
while (it != end)
|
|
|
|
{
|
|
|
|
if (not chars.contains(*it))
|
|
|
|
{
|
|
|
|
return chPos;
|
|
|
|
}
|
|
|
|
++it;
|
|
|
|
++chPos;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|