File indexing completed on 2024-05-12 16:28:56

0001 /*
0002  * This file is part of Office 2007 Filters for Calligra
0003  *
0004  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
0005  *
0006  * Contact: Christoph Schleifenbaum christoph@kdab.com
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Lesser General Public License
0010  * version 2.1 as published by the Free Software Foundation.
0011  *
0012  * This library is distributed in the hope that it will be useful, but
0013  * WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library; if not, write to the Free Software
0019  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020  * 02110-1301 USA
0021  *
0022  */
0023 #include "NumberFormatParser.h"
0024 
0025 #include <KoGenStyles.h>
0026 #include <KoXmlWriter.h>
0027 
0028 #include <MsoUtils.h>
0029 
0030 #include <QBuffer>
0031 #include <QLocale>
0032 #include <QString>
0033 #include <QStringList>
0034 
0035 #include <QColor>
0036 #include <QPalette>
0037 
0038 QColor NumberFormatParser::color(const QString& name)
0039 {
0040     if (name.toUpper().startsWith(QLatin1String("COLOR"))) {
0041         bool ok = false;
0042         const int index = name.mid(5).toInt(&ok) + 7;
0043         return MSO::defaultIndexedColor(index);
0044     } else {
0045         return QColor(name);
0046     }
0047 }
0048 
0049 QLocale NumberFormatParser::locale(int langid)
0050 {
0051     return MSO::localeForLangId(langid);
0052 }
0053 
0054 #define SET_TYPE_OR_RETURN( TYPE ) { \
0055 if (type == KoGenStyle::NumericDateStyle && TYPE == KoGenStyle::NumericTimeStyle)                \
0056 {                                                                                                \
0057 }                                                                                                \
0058 else if (type == KoGenStyle::NumericDateStyle && TYPE == KoGenStyle::NumericNumberStyle)         \
0059 {                                                                                                \
0060 }                                                                                                \
0061 else if (type == KoGenStyle::NumericTimeStyle && TYPE == KoGenStyle::NumericNumberStyle)         \
0062 {                                                                                                \
0063 }                                                                                                \
0064 else if (type == KoGenStyle::NumericTimeStyle && TYPE == KoGenStyle::NumericDateStyle)           \
0065 {                                                                                                \
0066     type = TYPE;                                                                                 \
0067 }                                                                                                \
0068 else if (type == KoGenStyle::NumericPercentageStyle && TYPE == KoGenStyle::NumericNumberStyle)   \
0069 {                                                                                                \
0070 }                                                                                                \
0071 else if (type == KoGenStyle::NumericNumberStyle && TYPE == KoGenStyle::NumericPercentageStyle)   \
0072 {                                                                                                \
0073     type = TYPE;                                                                                 \
0074 }                                                                                                \
0075 else if (type == KoGenStyle::NumericCurrencyStyle && TYPE == KoGenStyle::NumericNumberStyle)     \
0076 {                                                                                                \
0077 }                                                                                                \
0078 else if (type == KoGenStyle::NumericNumberStyle && TYPE == KoGenStyle::NumericCurrencyStyle)     \
0079 {                                                                                                \
0080     type = TYPE;                                                                                 \
0081 }                                                                                                \
0082 else if (type == KoGenStyle::NumericFractionStyle && TYPE == KoGenStyle::NumericNumberStyle)     \
0083 {                                                                                                \
0084 }                                                                                                \
0085 else if (type == KoGenStyle::NumericNumberStyle && TYPE == KoGenStyle::NumericFractionStyle)     \
0086 {                                                                                                \
0087     type = TYPE;                                                                                 \
0088 }                                                                                                \
0089 else if (type == KoGenStyle::NumericScientificStyle && TYPE == KoGenStyle::NumericNumberStyle)   \
0090 {                                                                                                \
0091 }                                                                                                \
0092 else if (type == KoGenStyle::NumericNumberStyle && TYPE == KoGenStyle::NumericScientificStyle)   \
0093 {                                                                                                \
0094     type = TYPE;                                                                                 \
0095 }                                                                                                \
0096 else if (type != KoGenStyle::ParagraphAutoStyle && type != TYPE)                                 \
0097 {                                                                                                \
0098     return KoGenStyle(KoGenStyle::ParagraphAutoStyle);                                           \
0099 }                                                                                                \
0100 else                                                                                             \
0101 {                                                                                                \
0102     type = TYPE;                                                                                 \
0103 }                                                                                                \
0104 }
0105 
0106 #define FINISH_PLAIN_TEXT_PART {             \
0107 if (!plainText.isEmpty()) {          \
0108     hadPlainText = true;                     \
0109     xmlWriter.startElement("number:text");   \
0110     xmlWriter.addTextNode(plainText );       \
0111     xmlWriter.endElement();                  \
0112     plainText.clear();                       \
0113 }                                            \
0114 }
0115 
0116 static KoGenStyle styleFromTypeAndBuffer(KoGenStyle::Type type, const QBuffer& buffer)
0117 {
0118     KoGenStyle result(type);
0119 
0120     const QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
0121     result.addChildElement("number", elementContents);
0122 
0123     return result;
0124 }
0125 
0126 KoGenStyle NumberFormatParser::parse(const QString& origNumberFormat, KoGenStyles* styles,
0127                      KoGenStyle::Type type)
0128 {
0129     QBuffer buffer;
0130     buffer.open(QIODevice::WriteOnly);
0131     KoXmlWriter xmlWriter(&buffer);
0132 
0133     QString plainText;
0134     QMap< QString, QString > conditions;
0135     QString condition;
0136 
0137     // This is for the month vs. minutes-context.
0138     bool justHadHours = false;
0139 
0140     // to skip escaped plain-text
0141     bool hadPlainText = false;
0142     QString numberFormat = origNumberFormat;
0143 
0144     for (int i = 0; i < numberFormat.length(); ++i) {
0145         const char c = numberFormat[ i ].toLatin1();
0146         bool isSpecial = true;
0147 
0148         const bool isLong       = (i < numberFormat.length() - 1
0149                    && numberFormat[ i + 1 ] == c);
0150         const bool isLonger     = (isLong
0151                    && i < numberFormat.length() - 2
0152                    && numberFormat[ i + 2 ] == c);
0153         const bool isLongest    = (isLonger
0154                    && i < numberFormat.length() - 3
0155                    && numberFormat[ i + 3 ] == c);
0156         const bool isWayTooLong = (isLongest
0157                    && i < numberFormat.length() - 4
0158                    && numberFormat[ i + 4 ] == c);
0159 
0160         switch (c) {
0161             // condition or color or locale or elapsed format
0162         case '[': {
0163             const char ch = (i < numberFormat.length() - 1) ? numberFormat[ ++i ].toLatin1() : ']';
0164             if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
0165                 // color code
0166                 QString colorName;
0167                 while (i < numberFormat.length() && numberFormat[ i ] != QLatin1Char(']'))
0168                     colorName += numberFormat[ i++ ];
0169 
0170                 const QColor color = NumberFormatParser::color(colorName);
0171                 if (color.isValid()) {
0172                     xmlWriter.startElement("style:text-properties");
0173                     xmlWriter.addAttribute("fo:color", color.name());
0174                     xmlWriter.endElement();
0175                 }
0176                 else if ((colorName == "hh") || (colorName == "h") ||
0177                          (colorName == "mm") || (colorName == "m") ||
0178                          (colorName == "ss") || (colorName == "s")) {
0179                     // was actually time in 'elapsed format'
0180                     numberFormat.insert(i+1, colorName);
0181                 }
0182             } else if (ch == '$'
0183                && i < numberFormat.length() - 2
0184                && numberFormat[ i + 1 ].toLatin1() != '-')
0185         {
0186                 SET_TYPE_OR_RETURN(KoGenStyle::NumericCurrencyStyle);
0187                 ++i;
0188 
0189         // currency code
0190                 QString currency;
0191                 while (i < numberFormat.length()
0192                && numberFormat[ i ] != QLatin1Char(']')
0193                && numberFormat[ i ] != QLatin1Char('-'))
0194                     currency += numberFormat[ i++ ];
0195 
0196                 QString language;
0197                 QString country;
0198 
0199                 if (numberFormat[ i ] == QLatin1Char('-')) {
0200                     ++i;
0201                     QString localeId;
0202                     while (i < numberFormat.length() && numberFormat[ i ] != QLatin1Char(']'))
0203                         localeId += numberFormat[ i++ ];
0204                     const QLocale locale = NumberFormatParser::locale(localeId.toInt(0, 16));
0205                     language = locale.name();
0206                     language = language.left(language.indexOf(QLatin1String("_")));
0207                     country = locale.name();
0208                     country = country.mid(country.indexOf(QLatin1String("_")) + 1);
0209                 }
0210 
0211                 xmlWriter.startElement("number:currency-symbol");
0212                 if (!language.isEmpty()) {
0213                     xmlWriter.addAttribute("number:language", language);
0214                 }
0215                 if (!country.isEmpty()) {
0216                     xmlWriter.addAttribute("number:country", country);
0217                 }
0218 
0219                 xmlWriter.addTextSpan(currency);
0220                 xmlWriter.endElement();
0221             } else {
0222                 // unknown - no idea, skip
0223                 while (i < numberFormat.length() && numberFormat[ i ] != QLatin1Char(']'))
0224                     ++i;
0225             }
0226         }
0227         break;
0228 
0229 
0230         // underscore: ignore the next char
0231         case '_':
0232             plainText += QLatin1Char(' ');
0233             ++i;
0234             break;
0235 
0236 
0237             // asterisk: ignore
0238         case '*':
0239         case '(':
0240         case ')':
0241             break;
0242 
0243             // percentage
0244         case '%':
0245             SET_TYPE_OR_RETURN(KoGenStyle::NumericPercentageStyle);
0246             FINISH_PLAIN_TEXT_PART
0247             xmlWriter.startElement("number:text");
0248             xmlWriter.addTextNode("%");
0249             xmlWriter.endElement();
0250             break;
0251 
0252             // a number
0253         case '.':
0254         case ',':
0255         case '#':
0256         case '0':
0257         case '?':
0258             SET_TYPE_OR_RETURN(KoGenStyle::NumericNumberStyle)
0259             FINISH_PLAIN_TEXT_PART
0260             // do following only if we are really a number and not part of another KoGenStyle like a date or time formatting
0261             if (type == KoGenStyle::NumericNumberStyle
0262         || type == KoGenStyle::NumericFractionStyle
0263         || type == KoGenStyle::NumericScientificStyle
0264         || type == KoGenStyle::NumericCurrencyStyle)
0265         {
0266                 bool grouping = false;
0267                 bool gotDot = false;
0268                 bool gotE = false;
0269                 bool gotFraction = false;
0270                 int decimalPlaces = 0;
0271                 int integerDigits = 0;
0272                 int exponentDigits = 0;
0273                 int numeratorDigits = 0;
0274                 int denominatorDigits = 0;
0275 
0276                 char ch = numberFormat[ i ].toLatin1();
0277                 do {
0278                     if (ch == '.') {
0279                         gotDot = true;
0280                     } else if (ch == ',') {
0281                         grouping = true;
0282                     } else if (ch == 'E' || ch == 'e') {
0283                         SET_TYPE_OR_RETURN(KoGenStyle::NumericScientificStyle);
0284 
0285                         if (i >= numberFormat.length() - 1) break;
0286                         const char chN = numberFormat[ i + 1 ].toLatin1();
0287                         if (chN == '-' || chN == '+') {
0288                             gotE = true;
0289                             ++i;
0290                         }
0291                     } else if (ch == '0' && gotE) {
0292                         ++exponentDigits;
0293                     } else if (ch == '0' && !gotDot && !gotFraction) {
0294                         ++integerDigits;
0295                     } else if (ch == '0' && gotDot && !gotFraction) {
0296                         ++decimalPlaces;
0297                     } else if (ch == '?' && !gotFraction) {
0298                         ++numeratorDigits;
0299                     } else if (ch == '?' && gotFraction) {
0300                         ++denominatorDigits;
0301                     } else if (ch == '/') {
0302                         SET_TYPE_OR_RETURN(KoGenStyle::NumericFractionStyle);
0303                         if (gotDot)
0304                             return KoGenStyle();
0305 
0306                         gotFraction = true;
0307                     }
0308 
0309                     if (i >= numberFormat.length() - 1) break;
0310                     ch = numberFormat[ ++i ].toLatin1();
0311 
0312                     if (ch == ' ') {
0313                         // Spaces are not allowed - but there is an exception:
0314                         // if this is a fraction. Let's check for '?' or '/'
0315                         const char c = numberFormat[ i + 1 ].toLatin1();
0316                         if (c == '?' || c == '/')
0317                             ch = numberFormat[ ++i ].toLatin1();
0318                     }
0319                 } while (i < numberFormat.length()
0320              && (ch == '.' || ch == ',' || ch == '#' || ch == '0'
0321                  || ch == 'E' || ch == 'e' || ch == '?' || ch == '/'));
0322 
0323                 if (!(ch == '.' || ch == ',' || ch == '#' || ch == '0'
0324               || ch == 'E' || ch == 'e' || ch == '?' || ch == '/'))
0325         {
0326                     --i;
0327                 }
0328 
0329                 if (gotFraction) {
0330                     xmlWriter.startElement("number:fraction");
0331                 } else if (exponentDigits > 0) {
0332                     xmlWriter.startElement("number:scientific-number");
0333                 } else {
0334                     xmlWriter.startElement("number:number");
0335                 }
0336                 if (!gotFraction) {
0337                     xmlWriter.addAttribute("number:decimal-places", decimalPlaces);
0338                 }
0339                 xmlWriter.addAttribute("number:min-integer-digits", integerDigits);
0340                 if (exponentDigits > 0) {
0341                     xmlWriter.addAttribute("number:min-exponent-digits", exponentDigits);
0342                 }
0343                 if (grouping) {
0344                     xmlWriter.addAttribute("number:grouping", grouping ? "true" : "false");
0345                 }
0346                 if (gotFraction) {
0347                     xmlWriter.addAttribute("number:min-numerator-digits", numeratorDigits);
0348                     xmlWriter.addAttribute("number:min-denominator-digits", denominatorDigits);
0349                 }
0350                 xmlWriter.endElement();
0351             }
0352             break;
0353 
0354 
0355             // Everything related to date/time
0356             // AM/PM
0357         case 'A':
0358         case 'a':
0359             if (numberFormat.mid(i, 5).toLower() == QLatin1String("am/pm") ||
0360                     numberFormat.mid(i, 3).toLower() == QLatin1String("a/p")) {
0361                 SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0362                 FINISH_PLAIN_TEXT_PART;
0363                 xmlWriter.startElement("number:am-pm");
0364                 xmlWriter.endElement();
0365                 if (numberFormat.mid(i, 5).toLower() == QLatin1String("am/pm"))
0366                     i += 2;
0367                 i += 2;
0368             }
0369             break;
0370 
0371 
0372             // hours, long or short
0373         case 'H':
0374         case 'h':
0375             SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0376             FINISH_PLAIN_TEXT_PART;
0377             xmlWriter.startElement("number:hours");
0378             if (isLong) {
0379                 xmlWriter.addAttribute("number:style", "long");
0380                 ++i;
0381             }
0382             xmlWriter.endElement();
0383             break;
0384 
0385 
0386             // minutes or months, depending on context
0387         case 'M':
0388         case 'm':
0389             // must be month, then, at least three M
0390             if (isLonger) {
0391                 SET_TYPE_OR_RETURN(KoGenStyle::NumericDateStyle)
0392                 FINISH_PLAIN_TEXT_PART;
0393                 xmlWriter.startElement("number:month");
0394                 const bool isReallyReallyLong = (isWayTooLong
0395                          && i < numberFormat.length() - 4
0396                          && numberFormat[ i + 4 ] == c);
0397                 if (isLongest && !isReallyReallyLong)
0398                     xmlWriter.addAttribute("number:style", "long");
0399                 if (isWayTooLong) {
0400             // If the month format is "mmmmm" then it's the
0401             // extra-short format of month.
0402                     xmlWriter.addAttribute("calligra:number-length", "extra-short");
0403                 }
0404                 xmlWriter.addAttribute("number:textual", "true");
0405                 xmlWriter.endElement();
0406                 i += isLongest ? (isWayTooLong ? 4 : 3) : 2;
0407                 if (isReallyReallyLong) {
0408                     ++i;
0409                 }
0410             }
0411             // This depends on the context. After hours and before seconds,
0412             // it's minutes otherwise it's the month
0413             else {
0414                 if (justHadHours) {
0415                     SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0416                     FINISH_PLAIN_TEXT_PART;
0417                     xmlWriter.startElement("number:minutes");
0418                     if (isLong)
0419                         xmlWriter.addAttribute("number:style", "long");
0420                     xmlWriter.endElement();
0421                 } else {
0422                     // On the next iteration, we might see whether there're
0423                     // seconds or something else. Let's just default to
0424                     // minutes, if there's nothing more.
0425                     bool minutes = true;
0426 
0427                     // So, let's look ahead:
0428                     for (int j = i + 1; j < numberFormat.length(); ++j) {
0429                         const char ch = numberFormat[ i ].toLatin1();
0430                         if (ch == 's' || ch == 'S') {   // minutes
0431                             break;
0432                         }
0433                         if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {   // months
0434                             continue;
0435                         }
0436                         minutes = false;
0437                         break;
0438                     }
0439                     if (minutes) {
0440                         SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0441                     } else {
0442                         SET_TYPE_OR_RETURN(KoGenStyle::NumericDateStyle)
0443                         FINISH_PLAIN_TEXT_PART;
0444                     }
0445                     if (minutes) {
0446                         xmlWriter.startElement("number:minutes");
0447                     } else {
0448                         xmlWriter.startElement("number:month");
0449                     }
0450                     if (isLong) {
0451                         xmlWriter.addAttribute("number:style", "long");
0452                     }
0453                     xmlWriter.endElement();
0454                 }
0455                 if (isLong) {
0456                     ++i;
0457                 }
0458             }
0459             break;
0460 
0461 
0462             // day (of week)
0463         case 'D':
0464         case 'd':
0465             SET_TYPE_OR_RETURN(KoGenStyle::NumericDateStyle)
0466             FINISH_PLAIN_TEXT_PART;
0467             if (!isLonger) {
0468                 xmlWriter.startElement("number:day");
0469                 if (isLong) {
0470                     xmlWriter.addAttribute("number:style", "long");
0471                 }
0472                 xmlWriter.endElement();
0473             } else {
0474                 xmlWriter.startElement("number:day-of-week");
0475                 if (isLongest) {
0476                     xmlWriter.addAttribute("number:style", "long");
0477                 }
0478                 xmlWriter.endElement();
0479             }
0480             if (isLong) {
0481                 ++i;
0482             }
0483             if (isLonger) {
0484                 ++i;
0485             }
0486             if (isLongest) {
0487                 ++i;
0488             }
0489             break;
0490 
0491             // seconds, long or short
0492         case 'S':
0493         case 's':
0494             SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0495             FINISH_PLAIN_TEXT_PART;
0496             xmlWriter.startElement("number:seconds");
0497             if (isLong) {
0498                 xmlWriter.addAttribute("number:style", "long");
0499                 ++i;
0500             }
0501             xmlWriter.endElement();
0502             break;
0503 
0504             // year, long or short
0505         case 'Y':
0506         case 'y':
0507             SET_TYPE_OR_RETURN(KoGenStyle::NumericDateStyle)
0508             FINISH_PLAIN_TEXT_PART;
0509             xmlWriter.startElement("number:year");
0510             if (isLongest) {
0511                 xmlWriter.addAttribute("number:style", "long");
0512                 i += 3;
0513             } else if (isLong) {
0514                 ++i;
0515             }
0516             xmlWriter.endElement();
0517             break;
0518 
0519             // Now it's getting really scary: semi-colon:
0520         case ';': {
0521             FINISH_PLAIN_TEXT_PART;
0522             buffer.close();
0523 
0524             // conditional style with the current format
0525             if (styles) {
0526                 KoGenStyle result = styleFromTypeAndBuffer(type, buffer);
0527                 result.addAttribute("style:volatile", "true");
0528                 const QString styleName = styles->insert(result, "N");
0529                 conditions.insertMulti(condition, styleName);
0530             }
0531             condition.clear();
0532 
0533             // Start a new style
0534             buffer.setData(QByteArray());
0535             buffer.open(QIODevice::WriteOnly);
0536             type = KoGenStyle::ParagraphAutoStyle;
0537             hadPlainText = false;
0538         }
0539         break;
0540 
0541         // text-content
0542         case '@':
0543             FINISH_PLAIN_TEXT_PART;
0544             hadPlainText = true;
0545             xmlWriter.startElement("number:text-content");
0546             xmlWriter.endElement();
0547             break;
0548 
0549             // quote - plain text block
0550         case '"':
0551             isSpecial = false;
0552             while (i < numberFormat.length() - 1 && numberFormat[ ++i ] != QLatin1Char('"'))
0553                 plainText += numberFormat[ i ];
0554             break;
0555 
0556             // backslash escapes the next char
0557         case '\\':
0558             isSpecial = false;
0559             if (i < numberFormat.length() - 1) {
0560                 plainText += numberFormat[ ++i ];
0561             }
0562             break;
0563 
0564             // every other char is just passed
0565         default:
0566             isSpecial = false;
0567             plainText += c;
0568             break;
0569         }
0570 
0571         // for the context-sensitive 'M' which can mean either minutes or months
0572         if (isSpecial) {
0573             justHadHours = (c == 'h' || c == 'H');
0574         }
0575     }
0576 
0577     FINISH_PLAIN_TEXT_PART;
0578 
0579     if (type == KoGenStyle::ParagraphAutoStyle && hadPlainText) {
0580         SET_TYPE_OR_RETURN(KoGenStyle::NumericTextStyle)
0581     }
0582 
0583     if (!condition.isEmpty()) {
0584         buffer.close();
0585 
0586         // conditional style with the current format
0587         if (styles) {
0588             KoGenStyle result = styleFromTypeAndBuffer(type, buffer);
0589             result.addAttribute("style:volatile", "true");
0590             const QString styleName = styles->insert(result, "N");
0591             conditions.insertMulti(condition, styleName);
0592         }
0593         condition.clear();
0594 
0595         // start a new style
0596         buffer.setData(QByteArray());
0597         buffer.open(QIODevice::WriteOnly);
0598     }
0599 
0600     // If conditions w/o explicit expressions where added, we create the
0601     // expressions.
0602     QStringList autoConditions;
0603     if (conditions.count(QString()) == 1) {
0604         autoConditions.push_back(QLatin1String("value()>=0"));
0605     } else {
0606         autoConditions.push_back(QLatin1String("value()>0"));
0607         autoConditions.push_back(QLatin1String("value()<0"));
0608         autoConditions.push_back(QLatin1String("value()=0"));
0609     }
0610 
0611     // Add conditional styles.
0612     for (QMap<QString, QString>::const_iterator it = conditions.constBegin();
0613      it != conditions.constEnd();
0614      ++it)
0615     {
0616         // Conditional styles are always numbers.
0617         type = KoGenStyle::NumericNumberStyle;
0618 
0619         xmlWriter.startElement("style:map");
0620         xmlWriter.addAttribute("style:condition", it.key().isEmpty() ? autoConditions.takeLast() : it.key());
0621         xmlWriter.addAttribute("style:apply-style-name", it.value());
0622         xmlWriter.endElement();
0623     }
0624 
0625     buffer.close();
0626 
0627     // conditional style with the current format
0628     return styleFromTypeAndBuffer(type, buffer);
0629 }
0630 
0631 bool NumberFormatParser::isDateFormat(const QString& numberFormat)
0632 {
0633     // this is for the month vs. minutes-context
0634     bool justHadHours = false;
0635 
0636     for (int i = 0; i < numberFormat.length(); ++i) {
0637         const char c = numberFormat[ i ].toLatin1();
0638         bool isSpecial = true;
0639 
0640         const bool isLong = i < numberFormat.length() - 1 && numberFormat[ i + 1 ] == c;
0641         const bool isLonger = isLong && i < numberFormat.length() - 2 && numberFormat[ i + 2 ] == c;
0642         const bool isLongest = isLonger && i < numberFormat.length() - 3 && numberFormat[ i + 3 ] == c;
0643         Q_UNUSED(isLongest);
0644         switch (c) {
0645             // condition or color or locale...
0646         case '[': {
0647             //don't care, skip
0648             while (i < numberFormat.length() && numberFormat[ i ] != QLatin1Char(']'))
0649                 ++i;
0650         }
0651         break;
0652 
0653 
0654         // underscore: ignore the next char
0655         case '_':
0656             ++i;
0657             break;
0658 
0659 
0660             // asterisk: ignore
0661         case '*':
0662         case '(':
0663         case ')':
0664             break;
0665 
0666             // percentage
0667         case '%':
0668             //SET_TYPE_OR_RETURN(KoGenStyle::NumericPercentageStyle);
0669             break;
0670 
0671             // a number
0672         case '.':
0673         case ',':
0674         case '#':
0675         case '0':
0676         case '?': {
0677             //SET_TYPE_OR_RETURN(KoGenStyle::NumericNumberStyle)
0678             char ch = numberFormat[ i ].toLatin1();
0679             do {
0680                 if (i >= numberFormat.length() - 1) break;
0681                 ch = numberFormat[ ++i ].toLatin1();
0682 
0683                 if (ch == ' ') {
0684                     // spaces are not allowed - but there's an exception: if
0685                     // this is a fraction. Let's check for '?' or '/'
0686                     const char c = numberFormat[ i + 1 ].toLatin1();
0687                     if (c == '?' || c == '/')
0688                         ch = numberFormat[ ++i ].toLatin1();
0689                 }
0690             } while (i < numberFormat.length()
0691              && (ch == '.' || ch == ',' || ch == '#' || ch == '0'
0692              || ch == 'E' || ch == 'e' || ch == '?' || ch == '/'));
0693 
0694             if (!(ch == '.' || ch == ',' || ch == '#' || ch == '0'
0695           || ch == 'E' || ch == 'e' || ch == '?' || ch == '/'))
0696         {
0697                 --i;
0698             }
0699         }
0700         break;
0701 
0702             // Everything related to date/time
0703             // AM/PM
0704         case 'A':
0705         case 'a':
0706             if (numberFormat.mid(i, 5).toLower() == QLatin1String("am/pm")
0707         || numberFormat.mid(i, 3).toLower() == QLatin1String("a/p"))
0708         {
0709                 // SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0710                 if (numberFormat.mid(i, 5).toLower() == QLatin1String("am/pm"))
0711                     i += 2;
0712                 i += 2;
0713             }
0714             break;
0715 
0716             // hours, long or short
0717         case 'H':
0718         case 'h':
0719             //SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0720             if (isLong) {
0721                 ++i;
0722             }
0723             break;
0724 
0725 
0726             // minutes or months, depending on context
0727         case 'M':
0728         case 'm':
0729             // must be month, then, at least three M
0730             if (isLonger) {
0731                 return true;
0732             }
0733             // depends on the context. After hours and before seconds, it's minutes
0734             // otherwise it's the month
0735             else {
0736                 if (justHadHours) {
0737                     //SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0738                 } else {
0739                     // On the next iteration, we might see whether there're
0740                     // seconds or something else. Let's just default to
0741                     // minutes, if there's nothing more.
0742                     bool minutes = true;
0743                     // so let's look ahead:
0744                     for (int j = i + 1; j < numberFormat.length(); ++j) {
0745                         const char ch = numberFormat[ i ].toLatin1();
0746                         if (ch == 's' || ch == 'S') {   // minutes
0747                             break;
0748                         }
0749                         if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {   // months
0750                             continue;
0751                         }
0752                         minutes = false;
0753                         break;
0754                     }
0755                     if (minutes) {
0756                         //SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0757                     } else {
0758                         return true;
0759                     }
0760                 }
0761                 if (isLong) {
0762                     ++i;
0763                 }
0764             }
0765             break;
0766 
0767 
0768             // day (of week)
0769         case 'D':
0770         case 'd':
0771             return true;
0772 
0773             // seconds, long or short
0774         case 'S':
0775         case 's':
0776             // SET_TYPE_OR_RETURN(KoGenStyle::NumericTimeStyle)
0777             if (isLong) {
0778                 ++i;
0779             }
0780             break;
0781 
0782             // year, long or short
0783         case 'Y':
0784         case 'y':
0785             return true;
0786 
0787             // now it's getting really scarry: semi-colon:
0788         case ';':
0789             break;
0790 
0791         // text-content
0792         case '@':
0793             break;
0794 
0795             // quote - plain text block
0796         case '"':
0797             isSpecial = false;
0798             while (i < numberFormat.length() - 1 && numberFormat[ ++i ] != QLatin1Char('"'))
0799                 /* empty */;
0800             break;
0801 
0802             // backslash escapes the next char
0803         case '\\':
0804             isSpecial = false;
0805             if (i < numberFormat.length() - 1) {
0806                 ++i;
0807             }
0808             break;
0809 
0810             // every other char is just passed
0811         default:
0812             isSpecial = false;
0813             break;
0814         }
0815 
0816         // for the context-sensitive 'M' which can mean either minutes or months
0817         if (isSpecial) {
0818             justHadHours = (c == 'h' || c == 'H');
0819         }
0820     }
0821 
0822     return false;
0823 }