You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
632 lines
15 KiB
632 lines
15 KiB
15 years ago
|
/* This file is part of the KDE project
|
||
|
Copyright 2004 Tomas Mecir <mecirt@gmail.com>
|
||
|
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Library General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Library General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Library General Public License
|
||
|
along with this library; see the file COPYING.LIB. If not, write to
|
||
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
|
* Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
#include "valueparser.h"
|
||
|
|
||
|
#include "kspread_cell.h"
|
||
|
#include "kspread_format.h"
|
||
|
#include "kspread_locale.h"
|
||
|
#include "kspread_value.h"
|
||
|
|
||
|
using namespace KSpread;
|
||
|
|
||
|
ValueParser::ValueParser( KLocale* locale ) : parserLocale( locale )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
KLocale* ValueParser::locale()
|
||
|
{
|
||
|
return parserLocale;
|
||
|
}
|
||
|
|
||
|
void ValueParser::parse (const QString& str, Cell *cell)
|
||
|
{
|
||
|
FormatType format = cell->formatType();
|
||
|
|
||
|
// If the text is empty, we don't have a value
|
||
|
// If the user stated explicitly that he wanted text
|
||
|
// (using the format or using a quote),
|
||
|
// then we don't parse as a value, but as string.
|
||
|
if ( str.isEmpty() || format == Text_format || str.at(0)=='\'' )
|
||
|
{
|
||
|
cell->setValue (str);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
QString strStripped = str.stripWhiteSpace();
|
||
|
|
||
|
// Try parsing as various datatypes, to find the type of the cell
|
||
|
|
||
|
// First as number
|
||
|
if (tryParseNumber (strStripped, cell))
|
||
|
return;
|
||
|
|
||
|
// Then as bool
|
||
|
if (tryParseBool (strStripped, cell))
|
||
|
return;
|
||
|
|
||
|
|
||
|
// Test for money number
|
||
|
bool ok;
|
||
|
double money = parserLocale->readMoney (strStripped, &ok);
|
||
|
if (ok)
|
||
|
{
|
||
|
cell->format()->setPrecision(2);
|
||
|
Value val (money);
|
||
|
val.setFormat (Value::fmt_Money);
|
||
|
cell->setValue (val);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (tryParseDate (strStripped, cell))
|
||
|
return;
|
||
|
|
||
|
if (tryParseTime (strStripped, cell))
|
||
|
return;
|
||
|
|
||
|
// Nothing particular found, then this is simply a string
|
||
|
cell->setValue (Value (str));
|
||
|
}
|
||
|
|
||
|
Value ValueParser::parse (const QString &str)
|
||
|
{
|
||
|
Value val;
|
||
|
|
||
|
// If the text is empty, we don't have a value
|
||
|
// If the user stated explicitly that he wanted text
|
||
|
// (using the format or using a quote),
|
||
|
// then we don't parse as a value, but as string.
|
||
|
if ( str.isEmpty() || str.at(0)=='\'' )
|
||
|
{
|
||
|
val.setValue (str);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
bool ok;
|
||
|
|
||
|
QString strStripped = str.stripWhiteSpace();
|
||
|
// Try parsing as various datatypes, to find the type of the string
|
||
|
|
||
|
// First as number
|
||
|
val = tryParseNumber (strStripped, &ok);
|
||
|
if (ok)
|
||
|
return val;
|
||
|
|
||
|
// Then as bool
|
||
|
// Note - I swapped the order of these two to try parsing as a number
|
||
|
// first because that will probably be the most common case
|
||
|
val = tryParseBool (strStripped, &ok);
|
||
|
if (ok)
|
||
|
return val;
|
||
|
|
||
|
|
||
|
// Test for money number
|
||
|
double money = parserLocale->readMoney (strStripped, &ok);
|
||
|
if (ok)
|
||
|
{
|
||
|
val.setValue (money);
|
||
|
val.setFormat (Value::fmt_Money);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
val = tryParseDate (strStripped, &ok);
|
||
|
if (ok)
|
||
|
return val;
|
||
|
|
||
|
val = tryParseTime (strStripped, &ok);
|
||
|
if (ok)
|
||
|
return val;
|
||
|
|
||
|
// Nothing particular found, then this is simply a string
|
||
|
val.setValue (str);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
bool ValueParser::tryParseBool (const QString& str, Cell *cell)
|
||
|
{
|
||
|
bool ok;
|
||
|
Value val = tryParseBool (str, &ok);
|
||
|
if (ok)
|
||
|
cell->setValue (val);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool ValueParser::tryParseNumber (const QString& str, Cell *cell)
|
||
|
{
|
||
|
bool ok;
|
||
|
Value val = tryParseNumber (str, &ok);
|
||
|
if (ok)
|
||
|
cell->setValue (val);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool ValueParser::tryParseDate (const QString& str, Cell *cell)
|
||
|
{
|
||
|
bool ok;
|
||
|
Value value = tryParseDate (str, &ok);
|
||
|
if (ok)
|
||
|
cell->setValue (value);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool ValueParser::tryParseTime (const QString& str, Cell *cell)
|
||
|
{
|
||
|
bool ok;
|
||
|
Value value = tryParseTime (str, &ok);
|
||
|
if (ok)
|
||
|
cell->setValue (value);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
|
||
|
Value ValueParser::tryParseBool (const QString& str, bool *ok)
|
||
|
{
|
||
|
Value val;
|
||
|
if (ok) *ok = false;
|
||
|
|
||
|
const QString& lowerStr = str.lower();
|
||
|
|
||
|
if ((lowerStr == "true") ||
|
||
|
(lowerStr == parserLocale->translate("true").lower()))
|
||
|
{
|
||
|
val.setValue (true);
|
||
|
if (ok) *ok = true;
|
||
|
}
|
||
|
else if ((lowerStr == "false") ||
|
||
|
(lowerStr == parserLocale->translate("false").lower()))
|
||
|
{
|
||
|
val.setValue (false);
|
||
|
if (ok) *ok = true;
|
||
|
fmtType = Number_format; //TODO: really?
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
double ValueParser::readNumber(const QString &_str, bool * ok, bool * isInt)
|
||
|
{
|
||
|
QString str = _str.stripWhiteSpace();
|
||
|
bool neg = str.find(parserLocale->negativeSign()) == 0;
|
||
|
if (neg)
|
||
|
str.remove( 0, parserLocale->negativeSign().length() );
|
||
|
|
||
|
/* will hold the scientific notation portion of the number.
|
||
|
Example, with 2.34E+23, exponentialPart == "E+23"
|
||
|
*/
|
||
|
QString exponentialPart;
|
||
|
int EPos;
|
||
|
|
||
|
EPos = str.find('E', 0, false);
|
||
|
|
||
|
if (EPos != -1)
|
||
|
{
|
||
|
exponentialPart = str.mid(EPos);
|
||
|
str = str.left(EPos);
|
||
|
}
|
||
|
|
||
|
int pos = str.find(parserLocale->decimalSymbol());
|
||
|
QString major;
|
||
|
QString minor;
|
||
|
if ( pos == -1 )
|
||
|
{
|
||
|
major = str;
|
||
|
if (isInt) *isInt = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
major = str.left(pos);
|
||
|
minor = str.mid(pos + parserLocale->decimalSymbol().length());
|
||
|
if (isInt) *isInt = false;
|
||
|
}
|
||
|
|
||
|
// Remove thousand separators
|
||
|
int thlen = parserLocale->thousandsSeparator().length();
|
||
|
int lastpos = 0;
|
||
|
while ( ( pos = major.find( parserLocale->thousandsSeparator() ) ) > 0 )
|
||
|
{
|
||
|
// e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
|
||
|
int fromEnd = major.length() - pos;
|
||
|
if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error
|
||
|
|| pos - lastpos > 3 // More than 3 digits between two separators -> error
|
||
|
|| pos == 0 // Can't start with a separator
|
||
|
|| (lastpos>0 && pos-lastpos!=3)) // Must have exactly 3 digits between two separators
|
||
|
{
|
||
|
if (ok) *ok = false;
|
||
|
return 0.0;
|
||
|
}
|
||
|
|
||
|
lastpos = pos;
|
||
|
major.remove( pos, thlen );
|
||
|
}
|
||
|
if (lastpos>0 && major.length()-lastpos!=3) // Must have exactly 3 digits after the last separator
|
||
|
{
|
||
|
if (ok) *ok = false;
|
||
|
return 0.0;
|
||
|
}
|
||
|
|
||
|
QString tot;
|
||
|
if (neg) tot = '-';
|
||
|
|
||
|
tot += major + '.' + minor + exponentialPart;
|
||
|
|
||
|
return tot.toDouble(ok);
|
||
|
}
|
||
|
|
||
|
Value ValueParser::tryParseNumber (const QString& str, bool *ok)
|
||
|
{
|
||
|
Value value;
|
||
|
|
||
|
bool percent = false;
|
||
|
QString str2;
|
||
|
if( str.at(str.length()-1)=='%')
|
||
|
{
|
||
|
str2 = str.left (str.length()-1).stripWhiteSpace();
|
||
|
percent = true;
|
||
|
}
|
||
|
else
|
||
|
str2 = str;
|
||
|
|
||
|
|
||
|
// First try to understand the number using the parserLocale
|
||
|
bool isInt;
|
||
|
double val = readNumber (str2, ok, &isInt);
|
||
|
// If not, try with the '.' as decimal separator
|
||
|
if (!(*ok))
|
||
|
{
|
||
|
val = str2.toDouble(ok);
|
||
|
if (str.contains('.'))
|
||
|
isInt = false;
|
||
|
else
|
||
|
isInt = true;
|
||
|
}
|
||
|
|
||
|
if (*ok)
|
||
|
{
|
||
|
if (percent)
|
||
|
{
|
||
|
//kdDebug(36001) << "ValueParser::tryParseNumber '" << str <<
|
||
|
// "' successfully parsed as percentage: " << val << "%" << endl;
|
||
|
value.setValue (val / 100.0);
|
||
|
value.setFormat (Value::fmt_Percent);
|
||
|
fmtType = Percentage_format;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//kdDebug(36001) << "ValueParser::tryParseNumber '" << str <<
|
||
|
// "' successfully parsed as number: " << val << endl;
|
||
|
if (isInt)
|
||
|
value.setValue (static_cast<long> (val));
|
||
|
else
|
||
|
value.setValue (val);
|
||
|
|
||
|
if ( str2.contains('E') || str2.contains('e') )
|
||
|
fmtType = Scientific_format;
|
||
|
else
|
||
|
{
|
||
|
if (val > 1e+10)
|
||
|
fmtType = Scientific_format;
|
||
|
else
|
||
|
fmtType = Number_format;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
Value ValueParser::tryParseDate (const QString& str, bool *ok)
|
||
|
{
|
||
|
bool valid = false;
|
||
|
QDate tmpDate = parserLocale->readDate (str, &valid);
|
||
|
if (!valid)
|
||
|
{
|
||
|
// Try without the year
|
||
|
// The tricky part is that we need to remove any separator around the year
|
||
|
// For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m
|
||
|
// If the year is in the middle, say %m-%Y/%d, we'll remove the sep.
|
||
|
// before it (%m/%d).
|
||
|
QString fmt = parserLocale->dateFormatShort();
|
||
|
int yearPos = fmt.find ("%Y", 0, false);
|
||
|
if ( yearPos > -1 )
|
||
|
{
|
||
|
if ( yearPos == 0 )
|
||
|
{
|
||
|
fmt.remove( 0, 2 );
|
||
|
while ( fmt[0] != '%' )
|
||
|
fmt.remove( 0, 1 );
|
||
|
} else
|
||
|
{
|
||
|
fmt.remove( yearPos, 2 );
|
||
|
for ( ; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos )
|
||
|
fmt.remove( yearPos, 1 );
|
||
|
}
|
||
|
//kdDebug(36001) << "Cell::tryParseDate short format w/o date: " << fmt << endl;
|
||
|
tmpDate = parserLocale->readDate( str, fmt, &valid );
|
||
|
}
|
||
|
}
|
||
|
if (valid)
|
||
|
{
|
||
|
// Note: if shortdate format only specifies 2 digits year, then 3/4/1955
|
||
|
// will be treated as in year 3055, while 3/4/55 as year 2055
|
||
|
// (because 55 < 69, see KLocale) and thus there's no way to enter for
|
||
|
// year 1995
|
||
|
|
||
|
// The following fixes the problem, 3/4/1955 will always be 1955
|
||
|
|
||
|
QString fmt = parserLocale->dateFormatShort();
|
||
|
if( ( fmt.contains( "%y" ) == 1 ) && ( tmpDate.year() > 2999 ) )
|
||
|
tmpDate = tmpDate.addYears( -1900 );
|
||
|
|
||
|
// this is another HACK !
|
||
|
// with two digit years, 0-69 is treated as year 2000-2069 (see KLocale)
|
||
|
// however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930
|
||
|
// onwards
|
||
|
|
||
|
// the following provides workaround for KLocale so we're compatible
|
||
|
// with Excel
|
||
|
// (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045)
|
||
|
if( ( tmpDate.year() >= 2030 ) && ( tmpDate.year() <= 2069 ) )
|
||
|
{
|
||
|
QString yearFourDigits = QString::number( tmpDate.year() );
|
||
|
QString yearTwoDigits = QString::number( tmpDate.year() % 100 );
|
||
|
|
||
|
// if year is 2045, check to see if "2045" isn't there --> actual
|
||
|
// input is "45"
|
||
|
if( ( str.contains( yearTwoDigits ) >= 1 ) &&
|
||
|
( str.contains( yearFourDigits ) == 0 ) )
|
||
|
tmpDate = tmpDate.addYears( -100 );
|
||
|
}
|
||
|
|
||
|
//test if it's a short date or text date.
|
||
|
if (parserLocale->formatDate (tmpDate, false) == str)
|
||
|
fmtType = TextDate_format;
|
||
|
else
|
||
|
fmtType = ShortDate_format;
|
||
|
}
|
||
|
if (!valid)
|
||
|
{
|
||
|
//try to use the standard Qt date parsing, using ISO 8601 format
|
||
|
tmpDate = QDate::fromString(str,Qt::ISODate);
|
||
|
if (tmpDate.isValid())
|
||
|
{
|
||
|
valid = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ok)
|
||
|
*ok = valid;
|
||
|
|
||
|
return Value (tmpDate);
|
||
|
}
|
||
|
|
||
|
Value ValueParser::tryParseTime (const QString& str, bool *ok)
|
||
|
{
|
||
|
if (ok)
|
||
|
*ok = false;
|
||
|
|
||
|
bool valid = false;
|
||
|
bool duration = false;
|
||
|
Value val;
|
||
|
|
||
|
QDateTime tmpTime = readTime (str, true, &valid, duration);
|
||
|
if (!tmpTime.isValid())
|
||
|
tmpTime = readTime (str, false, &valid, duration);
|
||
|
|
||
|
if (!valid)
|
||
|
{
|
||
|
QTime tm;
|
||
|
if (parserLocale->use12Clock())
|
||
|
{
|
||
|
QString stringPm = parserLocale->translate("pm");
|
||
|
QString stringAm = parserLocale->translate("am");
|
||
|
int pos=0;
|
||
|
if((pos=str.find(stringPm))!=-1)
|
||
|
{
|
||
|
QString tmp=str.mid(0,str.length()-stringPm.length());
|
||
|
tmp=tmp.simplifyWhiteSpace();
|
||
|
tm = parserLocale->readTime(tmp+" "+stringPm, &valid);
|
||
|
if (!valid)
|
||
|
tm = parserLocale->readTime(tmp+":00 "+stringPm, &valid);
|
||
|
}
|
||
|
else if((pos=str.find(stringAm))!=-1)
|
||
|
{
|
||
|
QString tmp = str.mid(0,str.length()-stringAm.length());
|
||
|
tmp = tmp.simplifyWhiteSpace();
|
||
|
tm = parserLocale->readTime (tmp + " " + stringAm, &valid);
|
||
|
if (!valid)
|
||
|
tm = parserLocale->readTime (tmp + ":00 " + stringAm, &valid);
|
||
|
}
|
||
|
if (valid)
|
||
|
tmpTime.setTime(tm);
|
||
|
}
|
||
|
}
|
||
|
if (valid)
|
||
|
{
|
||
|
fmtType = Time_format;
|
||
|
if ( duration )
|
||
|
{
|
||
|
val.setValue (tmpTime);
|
||
|
fmtType = Time_format7;
|
||
|
}
|
||
|
else
|
||
|
val.setValue (tmpTime.time());
|
||
|
}
|
||
|
|
||
|
if (ok)
|
||
|
*ok = valid;
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
QDateTime ValueParser::readTime (const QString & intstr, bool withSeconds,
|
||
|
bool *ok, bool & duration)
|
||
|
{
|
||
|
duration = false;
|
||
|
QString str = intstr.simplifyWhiteSpace().lower();
|
||
|
QString format = parserLocale->timeFormat().simplifyWhiteSpace();
|
||
|
if ( !withSeconds )
|
||
|
{
|
||
|
int n = format.find("%S");
|
||
|
format = format.left( n - 1 );
|
||
|
}
|
||
|
|
||
|
int days = -1;
|
||
|
int hour = -1, minute = -1;
|
||
|
int second = withSeconds ? -1 : 0; // don't require seconds
|
||
|
bool g_12h = false;
|
||
|
bool pm = false;
|
||
|
uint strpos = 0;
|
||
|
uint formatpos = 0;
|
||
|
|
||
|
QDate refDate( 1899, 12, 31 );
|
||
|
|
||
|
uint l = format.length();
|
||
|
uint sl = str.length();
|
||
|
|
||
|
while (l > formatpos || sl > strpos)
|
||
|
{
|
||
|
if ( !(l > formatpos && sl > strpos) )
|
||
|
goto error;
|
||
|
|
||
|
QChar c( format.at( formatpos++ ) );
|
||
|
|
||
|
if (c != '%')
|
||
|
{
|
||
|
if (c.isSpace())
|
||
|
++strpos;
|
||
|
else if (c != str.at(strpos++))
|
||
|
goto error;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// remove space at the begining
|
||
|
if (sl > strpos && str.at( strpos).isSpace() )
|
||
|
++strpos;
|
||
|
|
||
|
c = format.at( formatpos++ );
|
||
|
switch (c)
|
||
|
{
|
||
|
case 'p':
|
||
|
{
|
||
|
QString s;
|
||
|
s = parserLocale->translate("pm").lower();
|
||
|
int len = s.length();
|
||
|
if (str.mid(strpos, len) == s)
|
||
|
{
|
||
|
pm = true;
|
||
|
strpos += len;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
s = parserLocale->translate("am").lower();
|
||
|
len = s.length();
|
||
|
if (str.mid(strpos, len) == s)
|
||
|
{
|
||
|
pm = false;
|
||
|
strpos += len;
|
||
|
}
|
||
|
else
|
||
|
goto error;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'k':
|
||
|
case 'H':
|
||
|
g_12h = false;
|
||
|
hour = readInt(str, strpos);
|
||
|
if (hour < 0)
|
||
|
goto error;
|
||
|
if (hour > 23)
|
||
|
{
|
||
|
days = (int)(hour / 24);
|
||
|
hour %= 24;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 'l':
|
||
|
case 'I':
|
||
|
g_12h = true;
|
||
|
hour = readInt(str, strpos);
|
||
|
if (hour < 1 || hour > 12)
|
||
|
goto error;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 'M':
|
||
|
minute = readInt(str, strpos);
|
||
|
if (minute < 0 || minute > 59)
|
||
|
goto error;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 'S':
|
||
|
second = readInt(str, strpos);
|
||
|
if (second < 0 || second > 59)
|
||
|
goto error;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (g_12h)
|
||
|
{
|
||
|
hour %= 12;
|
||
|
if (pm) hour += 12;
|
||
|
}
|
||
|
|
||
|
if (days > 0)
|
||
|
{
|
||
|
refDate.addDays( days );
|
||
|
duration = true;
|
||
|
}
|
||
|
|
||
|
if (ok)
|
||
|
*ok = true;
|
||
|
return QDateTime( refDate, QTime( hour, minute, second ) );
|
||
|
|
||
|
error:
|
||
|
if (ok)
|
||
|
*ok = false;
|
||
|
// return invalid date if it didn't work
|
||
|
return QDateTime( refDate, QTime( -1, -1, -1 ) );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* helper function to read integers, used in readTime
|
||
|
* @param str
|
||
|
* @param pos the position to start at. It will be updated when we parse it.
|
||
|
* @return the integer read in the string, or -1 if no string
|
||
|
*/
|
||
|
int ValueParser::readInt (const QString &str, uint &pos)
|
||
|
{
|
||
|
if (!str.at(pos).isDigit())
|
||
|
return -1;
|
||
|
int result = 0;
|
||
|
for ( ; str.length() > pos && str.at(pos).isDigit(); pos++ )
|
||
|
{
|
||
|
result *= 10;
|
||
|
result += str.at(pos).digitValue();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|