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