File indexing completed on 2024-12-08 12:55:54
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 }