File indexing completed on 2024-04-28 16:21:39

0001 /* This file is part of the KDE project
0002    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003    Copyright 2004 Tomas Mecir <mecirt@gmail.com>
0004    Copyright 1998,1999 Torben Weis <weis@kde.org>
0005 
0006    This library is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU Library General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 
0011    This library is distributed in the hope that it will be useful,
0012    but WITHOUT ANY WARRANTY; without even the implied warranty of
0013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014    Library General Public License for more details.
0015 
0016    You should have received a copy of the GNU Library General Public License
0017    along with this library; see the file COPYING.LIB.  If not, write to
0018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019    Boston, MA 02110-1301, USA.
0020 */
0021 
0022 // Local
0023 #include "ValueParser.h"
0024 
0025 #include "CalculationSettings.h"
0026 #include "Localization.h"
0027 #include "Style.h"
0028 #include "Value.h"
0029 
0030 using namespace Calligra::Sheets;
0031 
0032 ValueParser::ValueParser(const CalculationSettings* settings)
0033         : m_settings(settings)
0034 {
0035 }
0036 
0037 const CalculationSettings* ValueParser::settings() const
0038 {
0039     return m_settings;
0040 }
0041 
0042 Value ValueParser::parse(const QString& str) const
0043 {
0044     Value val;
0045 
0046     // If the text is empty, we don't have a value
0047     // If the user stated explicitly that they wanted text
0048     // (using the format or using a quote),
0049     // then we don't parse as a value, but as string.
0050     if (str.isEmpty() || str.at(0) == '\'') {
0051         val = Value(str);
0052         return val;
0053     }
0054 
0055     bool ok;
0056 
0057     QString strStripped = str.trimmed();
0058     // Try parsing as various datatypes, to find the type of the string
0059 
0060     // First as number
0061     val = tryParseNumber(strStripped, &ok);
0062 
0063     if (ok)
0064         return val;
0065 
0066 // Then as bool
0067 // Note - I swapped the order of these two to try parsing as a number
0068 // first because that will probably be the most common case
0069     val = tryParseBool(strStripped, &ok);
0070     if (ok)
0071         return val;
0072 
0073     // Test for money number
0074     Number money = m_settings->locale()->readMoney(strStripped, &ok);
0075     if (ok) {
0076         val = Value(money);
0077         val.setFormat(Value::fmt_Money);
0078         return val;
0079     }
0080 
0081     val = tryParseDate(strStripped, &ok);
0082     if (ok)
0083         return val;
0084 
0085     val = tryParseTime(strStripped, &ok);
0086     if (ok)
0087         return val;
0088 
0089     // Nothing particular found, then this is simply a string
0090     val = Value(str);
0091     return val;
0092 }
0093 
0094 Value ValueParser::tryParseBool(const QString& str, bool *ok) const
0095 {
0096     Value val;
0097     if (ok) *ok = false;
0098 
0099     const QString& lowerStr = str.toLower();
0100     const QStringList localeCodes(m_settings->locale()->country());
0101 
0102     if ((lowerStr == "true") ||
0103             (lowerStr == ki18n("true").toString(localeCodes).toLower())) {
0104         val = Value(true);
0105         if (ok) *ok = true;
0106     } else if ((lowerStr == "false") ||
0107                (lowerStr == ki18n("false").toString(localeCodes).toLower())) {
0108         val = Value(false);
0109         if (ok) *ok = true;
0110     }
0111     return val;
0112 }
0113 
0114 Value ValueParser::readNumber(const QString& _str, bool *ok) const
0115 {
0116     bool isInt = false;
0117     QString str = _str.trimmed();
0118     bool neg = str.indexOf(m_settings->locale()->negativeSign()) == 0;
0119     if (neg)
0120         str.remove(0, m_settings->locale()->negativeSign().length());
0121 
0122     /* will hold the scientific notation portion of the number.
0123     Example, with 2.34E+23, exponentialPart == "E+23"
0124     */
0125     QString exponentialPart;
0126     int EPos  = str.indexOf('E', 0, Qt::CaseInsensitive);
0127 
0128     if (EPos != -1) {
0129         exponentialPart = str.mid(EPos);
0130         str = str.left(EPos);
0131     }
0132 
0133     int pos;
0134     int fracPos;
0135     QString major;
0136     QString minor;
0137     if ((pos = str.indexOf(m_settings->locale()->decimalSymbol())) != -1) {
0138         major = str.left(pos);
0139         minor = str.mid(pos + m_settings->locale()->decimalSymbol().length());
0140         isInt = false;
0141     } else if (((pos = str.indexOf(' ')) != -1) &&
0142                ((fracPos = str.indexOf('/')) != -1)) {
0143         // try to parse fractions of this form:
0144         // [0-9]+ [0-9]+/[1-9][0-9]?
0145         major = str.left(pos);
0146         QString numerator = str.mid(pos + 1, (fracPos - pos - 1));
0147         QString denominator = str.mid(fracPos + 1);
0148         double minorVal = numerator.toDouble() / denominator.toDouble();
0149         if (minorVal > 1) {
0150             // assume major is just a plain number
0151             double wholePart = floor(minorVal);
0152             minorVal -= wholePart;
0153             major = QString("%1").arg(major.toInt() + (int)wholePart);
0154         }
0155         minor = QString::number(minorVal, 'f').remove(0, 2);     // chop off the "0." part
0156         // debugSheets <<"fraction:" << major <<"." << minor;
0157     } else {
0158         major = str;
0159         isInt = (EPos == -1); // only, if no exponential part was found
0160     }
0161 
0162     // Remove thousand separators
0163     int thlen = m_settings->locale()->thousandsSeparator().length();
0164     int lastpos = 0;
0165     while ((pos = major.indexOf(m_settings->locale()->thousandsSeparator())) > 0) {
0166         // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
0167         int fromEnd = major.length() - pos;
0168         if (fromEnd % (3 + thlen) != 0 // Needs to be a multiple, otherwise it's an error
0169                 || pos - lastpos > 3 // More than 3 digits between two separators -> error
0170                 || pos == 0          // Can't start with a separator
0171                 || (lastpos > 0 && pos - lastpos != 3)) { // Must have exactly 3 digits between two separators
0172             if (ok) *ok = false;
0173             return Value();
0174         }
0175 
0176         lastpos = pos;
0177         major.remove(pos, thlen);
0178     }
0179     if (lastpos > 0 && major.length() - lastpos != 3) { // Must have exactly 3 digits after the last separator
0180         if (ok) *ok = false;
0181         return Value();
0182     }
0183 
0184     // log10(2^63) ~= 18
0185     if (isInt && major.length() > 19) isInt = false;
0186 
0187     QString tot;
0188     if (neg) tot = '-';
0189     tot += major;
0190     if (!isInt) tot += '.' + minor + exponentialPart;
0191 
0192     return isInt ? Value(tot.toLongLong(ok)) : Value(tot.toDouble(ok));
0193 }
0194 
0195 Number ValueParser::readImaginary(const QString& str, bool* ok) const
0196 {
0197     if (str.isEmpty()) {
0198         if (ok) *ok = false;
0199         return 0.0;
0200     }
0201 
0202     Number imag = 0.0;
0203     if (str[0] == 'i' || str[0] == 'j') {
0204         if (str.length() == 1) {
0205             if (ok) *ok = true;
0206             imag = 1.0;
0207         } else
0208             imag = readNumber(str.mid(1), ok).asFloat();
0209     } else if (str[str.length()-1] == 'i' || str[str.length()-1] == 'j') {
0210         const QString minus(m_settings->locale()->negativeSign());
0211         if (str.length() == 2 && str[0] == '+') {
0212             if (ok) *ok = true;
0213             imag = 1.0;
0214         } else if (str.length() == minus.length() + 1 && str.left(minus.length()) == minus) {
0215             if (ok) *ok = true;
0216             imag = -1.0;
0217         } else
0218             imag = readNumber(str.left(str.length() - 1), ok).asFloat();
0219     } else
0220         *ok = false;
0221     return imag;
0222 }
0223 
0224 Value ValueParser::tryParseNumber(const QString& str, bool *ok) const
0225 {
0226     Value value;
0227     if (str.endsWith('%')) {   // percentage
0228         const Number val = readNumber(str.left(str.length() - 1).trimmed(), ok).asFloat();
0229         if (*ok) {
0230             //debugSheets <<"ValueParser::tryParseNumber '" << str <<
0231             //    "' successfully parsed as percentage: " << val << '%' << endl;
0232             value = Value(val / 100.0);
0233             value.setFormat(Value::fmt_Percent);
0234         }
0235     } else if (str.count('i') == 1 || str.count('j') == 1) {    // complex number
0236         Number real = 0.0;
0237         Number imag = 0.0;
0238         const QString minus(m_settings->locale()->negativeSign());
0239         // both parts, real and imaginary, present?
0240         int sepPos;
0241         if ((sepPos = str.indexOf('+', 1)) != -1) {
0242             // imaginary part
0243             imag = readImaginary(str.mid(sepPos + 1).trimmed(), ok);
0244             // real part
0245             if (*ok)
0246                 real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
0247         } else if ((sepPos = str.indexOf(minus, minus.length())) != -1) {
0248             // imaginary part
0249             imag = -readImaginary(str.mid(sepPos + 1).trimmed(), ok);
0250             // real part
0251             if (*ok)
0252                 real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
0253         } else {
0254             // imaginary part
0255             if (str.trimmed().length() > 1)   // but don't parse a stand-alone 'i'
0256               imag = readImaginary(str.trimmed(), ok);
0257             // real part
0258             if (*ok)
0259                 real = 0.0;
0260         }
0261         if (*ok)
0262             value = Value(complex<Number>(real, imag));
0263     } else // real number
0264         value = readNumber(str, ok);
0265     return value;
0266 }
0267 
0268 Value ValueParser::tryParseDate(const QString& str, bool *ok) const
0269 {
0270     bool valid = false;
0271     QDate tmpDate = m_settings->locale()->readDate(str, &valid);
0272     if (!valid) {
0273         // Try without the year
0274         // The tricky part is that we need to remove any separator around the year
0275         // For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m
0276         // If the year is in the middle, say %m-%Y/%d, we'll remove the sep.
0277         // before it (%m/%d).
0278         QString fmt = m_settings->locale()->dateFormatShort();
0279         int yearPos = fmt.indexOf("%Y", 0, Qt::CaseInsensitive);
0280         if (yearPos > -1) {
0281             if (yearPos == 0) {
0282                 fmt.remove(0, 2);
0283                 while (fmt[0] != '%')
0284                     fmt.remove(0, 1);
0285             } else {
0286                 fmt.remove(yearPos, 2);
0287                 for (; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos)
0288                     fmt.remove(yearPos, 1);
0289             }
0290             //debugSheets <<"Cell::tryParseDate short format w/o date:" << fmt;
0291             tmpDate = m_settings->locale()->readDate(str, fmt, &valid);
0292         }
0293     }
0294     if (valid) {
0295         // Note: if shortdate format only specifies 2 digits year, then 3/4/1955
0296         // will be treated as in year 3055, while 3/4/55 as year 2055
0297         // (because 55 < 69, see KLocale) and thus there's no way to enter for
0298         // year 1995
0299 
0300         // The following fixes the problem, 3/4/1955 will always be 1955
0301 
0302         QString fmt = m_settings->locale()->dateFormatShort();
0303         if ((fmt.contains("%y") == 1) && (tmpDate.year() > 2999))
0304             tmpDate = tmpDate.addYears(-1900);
0305 
0306         // this is another HACK !
0307         // with two digit years, 0-69 is treated as year 2000-2069 (see KLocale)
0308         // however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930
0309         // onwards
0310 
0311         // the following provides workaround for KLocale so we're compatible
0312         // with Excel
0313         // (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045)
0314         if ((tmpDate.year() >= 2030) && (tmpDate.year() <= 2069)) {
0315             QString yearFourDigits = QString::number(tmpDate.year());
0316             QString yearTwoDigits = QString::number(tmpDate.year() % 100);
0317 
0318             // if year is 2045, check to see if "2045" isn't there --> actual
0319             // input is "45"
0320             if ((str.count(yearTwoDigits) >= 1) &&
0321                     (str.count(yearFourDigits) == 0))
0322                 tmpDate = tmpDate.addYears(-100);
0323         }
0324     }
0325     if (!valid) {
0326         //try to use the standard Qt date parsing, using ISO 8601 format
0327         tmpDate = QDate::fromString(str, Qt::ISODate);
0328         if (tmpDate.isValid()) {
0329             valid = true;
0330         }
0331     }
0332 
0333     if (ok)
0334         *ok = valid;
0335 
0336     return Value(tmpDate, m_settings);
0337 }
0338 
0339 Value ValueParser::tryParseTime(const QString& str, bool *ok) const
0340 {
0341     bool valid = false;
0342     QDateTime tmpTime = readTime(str, true, &valid);
0343     if (!valid)
0344         tmpTime = readTime(str, false, &valid);
0345 
0346     if (!valid) {
0347         const QStringList localeCodes(m_settings->locale()->country());
0348         const QString stringPm = ki18n("pm").toString(localeCodes);
0349         const QString stringAm = ki18n("am").toString(localeCodes);
0350         int pos = 0;
0351         if ((pos = str.indexOf(stringPm, 0, Qt::CaseInsensitive)) != -1) {
0352             // cut off 'PM'
0353             QString tmp = str.mid(0, str.length() - stringPm.length());
0354             tmp = tmp.simplified();
0355             // try again
0356             tmpTime = readTime(tmp, true, &valid);
0357             if (!valid)
0358                 tmpTime = readTime(tmp, false, &valid);
0359             if (valid && tmpTime.time().hour() > 11)
0360                 valid = false;
0361             else if (valid)
0362                 tmpTime = tmpTime.addSecs(43200); // add 12 hours
0363         } else if ((pos = str.indexOf(stringAm, 0, Qt::CaseInsensitive)) != -1) {
0364             // cut off 'AM'
0365             QString tmp = str.mid(0, str.length() - stringAm.length());
0366             tmp = tmp.simplified();
0367             // try again
0368             tmpTime = readTime(tmp, true, &valid);
0369             if (!valid)
0370                 tmpTime = readTime(tmp, false, &valid);
0371             if (valid && tmpTime.time().hour() > 11)
0372                 valid = false;
0373         }
0374     }
0375 
0376     if (ok)
0377         *ok = valid;
0378     Value value;
0379     if (valid) {
0380         value = Value(tmpTime, m_settings);
0381         value.setFormat(Value::fmt_Time);
0382     }
0383     return value;
0384 }
0385 
0386 QDateTime ValueParser::readTime(const QString& intstr, bool withSeconds, bool* ok) const
0387 {
0388     QString str = intstr.simplified().toLower();
0389     QString format = m_settings->locale()->timeFormat().simplified();
0390     if (!withSeconds) {
0391         int n = format.indexOf("%S");
0392         format = format.left(n - 1);
0393     }
0394 
0395     QDateTime result;
0396     int hour = 0;
0397     int minute = 0;
0398     int second = 0;
0399     int msecs = 0;
0400     bool g_12h = false;
0401     bool pm = false;
0402     bool negative = false;
0403     uint strpos = 0;
0404     uint formatpos = 0;
0405 
0406     const uint l  = format.length();
0407     const uint sl = str.length();
0408 
0409     while (l > formatpos || sl > strpos) {
0410         if (!(l > formatpos && sl > strpos))
0411             goto error;
0412 
0413         QChar c(format.at(formatpos++));
0414 
0415         if (c != '%') {
0416             if (c.isSpace())
0417                 ++strpos;
0418             else if (c != str.at(strpos++))
0419                 goto error;
0420             continue;
0421         }
0422 
0423         // remove space at the beginning
0424         if (sl > strpos && str.at(strpos).isSpace())
0425             ++strpos;
0426 
0427         c = format.at(formatpos++);
0428         switch (c.toLatin1()) {
0429         case 'p': {
0430             const QStringList localeCodes(m_settings->locale()->country());
0431             QString s(ki18n("pm").toString(localeCodes).toLower());
0432             int len = s.length();
0433             if (str.mid(strpos, len) == s) {
0434                 pm = true;
0435                 strpos += len;
0436             } else {
0437                 s = ki18n("am").toString(localeCodes).toLower();
0438                 len = s.length();
0439                 if (str.mid(strpos, len) == s) {
0440                     pm = false;
0441                     strpos += len;
0442                 } else
0443                     goto error;
0444             }
0445         }
0446         break;
0447 
0448         case 'k':
0449         case 'H':
0450             g_12h = false;
0451             if (str.at(strpos) == '-') {
0452                 negative = true;
0453                 if (sl <= ++strpos)
0454                     goto error;
0455             }
0456             hour = readInt(str, strpos);
0457             if (hour < 0)
0458                 goto error;
0459 
0460             break;
0461 
0462         case 'l':
0463         case 'I':
0464             g_12h = true;
0465             if (str.at(strpos) == '-') {
0466                 negative = true;
0467                 if (sl <= ++strpos)
0468                     goto error;
0469             }
0470             hour = readInt(str, strpos);
0471             if (hour < 1 || hour > 12)
0472                 goto error;
0473 
0474             break;
0475 
0476         case 'M':
0477             minute = readInt(str, strpos);
0478             if (minute < 0 || minute > 59)
0479                 goto error;
0480 
0481             break;
0482 
0483         case 'S':
0484             if (!withSeconds)
0485                 break;
0486             second = readInt(str, strpos);
0487             if (second < 0 || second > 59)
0488                 goto error;
0489             if (strpos < sl && str.indexOf(m_settings->locale()->decimalSymbol()) == (int)strpos) {
0490                 strpos += m_settings->locale()->decimalSymbol().length();
0491                 msecs = readInt(str, strpos);
0492                 if (msecs < 0 || msecs > 999)
0493                     goto error;
0494             }
0495 
0496             break;
0497         }
0498     }
0499 
0500     if (g_12h) {
0501         hour %= 12;
0502         if (pm) hour += 12;
0503     }
0504 
0505     if (ok)
0506         *ok = true;
0507     result = QDateTime(m_settings->referenceDate(), QTime(0, 0), Qt::UTC);
0508     msecs += (((hour * 60 + minute) * 60 + second) * 1000);
0509     result = result.addMSecs(negative ? -msecs : msecs);
0510     return result;
0511 
0512 error:
0513     if (ok)
0514         *ok = false;
0515     // return invalid date if it didn't work
0516     return QDateTime(m_settings->referenceDate(), QTime(-1, -1, -1), Qt::UTC);
0517 }
0518 
0519 /**
0520  * helper function to read integers, used in readTime
0521  * @param str
0522  * @param pos the position to start at. It will be updated when we parse it.
0523  * @return the integer read in the string, or -1 if no string
0524  */
0525 int ValueParser::readInt(const QString& str, uint& pos) const
0526 {
0527     if (!str.at(pos).isDigit())
0528         return -1;
0529     int result = 0;
0530     for (; (uint) str.length() > pos && str.at(pos).isDigit(); pos++) {
0531         result *= 10;
0532         result += str.at(pos).digitValue();
0533     }
0534 
0535     return result;
0536 }
0537