kspread

valueparser.cc

00001 /* This file is part of the KDE project
00002    Copyright 2004 Tomas Mecir <mecirt@gmail.com>
00003    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include "valueparser.h"
00022 
00023 #include "kspread_cell.h"
00024 #include "kspread_format.h"
00025 #include "kspread_locale.h"
00026 #include "kspread_value.h"
00027 
00028 using namespace KSpread;
00029 
00030 ValueParser::ValueParser( KLocale* locale ) : parserLocale( locale )
00031 {
00032 }
00033 
00034 KLocale* ValueParser::locale()
00035 {
00036   return parserLocale;
00037 }
00038 
00039 void ValueParser::parse (const QString& str, Cell *cell)
00040 {
00041   FormatType format = cell->formatType();
00042   
00043   // If the text is empty, we don't have a value
00044   // If the user stated explicitly that he wanted text
00045   // (using the format or using a quote),
00046   // then we don't parse as a value, but as string.
00047   if ( str.isEmpty() || format == Text_format || str.at(0)=='\'' )
00048   {
00049     cell->setValue (str);
00050     return;
00051   }
00052 
00053   QString strStripped = str.stripWhiteSpace();
00054   
00055   // Try parsing as various datatypes, to find the type of the cell
00056   
00057   // First as number
00058   if (tryParseNumber (strStripped, cell))
00059     return;
00060   
00061   // Then as bool
00062   if (tryParseBool (strStripped, cell))
00063     return;
00064 
00065 
00066   // Test for money number
00067   bool ok;
00068   double money = parserLocale->readMoney (strStripped, &ok);
00069   if (ok)
00070   {
00071     cell->format()->setPrecision(2);
00072     Value val (money);
00073     val.setFormat (Value::fmt_Money);
00074     cell->setValue (val);
00075     return;
00076   }
00077 
00078   if (tryParseDate (strStripped, cell))
00079     return;
00080 
00081   if (tryParseTime (strStripped, cell))
00082     return;
00083 
00084   // Nothing particular found, then this is simply a string
00085   cell->setValue (Value (str));
00086 }
00087 
00088 Value ValueParser::parse (const QString &str)
00089 {
00090   Value val;
00091   
00092   // If the text is empty, we don't have a value
00093   // If the user stated explicitly that he wanted text
00094   // (using the format or using a quote),
00095   // then we don't parse as a value, but as string.
00096   if ( str.isEmpty() || str.at(0)=='\'' )
00097   {
00098     val.setValue (str);
00099     return val;
00100   }
00101 
00102   bool ok;
00103   
00104   QString strStripped = str.stripWhiteSpace();
00105   // Try parsing as various datatypes, to find the type of the string
00106   
00107   // First as number
00108   val = tryParseNumber (strStripped, &ok);
00109   if (ok)
00110     return val;
00111 
00112  // Then as bool
00113  // Note - I swapped the order of these two to try parsing as a number
00114  // first because that will probably be the most common case
00115   val = tryParseBool (strStripped, &ok);
00116   if (ok)
00117     return val;
00118 
00119 
00120   // Test for money number
00121   double money = parserLocale->readMoney (strStripped, &ok);
00122   if (ok)
00123   {
00124     val.setValue (money);
00125     val.setFormat (Value::fmt_Money);
00126     return val;
00127   }
00128 
00129   val = tryParseDate (strStripped, &ok);
00130   if (ok)
00131     return val;
00132 
00133   val = tryParseTime (strStripped, &ok);
00134   if (ok)
00135     return val;
00136 
00137   // Nothing particular found, then this is simply a string
00138   val.setValue (str);
00139   return val;
00140 }
00141 
00142 bool ValueParser::tryParseBool (const QString& str, Cell *cell)
00143 {
00144   bool ok;
00145   Value val = tryParseBool (str, &ok);
00146   if (ok)
00147     cell->setValue (val);
00148   return ok;
00149 }
00150 
00151 bool ValueParser::tryParseNumber (const QString& str, Cell *cell)
00152 {
00153   bool ok;
00154   Value val = tryParseNumber (str, &ok);
00155   if (ok)
00156     cell->setValue (val);
00157   return ok;
00158 }
00159 
00160 bool ValueParser::tryParseDate (const QString& str, Cell *cell)
00161 {
00162   bool ok;
00163   Value value = tryParseDate (str, &ok);
00164   if (ok)
00165     cell->setValue (value);
00166   return ok;
00167 }
00168 
00169 bool ValueParser::tryParseTime (const QString& str, Cell *cell)
00170 {
00171   bool ok;
00172   Value value = tryParseTime (str, &ok);
00173   if (ok)
00174     cell->setValue (value);
00175   return ok;
00176 }
00177 
00178 
00179 Value ValueParser::tryParseBool (const QString& str, bool *ok)
00180 {
00181   Value val;
00182   if (ok) *ok = false;
00183 
00184   const QString& lowerStr = str.lower();
00185   
00186   if ((lowerStr == "true") ||
00187       (lowerStr == parserLocale->translate("true").lower()))
00188   {
00189     val.setValue (true);
00190     if (ok) *ok = true;
00191   }
00192   else if ((lowerStr == "false") ||
00193       (lowerStr == parserLocale->translate("false").lower()))
00194   {
00195     val.setValue (false);
00196     if (ok) *ok = true;
00197     fmtType = Number_format;    //TODO: really?
00198   }
00199   return val;
00200 }
00201 
00202 double ValueParser::readNumber(const QString &_str, bool * ok, bool * isInt)
00203 {
00204   QString str = _str.stripWhiteSpace();
00205   bool neg = str.find(parserLocale->negativeSign()) == 0;
00206   if (neg)
00207     str.remove( 0, parserLocale->negativeSign().length() );
00208  
00209   /* will hold the scientific notation portion of the number.
00210      Example, with 2.34E+23, exponentialPart == "E+23"
00211   */
00212   QString exponentialPart;
00213   int EPos;
00214  
00215   EPos = str.find('E', 0, false);
00216  
00217   if (EPos != -1)
00218   {
00219     exponentialPart = str.mid(EPos);
00220     str = str.left(EPos);
00221   }
00222  
00223   int pos = str.find(parserLocale->decimalSymbol());
00224   QString major;
00225   QString minor;
00226   if ( pos == -1 )
00227   {
00228     major = str;
00229     if (isInt) *isInt = true;
00230   }
00231   else
00232   {
00233     major = str.left(pos);
00234     minor = str.mid(pos + parserLocale->decimalSymbol().length());
00235     if (isInt) *isInt = false;
00236   }
00237  
00238   // Remove thousand separators
00239   int thlen = parserLocale->thousandsSeparator().length();
00240   int lastpos = 0;
00241   while ( ( pos = major.find( parserLocale->thousandsSeparator() ) ) > 0 )
00242   {
00243     // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
00244     int fromEnd = major.length() - pos;
00245     if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error
00246          || pos - lastpos > 3 // More than 3 digits between two separators -> error
00247          || pos == 0          // Can't start with a separator
00248          || (lastpos>0 && pos-lastpos!=3))   // Must have exactly 3 digits between two separators
00249     {
00250       if (ok) *ok = false;
00251       return 0.0;
00252     }
00253  
00254     lastpos = pos;
00255     major.remove( pos, thlen );
00256   }
00257   if (lastpos>0 && major.length()-lastpos!=3)   // Must have exactly 3 digits after the last separator
00258   {
00259     if (ok) *ok = false;
00260     return 0.0;
00261   }
00262  
00263   QString tot;
00264   if (neg) tot = '-';
00265  
00266   tot += major + '.' + minor + exponentialPart;
00267  
00268   return tot.toDouble(ok);
00269 }
00270 
00271 Value ValueParser::tryParseNumber (const QString& str, bool *ok)
00272 {
00273   Value value;
00274 
00275   bool percent = false;
00276   QString str2;
00277   if( str.at(str.length()-1)=='%')
00278   {
00279     str2 = str.left (str.length()-1).stripWhiteSpace();
00280     percent = true;
00281   }
00282   else
00283     str2 = str;
00284 
00285   
00286   // First try to understand the number using the parserLocale
00287   bool isInt;
00288   double val = readNumber (str2, ok, &isInt);
00289   // If not, try with the '.' as decimal separator
00290   if (!(*ok))
00291   {
00292     val = str2.toDouble(ok);
00293     if (str.contains('.'))
00294       isInt = false;
00295     else
00296       isInt = true;
00297   }
00298 
00299   if (*ok)
00300   {
00301     if (percent)
00302     {
00303       //kdDebug(36001) << "ValueParser::tryParseNumber '" << str <<
00304       //    "' successfully parsed as percentage: " << val << "%" << endl;
00305       value.setValue (val / 100.0);
00306       value.setFormat (Value::fmt_Percent);
00307       fmtType = Percentage_format;
00308     }
00309     else
00310     {
00311       //kdDebug(36001) << "ValueParser::tryParseNumber '" << str <<
00312       //    "' successfully parsed as number: " << val << endl;
00313       if (isInt)
00314         value.setValue (static_cast<long> (val));
00315       else
00316         value.setValue (val);
00317       
00318       if ( str2.contains('E') || str2.contains('e') )
00319         fmtType = Scientific_format;
00320       else
00321       {
00322         if (val > 1e+10)
00323           fmtType = Scientific_format;
00324         else
00325           fmtType = Number_format;
00326       }
00327     }
00328   }
00329 
00330   return value;
00331 }
00332 
00333 Value ValueParser::tryParseDate (const QString& str, bool *ok)
00334 {
00335   bool valid = false;
00336   QDate tmpDate = parserLocale->readDate (str, &valid);
00337   if (!valid)
00338   {
00339     // Try without the year
00340     // The tricky part is that we need to remove any separator around the year
00341     // For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m
00342     // If the year is in the middle, say %m-%Y/%d, we'll remove the sep.
00343     // before it (%m/%d).
00344     QString fmt = parserLocale->dateFormatShort();
00345     int yearPos = fmt.find ("%Y", 0, false);
00346     if ( yearPos > -1 )
00347     {
00348       if ( yearPos == 0 )
00349       {
00350         fmt.remove( 0, 2 );
00351         while ( fmt[0] != '%' )
00352           fmt.remove( 0, 1 );
00353       } else
00354       {
00355         fmt.remove( yearPos, 2 );
00356         for ( ; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos )
00357           fmt.remove( yearPos, 1 );
00358       }
00359       //kdDebug(36001) << "Cell::tryParseDate short format w/o date: " << fmt << endl;
00360       tmpDate = parserLocale->readDate( str, fmt, &valid );
00361     }
00362   }
00363   if (valid)
00364   {
00365     // Note: if shortdate format only specifies 2 digits year, then 3/4/1955
00366     // will be treated as in year 3055, while 3/4/55 as year 2055
00367     // (because 55 < 69, see KLocale) and thus there's no way to enter for
00368     // year 1995
00369   
00370     // The following fixes the problem, 3/4/1955 will always be 1955
00371 
00372     QString fmt = parserLocale->dateFormatShort();
00373     if( ( fmt.contains( "%y" ) == 1 ) && ( tmpDate.year() > 2999 ) )
00374       tmpDate = tmpDate.addYears( -1900 );
00375 
00376     // this is another HACK !
00377     // with two digit years, 0-69 is treated as year 2000-2069 (see KLocale)
00378     // however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930
00379     // onwards
00380 
00381     // the following provides workaround for KLocale so we're compatible
00382     // with Excel
00383     // (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045)
00384     if( ( tmpDate.year() >= 2030 ) && ( tmpDate.year() <= 2069 ) )
00385     {
00386       QString yearFourDigits = QString::number( tmpDate.year() );
00387       QString yearTwoDigits = QString::number( tmpDate.year() % 100 );
00388 
00389       // if year is 2045, check to see if "2045" isn't there --> actual
00390       // input is "45"
00391       if( ( str.contains( yearTwoDigits ) >= 1 ) &&
00392           ( str.contains( yearFourDigits ) == 0 ) )
00393         tmpDate = tmpDate.addYears( -100 );
00394     }
00395     
00396     //test if it's a short date or text date.
00397     if (parserLocale->formatDate (tmpDate, false) == str)
00398       fmtType = TextDate_format;
00399     else
00400       fmtType = ShortDate_format;
00401   }
00402   if (!valid)
00403   {
00404     //try to use the standard Qt date parsing, using ISO 8601 format
00405     tmpDate = QDate::fromString(str,Qt::ISODate);
00406     if (tmpDate.isValid())
00407     {
00408       valid = true;
00409     }
00410   }
00411   
00412   if (ok)
00413     *ok = valid;
00414     
00415   return Value (tmpDate);
00416 }
00417 
00418 Value ValueParser::tryParseTime (const QString& str, bool *ok)
00419 {
00420   if (ok)
00421     *ok = false;
00422 
00423   bool valid    = false;
00424   bool duration = false;
00425   Value val;
00426 
00427   QDateTime tmpTime = readTime (str, true, &valid, duration);
00428   if (!tmpTime.isValid())
00429     tmpTime = readTime (str, false, &valid, duration);
00430 
00431   if (!valid)
00432   {
00433     QTime tm;
00434     if (parserLocale->use12Clock())
00435     {
00436       QString stringPm = parserLocale->translate("pm");
00437       QString stringAm = parserLocale->translate("am");
00438       int pos=0;
00439       if((pos=str.find(stringPm))!=-1)
00440       {
00441           QString tmp=str.mid(0,str.length()-stringPm.length());
00442           tmp=tmp.simplifyWhiteSpace();
00443           tm = parserLocale->readTime(tmp+" "+stringPm, &valid);
00444           if (!valid)
00445               tm = parserLocale->readTime(tmp+":00 "+stringPm, &valid);
00446       }
00447       else if((pos=str.find(stringAm))!=-1)
00448       {
00449           QString tmp = str.mid(0,str.length()-stringAm.length());
00450           tmp = tmp.simplifyWhiteSpace();
00451           tm = parserLocale->readTime (tmp + " " + stringAm, &valid);
00452           if (!valid)
00453               tm = parserLocale->readTime (tmp + ":00 " + stringAm, &valid);
00454       }
00455       if (valid)
00456           tmpTime.setTime(tm);
00457     }
00458   }
00459   if (valid)
00460   {
00461     fmtType = Time_format;
00462     if ( duration )
00463     {
00464       val.setValue (tmpTime);
00465       fmtType = Time_format7;
00466     }
00467     else
00468       val.setValue (tmpTime.time());
00469   }
00470   
00471   if (ok)
00472     *ok = valid;
00473     
00474   return val;
00475 }
00476 
00477 QDateTime ValueParser::readTime (const QString & intstr, bool withSeconds,
00478     bool *ok, bool & duration)
00479 {
00480   duration = false;
00481   QString str = intstr.simplifyWhiteSpace().lower();
00482   QString format = parserLocale->timeFormat().simplifyWhiteSpace();
00483   if ( !withSeconds )
00484   {
00485     int n = format.find("%S");
00486     format = format.left( n - 1 );
00487   }
00488 
00489   int days = -1;
00490   int hour = -1, minute = -1;
00491   int second = withSeconds ? -1 : 0; // don't require seconds
00492   bool g_12h = false;
00493   bool pm = false;
00494   uint strpos = 0;
00495   uint formatpos = 0;
00496 
00497   QDate refDate( 1899, 12, 31 );
00498 
00499   uint l  = format.length();
00500   uint sl = str.length();
00501 
00502   while (l > formatpos || sl > strpos)
00503   {
00504     if ( !(l > formatpos && sl > strpos) )
00505       goto error;
00506 
00507     QChar c( format.at( formatpos++ ) );
00508 
00509     if (c != '%')
00510     {
00511       if (c.isSpace())
00512         ++strpos;
00513       else if (c != str.at(strpos++))
00514         goto error;
00515       continue;
00516     }
00517 
00518     // remove space at the begining
00519     if (sl > strpos && str.at( strpos).isSpace() )
00520       ++strpos;
00521 
00522     c = format.at( formatpos++ );
00523     switch (c)
00524     {
00525      case 'p':
00526       {
00527         QString s;
00528         s = parserLocale->translate("pm").lower();
00529         int len = s.length();
00530         if (str.mid(strpos, len) == s)
00531         {
00532           pm = true;
00533           strpos += len;
00534         }
00535         else
00536         {
00537           s = parserLocale->translate("am").lower();
00538           len = s.length();
00539           if (str.mid(strpos, len) == s)
00540           {
00541             pm = false;
00542             strpos += len;
00543           }
00544           else
00545             goto error;
00546         }
00547       }
00548       break;
00549 
00550      case 'k':
00551      case 'H':
00552       g_12h = false;
00553       hour = readInt(str, strpos);
00554       if (hour < 0)
00555         goto error;
00556       if (hour > 23)
00557       {
00558         days = (int)(hour / 24);
00559         hour %= 24;
00560       }
00561 
00562       break;
00563 
00564      case 'l':
00565      case 'I':
00566       g_12h = true;
00567       hour = readInt(str, strpos);
00568       if (hour < 1 || hour > 12)
00569         goto error;
00570 
00571       break;
00572 
00573      case 'M':
00574       minute = readInt(str, strpos);
00575       if (minute < 0 || minute > 59)
00576         goto error;
00577 
00578       break;
00579 
00580      case 'S':
00581       second = readInt(str, strpos);
00582       if (second < 0 || second > 59)
00583         goto error;
00584 
00585       break;
00586     }
00587   }
00588 
00589   if (g_12h)
00590   {
00591     hour %= 12;
00592     if (pm) hour += 12;
00593   }
00594 
00595   if (days > 0)
00596   {
00597     refDate.addDays( days );
00598     duration = true;
00599   }
00600 
00601   if (ok)
00602     *ok = true;
00603   return QDateTime( refDate, QTime( hour, minute, second ) );
00604 
00605  error:
00606   if (ok)
00607     *ok = false;
00608   // return invalid date if it didn't work
00609   return QDateTime( refDate, QTime( -1, -1, -1 ) ); 
00610 }
00611 
00618 int ValueParser::readInt (const QString &str, uint &pos)
00619 {
00620   if (!str.at(pos).isDigit())
00621     return -1;
00622   int result = 0;
00623   for ( ; str.length() > pos && str.at(pos).isDigit(); pos++ )
00624   {
00625     result *= 10;
00626     result += str.at(pos).digitValue();
00627   }
00628 
00629   return result;
00630 }
00631 
KDE Home | KDE Accessibility Home | Description of Access Keys