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 | |
30 | using namespace Calligra::Sheets; |
31 | |
32 | ValueParser::ValueParser(const CalculationSettings* settings) |
33 | : m_settings(settings) |
34 | { |
35 | } |
36 | |
37 | const CalculationSettings* ValueParser::settings() const |
38 | { |
39 | return m_settings; |
40 | } |
41 | |
42 | Value 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 | |
94 | Value 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 | |
113 | Value 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 | |
194 | Number 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 | |
223 | Value 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 | |
267 | Value 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 | |
338 | Value 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 | |
385 | QDateTime 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 | |
510 | error: |
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 | */ |
523 | int 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 | |