1/* This file is part of the KDE project
2 Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
3 Copyright 2004 Tomas Mecir <mecirt@gmail.com>
4 Copyright 1998,1999 Torben Weis <weis@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21
22// Local
23#include "ValueParser.h"
24
25#include "CalculationSettings.h"
26#include "Localization.h"
27#include "Style.h"
28#include "Value.h"
29
30using namespace Calligra::Sheets;
31
32ValueParser::ValueParser(const CalculationSettings* settings)
33 : m_settings(settings)
34{
35}
36
37const CalculationSettings* ValueParser::settings() const
38{
39 return m_settings;
40}
41
42Value ValueParser::parse(const QString& str) const
43{
44 Value val;
45
46 // If the text is empty, we don't have a value
47 // If the user stated explicitly that they wanted text
48 // (using the format or using a quote),
49 // then we don't parse as a value, but as string.
50 if (str.isEmpty() || str.at(0) == '\'') {
51 val = Value(str);
52 return val;
53 }
54
55 bool ok;
56
57 QString strStripped = str.trimmed();
58 // Try parsing as various datatypes, to find the type of the string
59
60 // First as number
61 val = tryParseNumber(strStripped, &ok);
62
63 if (ok)
64 return val;
65
66// Then as bool
67// Note - I swapped the order of these two to try parsing as a number
68// first because that will probably be the most common case
69 val = tryParseBool(strStripped, &ok);
70 if (ok)
71 return val;
72
73 // Test for money number
74 Number money = m_settings->locale()->readMoney(strStripped, &ok);
75 if (ok) {
76 val = Value(money);
77 val.setFormat(Value::fmt_Money);
78 return val;
79 }
80
81 val = tryParseDate(strStripped, &ok);
82 if (ok)
83 return val;
84
85 val = tryParseTime(strStripped, &ok);
86 if (ok)
87 return val;
88
89 // Nothing particular found, then this is simply a string
90 val = Value(str);
91 return val;
92}
93
94Value ValueParser::tryParseBool(const QString& str, bool *ok) const
95{
96 Value val;
97 if (ok) *ok = false;
98
99 const QString& lowerStr = str.toLower();
100
101 if ((lowerStr == "true") ||
102 (lowerStr == ki18n("true").toString(m_settings->locale()).toLower())) {
103 val = Value(true);
104 if (ok) *ok = true;
105 } else if ((lowerStr == "false") ||
106 (lowerStr == ki18n("false").toString(m_settings->locale()).toLower())) {
107 val = Value(false);
108 if (ok) *ok = true;
109 }
110 return val;
111}
112
113Value ValueParser::readNumber(const QString& _str, bool *ok) const
114{
115 bool isInt = false;
116 QString str = _str.trimmed();
117 bool neg = str.indexOf(m_settings->locale()->negativeSign()) == 0;
118 if (neg)
119 str.remove(0, m_settings->locale()->negativeSign().length());
120
121 /* will hold the scientific notation portion of the number.
122 Example, with 2.34E+23, exponentialPart == "E+23"
123 */
124 QString exponentialPart;
125 int EPos = str.indexOf('E', 0, Qt::CaseInsensitive);
126
127 if (EPos != -1) {
128 exponentialPart = str.mid(EPos);
129 str = str.left(EPos);
130 }
131
132 int pos;
133 int fracPos;
134 QString major;
135 QString minor;
136 if ((pos = str.indexOf(m_settings->locale()->decimalSymbol())) != -1) {
137 major = str.left(pos);
138 minor = str.mid(pos + m_settings->locale()->decimalSymbol().length());
139 isInt = false;
140 } else if (((pos = str.indexOf(' ')) != -1) &&
141 ((fracPos = str.indexOf('/')) != -1)) {
142 // try to parse fractions of this form:
143 // [0-9]+ [0-9]+/[1-9][0-9]?
144 major = str.left(pos);
145 QString numerator = str.mid(pos + 1, (fracPos - pos - 1));
146 QString denominator = str.mid(fracPos + 1);
147 double minorVal = numerator.toDouble() / denominator.toDouble();
148 if (minorVal > 1) {
149 // assume major is just a plain number
150 double wholePart = floor(minorVal);
151 minorVal -= wholePart;
152 major = QString("%1").arg(major.toInt() + (int)wholePart);
153 }
154 minor = QString::number(minorVal, 'f').mid(2); // chop off the "0." part
155 // kDebug() <<"fraction:" << major <<"." << minor;
156 } else {
157 major = str;
158 isInt = (EPos == -1); // only, if no exponential part was found
159 }
160
161 // Remove thousand separators
162 int thlen = m_settings->locale()->thousandsSeparator().length();
163 int lastpos = 0;
164 while ((pos = major.indexOf(m_settings->locale()->thousandsSeparator())) > 0) {
165 // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
166 int fromEnd = major.length() - pos;
167 if (fromEnd % (3 + thlen) != 0 // Needs to be a multiple, otherwise it's an error
168 || pos - lastpos > 3 // More than 3 digits between two separators -> error
169 || pos == 0 // Can't start with a separator
170 || (lastpos > 0 && pos - lastpos != 3)) { // Must have exactly 3 digits between two separators
171 if (ok) *ok = false;
172 return Value();
173 }
174
175 lastpos = pos;
176 major.remove(pos, thlen);
177 }
178 if (lastpos > 0 && major.length() - lastpos != 3) { // Must have exactly 3 digits after the last separator
179 if (ok) *ok = false;
180 return Value();
181 }
182
183 // log10(2^63) ~= 18
184 if (isInt && major.length() > 19) isInt = false;
185
186 QString tot;
187 if (neg) tot = '-';
188 tot += major;
189 if (!isInt) tot += '.' + minor + exponentialPart;
190
191 return isInt ? Value(tot.toLongLong(ok)) : Value(tot.toDouble(ok));
192}
193
194Number ValueParser::readImaginary(const QString& str, bool* ok) const
195{
196 if (str.isEmpty()) {
197 if (ok) *ok = false;
198 return 0.0;
199 }
200
201 Number imag = 0.0;
202 if (str[0] == 'i' || str[0] == 'j') {
203 if (str.length() == 1) {
204 if (ok) *ok = true;
205 imag = 1.0;
206 } else
207 imag = readNumber(str.mid(1), ok).asFloat();
208 } else if (str[str.length()-1] == 'i' || str[str.length()-1] == 'j') {
209 const QString minus(m_settings->locale()->negativeSign());
210 if (str.length() == 2 && str[0] == '+') {
211 if (ok) *ok = true;
212 imag = 1.0;
213 } else if (str.length() == minus.length() + 1 && str.left(minus.length()) == minus) {
214 if (ok) *ok = true;
215 imag = -1.0;
216 } else
217 imag = readNumber(str.left(str.length() - 1), ok).asFloat();
218 } else
219 *ok = false;
220 return imag;
221}
222
223Value ValueParser::tryParseNumber(const QString& str, bool *ok) const
224{
225 Value value;
226 if (str.endsWith('%')) { // percentage
227 const Number val = readNumber(str.left(str.length() - 1).trimmed(), ok).asFloat();
228 if (*ok) {
229 //kDebug(36001) <<"ValueParser::tryParseNumber '" << str <<
230 // "' successfully parsed as percentage: " << val << '%' << endl;
231 value = Value(val / 100.0);
232 value.setFormat(Value::fmt_Percent);
233 }
234 } else if (str.count('i') == 1 || str.count('j') == 1) { // complex number
235 Number real = 0.0;
236 Number imag = 0.0;
237 const QString minus(m_settings->locale()->negativeSign());
238 // both parts, real and imaginary, present?
239 int sepPos;
240 if ((sepPos = str.indexOf('+', 1)) != -1) {
241 // imaginary part
242 imag = readImaginary(str.mid(sepPos + 1).trimmed(), ok);
243 // real part
244 if (*ok)
245 real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
246 } else if ((sepPos = str.indexOf(minus, minus.length())) != -1) {
247 // imaginary part
248 imag = -readImaginary(str.mid(sepPos + 1).trimmed(), ok);
249 // real part
250 if (*ok)
251 real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
252 } else {
253 // imaginary part
254 if (str.trimmed().length() > 1) // but don't parse a stand-alone 'i'
255 imag = readImaginary(str.trimmed(), ok);
256 // real part
257 if (*ok)
258 real = 0.0;
259 }
260 if (*ok)
261 value = Value(complex<Number>(real, imag));
262 } else // real number
263 value = readNumber(str, ok);
264 return value;
265}
266
267Value ValueParser::tryParseDate(const QString& str, bool *ok) const
268{
269 bool valid = false;
270 QDate tmpDate = m_settings->locale()->readDate(str, &valid);
271 if (!valid) {
272 // Try without the year
273 // The tricky part is that we need to remove any separator around the year
274 // For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m
275 // If the year is in the middle, say %m-%Y/%d, we'll remove the sep.
276 // before it (%m/%d).
277 QString fmt = m_settings->locale()->dateFormatShort();
278 int yearPos = fmt.indexOf("%Y", 0, Qt::CaseInsensitive);
279 if (yearPos > -1) {
280 if (yearPos == 0) {
281 fmt.remove(0, 2);
282 while (fmt[0] != '%')
283 fmt.remove(0, 1);
284 } else {
285 fmt.remove(yearPos, 2);
286 for (; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos)
287 fmt.remove(yearPos, 1);
288 }
289 //kDebug(36001) <<"Cell::tryParseDate short format w/o date:" << fmt;
290 tmpDate = m_settings->locale()->readDate(str, fmt, &valid);
291 }
292 }
293 if (valid) {
294 // Note: if shortdate format only specifies 2 digits year, then 3/4/1955
295 // will be treated as in year 3055, while 3/4/55 as year 2055
296 // (because 55 < 69, see KLocale) and thus there's no way to enter for
297 // year 1995
298
299 // The following fixes the problem, 3/4/1955 will always be 1955
300
301 QString fmt = m_settings->locale()->dateFormatShort();
302 if ((fmt.contains("%y") == 1) && (tmpDate.year() > 2999))
303 tmpDate = tmpDate.addYears(-1900);
304
305 // this is another HACK !
306 // with two digit years, 0-69 is treated as year 2000-2069 (see KLocale)
307 // however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930
308 // onwards
309
310 // the following provides workaround for KLocale so we're compatible
311 // with Excel
312 // (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045)
313 if ((tmpDate.year() >= 2030) && (tmpDate.year() <= 2069)) {
314 QString yearFourDigits = QString::number(tmpDate.year());
315 QString yearTwoDigits = QString::number(tmpDate.year() % 100);
316
317 // if year is 2045, check to see if "2045" isn't there --> actual
318 // input is "45"
319 if ((str.count(yearTwoDigits) >= 1) &&
320 (str.count(yearFourDigits) == 0))
321 tmpDate = tmpDate.addYears(-100);
322 }
323 }
324 if (!valid) {
325 //try to use the standard Qt date parsing, using ISO 8601 format
326 tmpDate = QDate::fromString(str, Qt::ISODate);
327 if (tmpDate.isValid()) {
328 valid = true;
329 }
330 }
331
332 if (ok)
333 *ok = valid;
334
335 return Value(tmpDate, m_settings);
336}
337
338Value ValueParser::tryParseTime(const QString& str, bool *ok) const
339{
340 bool valid = false;
341
342 QDateTime tmpTime = readTime(str, true, &valid);
343 if (!valid)
344 tmpTime = readTime(str, false, &valid);
345
346 if (!valid) {
347 const QString stringPm = ki18n("pm").toString(m_settings->locale());
348 const QString stringAm = ki18n("am").toString(m_settings->locale());
349 int pos = 0;
350 if ((pos = str.indexOf(stringPm, 0, Qt::CaseInsensitive)) != -1) {
351 // cut off 'PM'
352 QString tmp = str.mid(0, str.length() - stringPm.length());
353 tmp = tmp.simplified();
354 // try again
355 tmpTime = readTime(tmp, true, &valid);
356 if (!valid)
357 tmpTime = readTime(tmp, false, &valid);
358 if (valid && tmpTime.time().hour() > 11)
359 valid = false;
360 else if (valid)
361 tmpTime = tmpTime.addSecs(43200); // add 12 hours
362 } else if ((pos = str.indexOf(stringAm, 0, Qt::CaseInsensitive)) != -1) {
363 // cut off 'AM'
364 QString tmp = str.mid(0, str.length() - stringAm.length());
365 tmp = tmp.simplified();
366 // try again
367 tmpTime = readTime(tmp, true, &valid);
368 if (!valid)
369 tmpTime = readTime(tmp, false, &valid);
370 if (valid && tmpTime.time().hour() > 11)
371 valid = false;
372 }
373 }
374
375 if (ok)
376 *ok = valid;
377 Value value;
378 if (valid) {
379 value = Value(tmpTime, m_settings);
380 value.setFormat(Value::fmt_Time);
381 }
382 return value;
383}
384
385QDateTime ValueParser::readTime(const QString& intstr, bool withSeconds, bool* ok) const
386{
387 QString str = intstr.simplified().toLower();
388 QString format = m_settings->locale()->timeFormat().simplified();
389 if (!withSeconds) {
390 int n = format.indexOf("%S");
391 format = format.left(n - 1);
392 }
393
394 QDateTime result;
395 int hour = 0;
396 int minute = 0;
397 int second = 0;
398 int msecs = 0;
399 bool g_12h = false;
400 bool pm = false;
401 bool negative = false;
402 uint strpos = 0;
403 uint formatpos = 0;
404
405 const uint l = format.length();
406 const uint sl = str.length();
407
408 while (l > formatpos || sl > strpos) {
409 if (!(l > formatpos && sl > strpos))
410 goto error;
411
412 QChar c(format.at(formatpos++));
413
414 if (c != '%') {
415 if (c.isSpace())
416 ++strpos;
417 else if (c != str.at(strpos++))
418 goto error;
419 continue;
420 }
421
422 // remove space at the beginning
423 if (sl > strpos && str.at(strpos).isSpace())
424 ++strpos;
425
426 c = format.at(formatpos++);
427 switch (c.toLatin1()) {
428 case 'p': {
429 QString s(ki18n("pm").toString(m_settings->locale()).toLower());
430 int len = s.length();
431 if (str.mid(strpos, len) == s) {
432 pm = true;
433 strpos += len;
434 } else {
435 s = ki18n("am").toString(m_settings->locale()).toLower();
436 len = s.length();
437 if (str.mid(strpos, len) == s) {
438 pm = false;
439 strpos += len;
440 } else
441 goto error;
442 }
443 }
444 break;
445
446 case 'k':
447 case 'H':
448 g_12h = false;
449 if (str.at(strpos) == '-') {
450 negative = true;
451 if (sl <= ++strpos)
452 goto error;
453 }
454 hour = readInt(str, strpos);
455 if (hour < 0)
456 goto error;
457
458 break;
459
460 case 'l':
461 case 'I':
462 g_12h = true;
463 if (str.at(strpos) == '-') {
464 negative = true;
465 if (sl <= ++strpos)
466 goto error;
467 }
468 hour = readInt(str, strpos);
469 if (hour < 1 || hour > 12)
470 goto error;
471
472 break;
473
474 case 'M':
475 minute = readInt(str, strpos);
476 if (minute < 0 || minute > 59)
477 goto error;
478
479 break;
480
481 case 'S':
482 if (!withSeconds)
483 break;
484 second = readInt(str, strpos);
485 if (second < 0 || second > 59)
486 goto error;
487 if (strpos < sl && str.indexOf(m_settings->locale()->decimalSymbol()) == (int)strpos) {
488 strpos += m_settings->locale()->decimalSymbol().length();
489 msecs = readInt(str, strpos);
490 if (msecs < 0 || msecs > 999)
491 goto error;
492 }
493
494 break;
495 }
496 }
497
498 if (g_12h) {
499 hour %= 12;
500 if (pm) hour += 12;
501 }
502
503 if (ok)
504 *ok = true;
505 result = QDateTime(m_settings->referenceDate(), QTime(0, 0), Qt::UTC);
506 msecs += (((hour * 60 + minute) * 60 + second) * 1000);
507 result = result.addMSecs(negative ? -msecs : msecs);
508 return result;
509
510error:
511 if (ok)
512 *ok = false;
513 // return invalid date if it didn't work
514 return QDateTime(m_settings->referenceDate(), QTime(-1, -1, -1), Qt::UTC);
515}
516
517/**
518 * helper function to read integers, used in readTime
519 * @param str
520 * @param pos the position to start at. It will be updated when we parse it.
521 * @return the integer read in the string, or -1 if no string
522 */
523int ValueParser::readInt(const QString& str, uint& pos) const
524{
525 if (!str.at(pos).isDigit())
526 return -1;
527 int result = 0;
528 for (; (uint) str.length() > pos && str.at(pos).isDigit(); pos++) {
529 result *= 10;
530 result += str.at(pos).digitValue();
531 }
532
533 return result;
534}
535
536