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 }