File indexing completed on 2025-01-12 10:34:20

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