File indexing completed on 2024-04-28 16:21:34

0001 /* This file is part of the KDE project
0002    Copyright 2006,2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003    Copyright 1998,1999 Torben Weis <weis@kde.org>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 // Local
0022 #include "Util.h"
0023 
0024 #include <ctype.h>
0025 
0026 #include <QPen>
0027 
0028 #include <KCharsets>
0029 
0030 #include <KoUnit.h>
0031 
0032 #include "SheetsDebug.h"
0033 #include "Formula.h"
0034 #include "calligra_sheets_limits.h"
0035 #include "Localization.h"
0036 #include "Map.h"
0037 #include "NamedAreaManager.h"
0038 #include "Region.h"
0039 #include "Sheet.h"
0040 #include "Style.h"
0041 #include "odf/SheetsOdf.h"
0042 
0043 using namespace Calligra::Sheets;
0044 
0045 
0046 //used in Cell::encodeFormula and
0047 //  dialogs/kspread_dlg_paperlayout.cc
0048 int Calligra::Sheets::Util::decodeColumnLabelText(const QString &labelText)
0049 {
0050     int col = 0;
0051     const int offset = 'a' - 'A';
0052     int counterColumn = 0;
0053     const uint totalLength = labelText.length();
0054     uint labelTextLength = 0;
0055     for ( ; labelTextLength < totalLength; labelTextLength++) {
0056         const char c = labelText[labelTextLength].toLatin1();
0057         if (labelTextLength == 0 && c == '$')
0058             continue; // eat an absolute reference char that could be at the beginning only
0059         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
0060             break;
0061     }
0062     if (labelTextLength == 0) {
0063         warnSheets << "No column label text found for col:" << labelText;
0064         return 0;
0065     }
0066     for (uint i = 0; i < labelTextLength; i++) {
0067         const char c = labelText[i].toLatin1();
0068         counterColumn = (int) ::pow(26.0 , static_cast<int>(labelTextLength - i - 1));
0069         if (c >= 'A' && c <= 'Z')
0070             col += counterColumn * (c - 'A' + 1);  // okay here (Werner)
0071         else if (c >= 'a' && c <= 'z')
0072             col += counterColumn * (c - 'A' - offset + 1);
0073     }
0074     return col;
0075 }
0076 
0077 int Calligra::Sheets::Util::decodeRowLabelText(const QString &labelText)
0078 {
0079     QRegExp rx("(|\\$)([A-Za-z]+)(|\\$)([0-9]+)");
0080     if(rx.exactMatch(labelText))
0081         return rx.cap(4).toInt();
0082     return 0;
0083 }
0084 
0085 QString Calligra::Sheets::Util::encodeColumnLabelText(int column)
0086 {
0087     return Cell::columnName(column);
0088 }
0089 
0090 bool Calligra::Sheets::Util::isCellReference(const QString &text, int startPos)
0091 {
0092     int length = text.length();
0093     if (length < 1 || startPos >= length)
0094         return false;
0095 
0096     const QChar *data = text.constData();
0097 
0098     if (startPos > 0) {
0099         data += startPos;
0100     }
0101 
0102     if (*data == QChar('$', 0)) {
0103         ++data;
0104     }
0105 
0106     bool letterFound = false;
0107     while (1) {
0108         if (data->isNull()) {
0109             return false;
0110         }
0111 
0112         ushort c = data->unicode();
0113         if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z'))
0114             break;
0115 
0116         letterFound = true;
0117         ++data;
0118     }
0119 
0120     if (!letterFound) {
0121         return false;
0122     }
0123 
0124     if (*data == QChar('$', 0)) {
0125         ++data;
0126     }
0127 
0128     bool numberFound = false;
0129     while (!data->isNull()) {
0130         ushort c = data->unicode();
0131         if (c < '0' || c > '9')
0132             break;
0133         numberFound = true;
0134         ++data;
0135     }
0136 
0137     return numberFound && data->isNull(); // we found the number and reached end
0138 }
0139 
0140 QDomElement Calligra::Sheets::NativeFormat::createElement(const QString & tagName, const QFont & font, QDomDocument & doc)
0141 {
0142     QDomElement e(doc.createElement(tagName));
0143 
0144     e.setAttribute("family", font.family());
0145     e.setAttribute("size", QString::number(font.pointSize()));
0146     e.setAttribute("weight", QString::number(font.weight()));
0147     if (font.bold())
0148         e.setAttribute("bold", "yes");
0149     if (font.italic())
0150         e.setAttribute("italic", "yes");
0151     if (font.underline())
0152         e.setAttribute("underline", "yes");
0153     if (font.strikeOut())
0154         e.setAttribute("strikeout", "yes");
0155     //e.setAttribute( "charset", KCharsets::charsets()->name( font ) );
0156 
0157     return e;
0158 }
0159 
0160 QDomElement Calligra::Sheets::NativeFormat::createElement(const QString & tagname, const QPen & pen, QDomDocument & doc)
0161 {
0162     QDomElement e(doc.createElement(tagname));
0163     e.setAttribute("color", pen.color().name());
0164     e.setAttribute("style", QString::number((int)pen.style()));
0165     e.setAttribute("width", QString::number((int)pen.width())); // not qreal, would need spec change
0166     return e;
0167 }
0168 
0169 QFont Calligra::Sheets::NativeFormat::toFont(KoXmlElement & element)
0170 {
0171     QFont f;
0172     f.setFamily(element.attribute("family"));
0173 
0174     bool ok;
0175     const int size = element.attribute("size").toInt(&ok);
0176     if (ok)
0177         f.setPointSize(size);
0178 
0179     const int weight = element.attribute("weight").toInt(&ok);
0180     if (!ok)
0181         f.setWeight(weight);
0182 
0183     if (element.hasAttribute("italic") && element.attribute("italic") == "yes")
0184         f.setItalic(true);
0185 
0186     if (element.hasAttribute("bold") && element.attribute("bold") == "yes")
0187         f.setBold(true);
0188 
0189     if (element.hasAttribute("underline") && element.attribute("underline") == "yes")
0190         f.setUnderline(true);
0191 
0192     if (element.hasAttribute("strikeout") && element.attribute("strikeout") == "yes")
0193         f.setStrikeOut(true);
0194 
0195     /* Uncomment when charset is added to kspread_dlg_layout
0196        + save a document-global charset
0197        if ( element.hasAttribute( "charset" ) )
0198          KCharsets::charsets()->setQFont( f, element.attribute("charset") );
0199         else
0200     */
0201     // ######## Not needed anymore in 3.0?
0202     //KCharsets::charsets()->setQFont( f, KLocale::global()->charset() );
0203 
0204     return f;
0205 }
0206 
0207 QPen Calligra::Sheets::NativeFormat::toPen(KoXmlElement & element)
0208 {
0209     bool ok;
0210     QPen p;
0211 
0212     p.setStyle((Qt::PenStyle)element.attribute("style").toInt(&ok));
0213     if (!ok)
0214         return QPen();
0215 
0216     p.setWidth(element.attribute("width").toInt(&ok));
0217     if (!ok)
0218         return QPen();
0219 
0220     p.setColor(QColor(element.attribute("color")));
0221 
0222     return p;
0223 }
0224 
0225 bool util_isPointValid(const QPoint& point)
0226 {
0227     if (point.x() >= 0
0228             &&  point.y() >= 0
0229             &&  point.x() <= KS_colMax
0230             &&  point.y() <= KS_rowMax
0231        )
0232         return true;
0233     else
0234         return false;
0235 }
0236 
0237 bool util_isRectValid(const QRect& rect)
0238 {
0239     if (util_isPointValid(rect.topLeft())
0240             &&  util_isPointValid(rect.bottomRight())
0241        )
0242         return true;
0243     else
0244         return false;
0245 }
0246 
0247 
0248 //not used anywhere
0249 int Calligra::Sheets::Util::penCompare(QPen const & pen1, QPen const & pen2)
0250 {
0251     if (pen1.style() == Qt::NoPen && pen2.style() == Qt::NoPen)
0252         return 0;
0253 
0254     if (pen1.style() == Qt::NoPen)
0255         return -1;
0256 
0257     if (pen2.style() == Qt::NoPen)
0258         return 1;
0259 
0260     if (pen1.width() < pen2.width())
0261         return -1;
0262 
0263     if (pen1.width() > pen2.width())
0264         return 1;
0265 
0266     if (pen1.style() < pen2.style())
0267         return -1;
0268 
0269     if (pen1.style() > pen2.style())
0270         return 1;
0271 
0272     if (pen1.color().name() < pen2.color().name())
0273         return -1;
0274 
0275     if (pen1.color().name() > pen2.color().name())
0276         return 1;
0277 
0278     return 0;
0279 }
0280 
0281 
0282 QString Calligra::Sheets::Odf::convertRefToBase(const QString & sheet, const QRect & rect)
0283 {
0284     QPoint bottomRight(rect.bottomRight());
0285 
0286     QString s = '$' +
0287         sheet +
0288         ".$" +
0289         Cell::columnName(bottomRight.x()) +
0290         '$' +
0291         QString::number(bottomRight.y());
0292 
0293     return s;
0294 }
0295 
0296 QString Calligra::Sheets::Odf::convertRefToRange(const QString & sheet, const QRect & rect)
0297 {
0298     QPoint topLeft(rect.topLeft());
0299     QPoint bottomRight(rect.bottomRight());
0300 
0301     if (topLeft == bottomRight)
0302         return Odf::convertRefToBase(sheet, rect);
0303 
0304     QString s = '$' +
0305         sheet +
0306         ".$" +
0307         /*Util::encodeColumnLabelText*/Cell::columnName(topLeft.x()) +
0308         '$' +
0309         QString::number(topLeft.y()) +
0310         ":.$" +
0311         /*Util::encodeColumnLabelText*/Cell::columnName(bottomRight.x()) +
0312         '$' +
0313         QString::number(bottomRight.y());
0314 
0315     return s;
0316 }
0317 
0318 // e.g.: Sheet4.A1:Sheet4.E28
0319 //used in Sheet::saveOdf
0320 QString Calligra::Sheets::Odf::convertRangeToRef(const QString & sheetName, const QRect & _area)
0321 {
0322     return sheetName + '.' + Cell::name(_area.left(), _area.top()) + ':' + sheetName + '.' + Cell::name(_area.right(), _area.bottom());
0323 }
0324 
0325 //Return true when it's a reference to cell from sheet.
0326 bool Calligra::Sheets::Util::localReferenceAnchor(const QString &_ref)
0327 {
0328     bool isLocalRef = (_ref.indexOf("http://") != 0 &&
0329                        _ref.indexOf("https://") != 0 &&
0330                        _ref.indexOf("mailto:") != 0 &&
0331                        _ref.indexOf("ftp://") != 0  &&
0332                        _ref.indexOf("file:") != 0);
0333     return isLocalRef;
0334 }
0335 
0336 
0337 QString Calligra::Sheets::Odf::decodeFormula(const QString& expression_, const KLocale *locale, const QString &namespacePrefix)
0338 {
0339     // parsing state
0340     enum { Start, InNumber, InString, InIdentifier, InReference, InSheetName } state = Start;
0341 
0342     QString expression = expression_;
0343     if (namespacePrefix == "msoxl:") {
0344         expression = MSOOXML::convertFormula(expression);
0345     }
0346 
0347     // use locale settings
0348     QString decimal = locale ? locale->decimalSymbol() : ".";
0349 
0350     const QChar *data = expression.constData();
0351     const QChar *start = data;
0352 
0353     if (data->isNull()) {
0354         return QString();
0355     }
0356 
0357     int length = expression.length() * 2;
0358     QString result(length, QChar());
0359     result.reserve(length);
0360     QChar * out = result.data();
0361     QChar * outStart = result.data();
0362 
0363     if (*data == QChar('=', 0)) {
0364         *out = *data;
0365         ++data;
0366         ++out;
0367     }
0368 
0369     const QChar *pos = data;
0370     while (!data->isNull()) {
0371         switch (state) {
0372         case Start: {
0373             if (data->isDigit()) { // check for number
0374                 state = InNumber;
0375                 *out++ = *data++;
0376             }
0377             else if (*data == QChar('.', 0)) {
0378                 state = InNumber;
0379                 *out = decimal[0];
0380                 ++out;
0381                 ++data;
0382             }
0383             else if (isIdentifier(*data)) {
0384                 // beginning with alphanumeric ?
0385                 // could be identifier, cell, range, or function...
0386                 state = InIdentifier;
0387                 int i = data - start;
0388                 const static QString errorTypeReplacement("ERRORTYPE");
0389                 const static QString legacyNormsdistReplacement("LEGACYNORMSDIST");
0390                 const static QString legacyNormsinvReplacement("LEGACYNORMSINV");
0391                 const static QString multipleOperations("MULTIPLE.OPERATIONS");
0392                 if (expression.midRef(i,10).compare(QLatin1String("ERROR.TYPE")) == 0) {
0393                     // replace it
0394                     int outPos = out - outStart;
0395                     result.replace(outPos, 9, errorTypeReplacement);
0396                     data += 10; // number of characters in "ERROR.TYPE"
0397                     out += 9;
0398                 }
0399                 else if (expression.midRef(i, 12).compare(QLatin1String("LEGACY.NORMS")) == 0) {
0400                     if (expression.midRef(i + 12, 4).compare(QLatin1String("DIST")) == 0) {
0401                         // replace it
0402                         int outPos = out - outStart;
0403                         result.replace(outPos, 15, legacyNormsdistReplacement);
0404                         data += 16; // number of characters in "LEGACY.NORMSDIST"
0405                         out += 15;
0406                     }
0407                     else if (expression.midRef(i + 12, 3).compare(QLatin1String("INV")) == 0) {
0408                         // replace it
0409                         int outPos = out - outStart;
0410                         result.replace(outPos, 14, legacyNormsinvReplacement);
0411                         data += 15; // number of characters in "LEGACY.NORMSINV"
0412                         out += 14;
0413                     }
0414                 }
0415                 else if (namespacePrefix == "oooc:" && expression.midRef(i, 5).compare(QLatin1String("TABLE")) == 0 && !isIdentifier(expression[i+5])) {
0416                     int outPos = out - outStart;
0417                     result.replace(outPos, 19, multipleOperations);
0418                     data += 5;
0419                     out += 19;
0420                 }
0421                 else if (expression.midRef(i, 3).compare(QLatin1String("NEG")) == 0) {
0422                     *out = QChar('-', 0);
0423                     data += 3;
0424                     ++out;
0425                 }
0426             }
0427             else {
0428                 switch (data->unicode()) {
0429                 case '"': // a string ?
0430                     state = InString;
0431                     *out++ = *data++;
0432                     break;
0433                 case '[': // [ marks sheet name for 3-d cell, e.g ['Sales Q3'.A4]
0434                     state = InReference;
0435                     ++data;
0436                     // NOTE: As long as Calligra::Sheets does not support fixed sheets eat the dollar sign.
0437                     if (*data == QChar('$', 0)) {
0438                         ++data;
0439                     }
0440                     pos = data;
0441                     break;
0442                 default:
0443                     const QChar *operatorStart = data;
0444                     if (!parseOperator(data, out)) {
0445                         *out++ = *data++;
0446                     }
0447                     else if (*operatorStart == QChar('=', 0) && data - operatorStart == 1) { // only one =
0448                         *out++ = QChar('=', 0);
0449                     }
0450                     break;
0451                 }
0452             }
0453         }   break;
0454         case InNumber:
0455             if (data->isDigit()) {
0456                 *out++ = *data++;
0457             }
0458             else if (*data == QChar('.', 0)) {
0459                 const QChar *decimalChar = decimal.constData();
0460                 while (!decimalChar->isNull()) {
0461                     *out++ = *decimalChar++;
0462                 }
0463                 ++data;
0464             }
0465             else if (*data == QChar('E', 0) || *data == QChar('e', 0)) {
0466                 *out++ = QChar('E', 0);
0467                 ++data;
0468             }
0469             else {
0470                 state = Start;
0471             }
0472 
0473             break;
0474         case InString:
0475             if (*data == QChar('"', 0)) {
0476                 state = Start;
0477             }
0478             *out++ = *data++;
0479             break;
0480         case InIdentifier: {
0481             if (isIdentifier(*data) || data->isDigit()) {
0482                 *out++ = *data++;
0483             }
0484             else {
0485                 state = Start;
0486             }
0487         }   break;
0488         case InReference:
0489             switch (data->unicode()) {
0490             case ']':
0491                 Odf::loadRegion(pos, data, out);
0492                 pos = data;
0493                 state = Start;
0494                 break;
0495             case '\'':
0496                 state = InSheetName;
0497                 break;
0498             default:
0499                 break;
0500             }
0501             ++data;
0502             break;
0503         case InSheetName:
0504             if (*data == QChar('\'', 0)) {
0505                 ++data;
0506                 if (!data->isNull() && *data == QChar('\'', 0)) {
0507                     ++data;
0508                 }
0509                 else {
0510                     state = InReference;
0511                 }
0512             }
0513             else {
0514                 ++data;
0515             }
0516             break;
0517         }
0518     }
0519     result.resize(out - outStart);
0520     return result;
0521 }
0522 
0523 QString Calligra::Sheets::Odf::encodeFormula(const QString& expr, const KLocale* locale)
0524 {
0525     // use locale settings
0526     const QString decimal = locale ? locale->decimalSymbol() : ".";
0527 
0528     QString result('=');
0529 
0530     Formula formula;
0531     Tokens tokens = formula.scan(expr, locale);
0532 
0533     if (!tokens.valid() || tokens.count() == 0)
0534         return expr; // no altering on error
0535 
0536     for (int i = 0; i < tokens.count(); ++i) {
0537         const QString tokenText = tokens[i].text();
0538         const Token::Type type = tokens[i].type();
0539 
0540         switch (type) {
0541         case Token::Cell:
0542         case Token::Range: {
0543             result.append('[');
0544             // FIXME Stefan: Hack to get the apostrophes right. Fix and remove!
0545             const int pos = tokenText.lastIndexOf('!');
0546             if (pos != -1 && tokenText.left(pos).contains(' '))
0547                 result.append(Odf::saveRegion('\'' + tokenText.left(pos) + '\'' + tokenText.mid(pos)));
0548             else
0549                 result.append(Odf::saveRegion(tokenText));
0550             result.append(']');
0551             break;
0552         }
0553         case Token::Float: {
0554             QString tmp(tokenText);
0555             result.append(tmp.replace(decimal, "."));
0556             break;
0557         }
0558         case Token::Operator: {
0559             if (tokens[i].asOperator() == Token::Equal)
0560                 result.append('=');
0561             else
0562                 result.append(tokenText);
0563             break;
0564         }
0565         case Token::Identifier: {
0566             if (tokenText == "ERRORTYPE") {
0567                 // need to replace this
0568                 result.append("ERROR.TYPE");
0569             } else if (tokenText == "LEGACYNORMSDIST") {
0570                 result.append("LEGACY.NORMSDIST");
0571             } else if (tokenText == "LEGACYNORMSINV") {
0572                 result.append("LEGACY.NORMSINV");
0573             } else {
0574                 // dump it out unchanged
0575                 result.append(tokenText);
0576             }
0577             break;
0578 
0579         }
0580         case Token::Boolean:
0581         case Token::Integer:
0582         case Token::String:
0583         default:
0584             result.append(tokenText);
0585             break;
0586         }
0587     }
0588     return result;
0589 }
0590 
0591 static bool isCellnameCharacter(const QChar &c)
0592 {
0593     return c.isDigit() || c.isLetter() || c == '$';
0594 }
0595 
0596 static void replaceFormulaReference(int referencedRow, int referencedColumn, int thisRow, int thisColumn, QString &result, int cellReferenceStart, int cellReferenceLength)
0597 {
0598     const QString ref = result.mid(cellReferenceStart, cellReferenceLength);
0599     QRegExp rx("(|\\$)[A-Za-z]+(|\\$)[0-9]+");
0600     if (rx.exactMatch(ref)) {
0601         int c = Calligra::Sheets::Util::decodeColumnLabelText(ref);
0602         int r = Calligra::Sheets::Util::decodeRowLabelText(ref);
0603         if (rx.cap(1) != "$") // absolute or relative column?
0604             c += thisColumn - referencedColumn;
0605         if (rx.cap(2) != "$") // absolute or relative row?
0606             r += thisRow - referencedRow;
0607         result.replace(cellReferenceStart,
0608                        cellReferenceLength,
0609                        rx.cap(1) + Calligra::Sheets::Util::encodeColumnLabelText(c) +
0610                        rx.cap(2) + QString::number(r) );
0611     }
0612 }
0613 
0614 QString Calligra::Sheets::Util::adjustFormulaReference(const QString& formula, int referencedRow, int referencedColumn, int thisRow, int thisColumn)
0615 {
0616     QString result = formula;
0617     if (result.isEmpty())
0618         return QString();
0619     enum { InStart, InCellReference, InString, InSheetOrAreaName } state;
0620     state = InStart;
0621     int cellReferenceStart = 0;
0622     for(int i = 1; i < result.length(); ++i) {
0623         QChar ch = result[i];
0624         switch (state) {
0625         case InStart:
0626             if (ch == '"')
0627                 state = InString;
0628             else if (ch.unicode() == '\'')
0629                 state = InSheetOrAreaName;
0630             else if (isCellnameCharacter(ch)) {
0631                 state = InCellReference;
0632                 cellReferenceStart = i;
0633             }
0634             break;
0635         case InString:
0636             if (ch == '"')
0637                 state = InStart;
0638             break;
0639         case InSheetOrAreaName:
0640             if (ch == '\'')
0641                 state = InStart;
0642             break;
0643         case InCellReference:
0644             if (!isCellnameCharacter(ch)) {
0645                 // We need to update cell-references according to the position of the referenced cell and this
0646                 // cell. This means that if the referenced cell is for example at C5 and contains the formula
0647                 // "=SUM(K22)" and if thisCell is at E6 then thisCell will get the formula "=SUM(L23)".
0648                 if (ch != '(') /* skip formula-names */ {
0649                     replaceFormulaReference(referencedRow, referencedColumn, thisRow, thisColumn, result, cellReferenceStart, i - cellReferenceStart);
0650                 }
0651                 state = InStart;
0652                 --i; // decrement again to handle the current char in the InStart-switch.
0653             }
0654             break;
0655         };
0656     }
0657     if(state == InCellReference) {
0658         replaceFormulaReference(referencedRow, referencedColumn, thisRow, thisColumn, result, cellReferenceStart, result.length() - cellReferenceStart);
0659     }
0660     return result;
0661 }
0662 
0663 QString Calligra::Sheets::MSOOXML::convertFormula(const QString& formula)
0664 {
0665     if (formula.isEmpty())
0666         return QString();
0667     enum { InStart, InArguments, InParenthesizedArgument, InString, InSheetOrAreaName, InCellReference } state;
0668     state = InStart;
0669     int cellReferenceStart = 0;
0670     int sheetOrAreaNameDelimiterCount = 0;
0671     QString result = formula.startsWith('=') ? formula : '=' + formula;
0672     for(int i = 1; i < result.length(); ++i) {
0673         QChar ch = result[i];
0674         switch (state) {
0675         case InStart:
0676             if(ch == '(')
0677                 state = InArguments;
0678             break;
0679         case InArguments:
0680             if (ch == '"')
0681                 state = InString;
0682             else if (ch.unicode() == '\'') {
0683                 sheetOrAreaNameDelimiterCount = 1;
0684                 for(int j = i + 1; j < result.length(); ++j) {
0685                     if (result[j].unicode() != '\'')
0686                         break;
0687                     ++sheetOrAreaNameDelimiterCount;
0688                 }
0689                 if (sheetOrAreaNameDelimiterCount >= 2)
0690                     result.remove(i + 1, sheetOrAreaNameDelimiterCount - 1);
0691                 state = InSheetOrAreaName;
0692             } else if (isCellnameCharacter(ch)) {
0693                 state = InCellReference;
0694                 cellReferenceStart = i;
0695             } else if (ch == ',')
0696                 result[i] = ';'; // replace argument delimiter
0697             else if (ch == '(' && !result[i-1].isLetterOrNumber())
0698                 state = InParenthesizedArgument;
0699             else if (ch == ' ') {
0700                 // check if it might be an intersection operator
0701                 // for it to be an intersection operator the next non-space char must be a cell-name-character or '
0702                 // and previous converted char cannot be ';'
0703                 int firstNonSpace = i+1;
0704                 while (firstNonSpace < result.length() && result[firstNonSpace] == ' ') {
0705                     firstNonSpace++;
0706                 }
0707                 bool wasDelimeter = (i-1 > 0) && (result[i-1] == ';');
0708                 bool isIntersection = !wasDelimeter && firstNonSpace < result.length() && (result[firstNonSpace].isLetter() || result[firstNonSpace] == '$' || result[firstNonSpace] == '\'');
0709                 if (isIntersection) {
0710                     result[i] = '!';
0711                     i = firstNonSpace-1;
0712                 }
0713             }
0714             break;
0715         case InParenthesizedArgument:
0716             if (ch == ',')
0717                 result[i] = '~'; // union operator
0718             else if (ch == ' ')
0719                 result[i] = '!'; // intersection operator
0720             else if (ch == ')')
0721                 state = InArguments;
0722             break;
0723         case InString:
0724             if (ch == '"')
0725                 state = InArguments;
0726             break;
0727         case InSheetOrAreaName:
0728             Q_ASSERT( i >= 1 );
0729             if (ch == '\'' && result[i - 1].unicode() != '\\') {
0730                 int count = 1;
0731                 for(int j = i + 1; count < sheetOrAreaNameDelimiterCount && j < result.length(); ++j) {
0732                     if (result[j].unicode() != '\'')
0733                         break;
0734                     ++count;
0735                 }
0736                 if (count == sheetOrAreaNameDelimiterCount) {
0737                     if (sheetOrAreaNameDelimiterCount >= 2)
0738                         result.remove(i + 1, sheetOrAreaNameDelimiterCount - 1);
0739                     state = InArguments;
0740                 } else {
0741                     result.insert(i, '\'');
0742                     ++i;
0743                 }
0744             }
0745             break;
0746         case InCellReference:
0747             if (!isCellnameCharacter(ch)) {
0748                 if (ch != '(') /* skip formula-names */ {
0749                     // Excel is able to use only the column-name to define a column
0750                     // where all rows are selected. Since that is not supported in
0751                     // ODF we add to such definitions the minimum/maximum row-number.
0752                     // So, something like "A:B" would become "A$1:B$65536". Note that
0753                     // such whole column-definitions are only allowed for ranges like
0754                     // "A:B" but not for single column definitions like "A" or "B".
0755                     const QString ref = result.mid(qMax(0, cellReferenceStart - 1), i - cellReferenceStart + 2);
0756                     QRegExp rxStart(".*(|\\$)[A-Za-z]+\\:");
0757                     QRegExp rxEnd("\\:(|\\$)[A-Za-z]+(|(|\\$)[0-9]+).*");
0758                     if (rxEnd.exactMatch(ref) && rxEnd.cap(2).isEmpty()) {
0759                         result.insert(i, "$65536");
0760                         i += 6;
0761                     } else if (rxStart.exactMatch(ref)) {
0762                         result.insert(i, "$1");
0763                         i += 2;
0764                     }
0765                 }
0766                 state = InArguments;
0767                 --i; // decrement again to handle the current char in the InArguments-switch.
0768             }
0769             break;
0770         };
0771     };
0772     return result;
0773 }