/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "zforscan.hxx" #include "zforfind.hxx" #include #include #include #include #include using namespace svt; namespace { constexpr OUString GREGORIAN = u"gregorian"_ustr; const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary... const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value. const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value. // Back in time was E16 that lead // to display rounding errors, see // also sal/rtl/math.cxx // doubleToString() constexpr sal_Int32 kTimeSignificantRound = 7; // Round (date+)time at 7 decimals // (+5 of 86400 == 12 significant digits). } // namespace const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0 constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1; const double D_MAX_D_BY_100 = 1.7E306; const double D_MIN_M_BY_1000 = 2.3E-305; const sal_uInt8 cCharWidths[ 128-32 ] = { 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2, 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3, 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2, 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2, 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1 }; // static sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c ) { if( c >= 32 ) { int n = 2; // Default for chars > 128 (HACK!) if( c <= 127 ) { n = static_cast(cCharWidths[ c - 32 ]); } while( n-- ) { r.insert( nPos++, ' '); } } return nPos; } static tools::Long GetPrecExp( double fAbsVal ) { DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" ); if ( fAbsVal < 1e-7 || fAbsVal > 1e7 ) { // Shear: whether it's faster or not, falls in between 1e6 and 1e7 return static_cast(floor( log10( fAbsVal ) )) + 1; } else { tools::Long nPrecExp = 1; while( fAbsVal < 1 ) { fAbsVal *= 10; nPrecExp--; } while( fAbsVal >= 10 ) { fAbsVal /= 10; nPrecExp++; } return nPrecExp; } } /** * SvNumberformatInfo * */ void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt ) { for (sal_uInt16 i = 0; i < nCnt; ++i) { sStrArray[i] = rNumFor.sStrArray[i]; nTypeArray[i] = rNumFor.nTypeArray[i]; } eScannedType = rNumFor.eScannedType; bThousand = rNumFor.bThousand; nThousand = rNumFor.nThousand; nCntPre = rNumFor.nCntPre; nCntPost = rNumFor.nCntPost; nCntExp = rNumFor.nCntExp; } const std::map> tblDBNumToNatNum = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } }, { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } }, { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } }; // static sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate ) { sal_uInt8 nNatNum = 0; eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. eLang = primary(eLang); // 10 bit primary language if ( bDate ) { if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) ) { nNatNum = 10; } else if ( nDBNum <= 3 ) { nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3 } } else { if (1 <= nDBNum && nDBNum <= 4) { auto const it = tblDBNumToNatNum.find(eLang); if (it != tblDBNumToNatNum.end()) nNatNum = it->second[nDBNum - 1]; } } return nNatNum; } const std::map> tblNatNumToDBNum = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } }, { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } }, { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } }; // static sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate ) { sal_uInt8 nDBNum = 0; eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. eLang = primary(eLang); // 10 bit primary language if ( bDate ) { if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) ) { nDBNum = 4; } else if ( nNatNum <= 3 ) { nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3 } } else { if (1 <= nNatNum && nNatNum <= 9) { auto const it = tblNatNumToDBNum.find(eLang); if (it != tblNatNumToDBNum.end()) nDBNum = it->second[nNatNum - 1]; } } return nDBNum; } /** * SvNumFor */ ImpSvNumFor::ImpSvNumFor() { nStringsCnt = 0; aI.eScannedType = SvNumFormatType::UNDEFINED; aI.bThousand = false; aI.nThousand = 0; aI.nCntPre = 0; aI.nCntPost = 0; aI.nCntExp = 0; pColor = nullptr; } ImpSvNumFor::~ImpSvNumFor() { } void ImpSvNumFor::Enlarge(sal_uInt16 nCnt) { if ( nStringsCnt != nCnt ) { nStringsCnt = nCnt; aI.nTypeArray.resize(nCnt); aI.sStrArray.resize(nCnt); } } void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc ) { Enlarge( rNumFor.nStringsCnt ); aI.Copy( rNumFor.aI, nStringsCnt ); sColorName = rNumFor.sColorName; if ( pSc ) { pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents } else { pColor = rNumFor.pColor; } aNatNum = rNumFor.aNatNum; } bool ImpSvNumFor::HasNewCurrency() const { for ( sal_uInt16 j=0; j 0 ) { return true; // conditions } switch ( nSymbolType ) { case BRACKET_SYMBOLTYPE_COLOR : case BRACKET_SYMBOLTYPE_DBNUM1 : case BRACKET_SYMBOLTYPE_DBNUM2 : case BRACKET_SYMBOLTYPE_DBNUM3 : case BRACKET_SYMBOLTYPE_DBNUM4 : case BRACKET_SYMBOLTYPE_DBNUM5 : case BRACKET_SYMBOLTYPE_DBNUM6 : case BRACKET_SYMBOLTYPE_DBNUM7 : case BRACKET_SYMBOLTYPE_DBNUM8 : case BRACKET_SYMBOLTYPE_DBNUM9 : case BRACKET_SYMBOLTYPE_LOCALE : case BRACKET_SYMBOLTYPE_NATNUM0 : case BRACKET_SYMBOLTYPE_NATNUM1 : case BRACKET_SYMBOLTYPE_NATNUM2 : case BRACKET_SYMBOLTYPE_NATNUM3 : case BRACKET_SYMBOLTYPE_NATNUM4 : case BRACKET_SYMBOLTYPE_NATNUM5 : case BRACKET_SYMBOLTYPE_NATNUM6 : case BRACKET_SYMBOLTYPE_NATNUM7 : case BRACKET_SYMBOLTYPE_NATNUM8 : case BRACKET_SYMBOLTYPE_NATNUM9 : case BRACKET_SYMBOLTYPE_NATNUM10 : case BRACKET_SYMBOLTYPE_NATNUM11 : case BRACKET_SYMBOLTYPE_NATNUM12 : case BRACKET_SYMBOLTYPE_NATNUM13 : case BRACKET_SYMBOLTYPE_NATNUM14 : case BRACKET_SYMBOLTYPE_NATNUM15 : case BRACKET_SYMBOLTYPE_NATNUM16 : case BRACKET_SYMBOLTYPE_NATNUM17 : case BRACKET_SYMBOLTYPE_NATNUM18 : case BRACKET_SYMBOLTYPE_NATNUM19 : return true; } return false; } /** Import extended LCID from Excel */ OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos, LanguageType& nLang, const LocaleType& aTmpLocale ) { OUString sCalendar; sal_uInt16 nNatNum = 0; LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage ); LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage ); /* NOTE: enhancement to allow other possible locale dependent * calendars and numerals. BUT only if our locale data allows it! For LCID * numerals and calendars see * http://office.microsoft.com/en-us/excel/HA010346351033.aspx * Calendar is inserted after * all prefixes have been consumed as it is actually a format modifier * and not a prefix. * Currently calendars are tied to the locale of the entire number * format, e.g. [~buddhist] in en_US doesn't work. * => Having different locales in sub formats does not work! * */ /* TODO: calendars could be tied to a sub format's NatNum info * instead, or even better be available for any locale. Needs a * different implementation of GetCal() and locale data calendars. * */ switch ( aTmpLocale.mnCalendarType & 0x7F ) { case 0x03 : // Gengou calendar // Only Japanese language support Gengou calendar. // It is an implicit "other" calendar where E, EE, R and RR // automatically switch to and YY and YYYY switch to Gregorian. Do // not add the "[~gengou]" modifier. if ( nLocaleLang != LANGUAGE_JAPANESE ) { nLang = maLocale.meLanguage = LANGUAGE_JAPANESE; } break; case 0x05 : // Korean Dangi calendar sCalendar = "[~dangi]"; // Only Korean language support dangi calendar if ( nLocaleLang != LANGUAGE_KOREAN ) { nLang = maLocale.meLanguage = LANGUAGE_KOREAN; } break; case 0x06 : // Hijri calendar case 0x17 : // same? sCalendar = "[~hijri]"; // Only Arabic or Farsi languages support Hijri calendar if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) && nLocaleLang != LANGUAGE_FARSI ) { if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY ) || nTmpLocaleLang == LANGUAGE_FARSI ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA; } } break; case 0x07 : // Buddhist calendar sCalendar="[~buddhist]"; // Only Thai or Lao languages support Buddhist calendar if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO ) { if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_THAI; } } break; case 0x08 : // Hebrew calendar sCalendar = "[~jewish]"; // Many languages (but not all) support Jewish calendar // Unable to find any logic => keep same language break; case 0x0E : // unknown calendar case 0x0F : // unknown calendar case 0x10 : // Indian calendar (unsupported) case 0x11 : // unknown calendar case 0x12 : // unknown calendar case 0x13 : // unknown calendar default : // other calendars (see tdf#36038) are not handle by LibO break; } /** Reference language for each numeral ID */ static const LanguageType aNumeralIDtoLanguage []= { LANGUAGE_DONTKNOW, // 0x00 LANGUAGE_ENGLISH_US, // 0x01 LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic LANGUAGE_FARSI, // 0x03 LANGUAGE_HINDI, // 0x04 + Devanagari LANGUAGE_BENGALI, // 0x05 LANGUAGE_PUNJABI, // 0x06 LANGUAGE_GUJARATI, // 0x07 LANGUAGE_ODIA, // 0x08 LANGUAGE_TAMIL, // 0x09 LANGUAGE_TELUGU, // 0x0A LANGUAGE_KANNADA, // 0x0B LANGUAGE_MALAYALAM, // 0x0C LANGUAGE_THAI, // 0x0D LANGUAGE_LAO, // 0x0E LANGUAGE_TIBETAN, // 0x0F LANGUAGE_BURMESE, // 0x10 LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11 LANGUAGE_KHMER, // 0x12 LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13 LANGUAGE_DONTKNOW, // 0x14 LANGUAGE_DONTKNOW, // 0x15 LANGUAGE_DONTKNOW, // 0x16 LANGUAGE_DONTKNOW, // 0x17 LANGUAGE_DONTKNOW, // 0x18 LANGUAGE_DONTKNOW, // 0x19 LANGUAGE_DONTKNOW, // 0x1A LANGUAGE_JAPANESE, // 0x1B LANGUAGE_JAPANESE, // 0x1C LANGUAGE_JAPANESE, // 0x1D LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F LANGUAGE_CHINESE_SIMPLIFIED, // 0x20 LANGUAGE_CHINESE_TRADITIONAL, // 0x21 LANGUAGE_CHINESE_TRADITIONAL, // 0x22 LANGUAGE_CHINESE_TRADITIONAL, // 0x23 LANGUAGE_KOREAN, // 0x24 LANGUAGE_KOREAN, // 0x25 LANGUAGE_KOREAN, // 0x26 LANGUAGE_KOREAN // 0x27 }; sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F; LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW; switch ( nNumeralID ) { // Regular cases: all languages with same primary mask use same numerals case 0x03 : // Perso-Arabic (Farsi) numerals case 0x05 : // Bengali numerals case 0x06 : // Punjabi numerals case 0x07 : // Gujarati numerals case 0x08 : // Odia (Orya) numerals case 0x09 : // Tamil numerals case 0x0A : // Telugu numerals case 0x0B : // Kannada numerals case 0x0C : // Malayalam numerals case 0x0D : // Thai numerals case 0x0E : // Lao numerals case 0x0F : // Tibetan numerals case 0x10 : // Burmese (Myanmar) numerals case 0x11 : // Tigrigna (Ethiopia) numerals case 0x12 : // Khmer numerals if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) { if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = nReferenceLanguage; } } break; // Special cases case 0x04 : // Devanagari (Hindi) numerals // same numerals (Devanagari) for languages with different primary masks if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) ) { if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_HINDI; } } break; case 0x13 : // Mongolian numerals // not all Mongolian languages use Mongolian numerals if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) { if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA; } } break; case 0x02 : // Eastern-Arabic numerals // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) { if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = nReferenceLanguage; } } break; // CJK numerals case 0x1B : // simple Asian numerals, Japanese case 0x1C : // financial Asian numerals, Japanese case 0x1D : // Arabic fullwidth numerals, Japanese case 0x24 : // simple Asian numerals, Korean case 0x25 : // financial Asian numerals, Korean case 0x26 : // Arabic fullwidth numerals, Korean case 0x27 : // Korean Hangul numerals // Japanese and Korean are regular if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) { if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = nReferenceLanguage; } } [[fallthrough]]; case 0x1E : // simple Asian numerals, Chinese-PRC case 0x1F : // financial Asian numerals, Chinese-PRC case 0x20 : // Arabic fullwidth numerals, Chinese-PRC case 0x21 : // simple Asian numerals, Chinese-Taiwan case 0x22 : // financial Asian numerals, Chinese-Taiwan case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1; // [NatNum1] simple numerals // [natNum2] financial numerals // [NatNum3] Arabic fullwidth numerals // Chinese simplified and Chinese traditional have same primary mask // Chinese-PRC if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE && nLocaleLang != LANGUAGE_CHINESE_LSO ) { if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE || nTmpLocaleLang == LANGUAGE_CHINESE_LSO ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED; } } // Chinese-Taiwan else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL && nLocaleLang != LANGUAGE_CHINESE_HONGKONG && nLocaleLang != LANGUAGE_CHINESE_MACAU ) { if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU ) { nLang = maLocale.meLanguage = aTmpLocale.meLanguage; } else { nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL; } } break; } if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 ) nNatNum = 1; if ( nNatNum ) rString.insert(nPos, "[NatNum" + OUString::number(nNatNum) + "]"); return sCalendar; } namespace { bool NatNumTakesParameters(sal_Int16 nNum) { return (nNum == css::i18n::NativeNumberMode::NATNUM12); } } // is there a 3-letter bank code in NatNum12 param (but not // followed by an equal mark, like in the date code "NNN=")? static bool lcl_isNatNum12Currency( const OUString& sParam ) { sal_Int32 nUpper = 0; sal_Int32 nLen = sParam.getLength(); for (sal_Int32 n = 0; n < nLen; ++n) { sal_Unicode c = sParam[n]; if ( 'A' <= c && c <= 'Z' ) { ++nUpper; } else if ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') ) { return true; } else { nUpper = 0; } } return nUpper == 3 && (nLen == 3 || sParam[nLen - 4] == ' '); } SvNumberformat::SvNumberformat(OUString& rString, ImpSvNumberformatScan* pSc, ImpSvNumberInputScan* pISc, sal_Int32& nCheckPos, LanguageType& eLan, bool bReplaceBooleanEquivalent) : rScan(*pSc) , bAdditionalBuiltin( false ) , bStarFlag( false ) { if (bReplaceBooleanEquivalent) rScan.ReplaceBooleanEquivalent( rString); OUStringBuffer sBuff(rString); // If the group (AKA thousand) separator is a No-Break Space (French) // replace all occurrences by a simple space. // The same for Narrow No-Break Space just in case some locale uses it. // The tokens will be changed to the LocaleData separator again later on. const OUString& rThSep = GetFormatter().GetNumThousandSep(); if ( rThSep.getLength() == 1) { const sal_Unicode cNBSp = 0xA0; const sal_Unicode cNNBSp = 0x202F; if (rThSep[0] == cNBSp ) sBuff.replace( cNBSp, ' '); else if (rThSep[0] == cNNBSp ) sBuff.replace( cNNBSp, ' '); } OUString aConvertFromDecSep; OUString aConvertToDecSep; if (rScan.GetConvertMode()) { aConvertFromDecSep = GetFormatter().GetNumDecimalSep(); maLocale.meLanguage = rScan.GetNewLnge(); eLan = maLocale.meLanguage; // Make sure to return switch } else { maLocale.meLanguage = eLan; } bStandard = false; bIsUsed = false; fLimit1 = 0.0; fLimit2 = 0.0; eOp1 = NUMBERFORMAT_OP_NO; eOp2 = NUMBERFORMAT_OP_NO; eType = SvNumFormatType::DEFINED; bool bCancel = false; bool bCondition = false; short eSymbolType; sal_Int32 nPos = 0; sal_Int32 nPosOld; nCheckPos = 0; // Split into 4 sub formats sal_uInt16 nIndex; for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ ) { // Original language/country may have to be reestablished if (rScan.GetConvertMode()) { rScan.GetNumberformatter()->ChangeIntl(rScan.GetTmpLnge()); } OUString sInsertCalendar; // a calendar resulting from parsing LCID OUString sStr; nPosOld = nPos; // Start position of substring // first get bracketed prefixes; e.g. conditions, color do { eSymbolType = ImpNextSymbol(sBuff, nPos, sStr); if (eSymbolType > 0) // condition { if ( nIndex == 0 && !bCondition ) { bCondition = true; eOp1 = static_cast(eSymbolType); } else if ( nIndex == 1 && bCondition ) { eOp2 = static_cast(eSymbolType); } else // error { bCancel = true; // break for nCheckPos = nPosOld; } if (!bCancel) { double fNumber; sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr); if (nCntChars > 0) { sal_Int32 nDecPos; SvNumFormatType F_Type = SvNumFormatType::UNDEFINED; if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, SvNumInputOptions::NONE) || ( F_Type != SvNumFormatType::NUMBER && F_Type != SvNumFormatType::SCIENTIFIC) ) { fNumber = 0.0; nPos = nPos - nCntChars; sBuff.remove(nPos, nCntChars); sBuff.insert(nPos, '0'); nPos++; } else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0)) { if (aConvertToDecSep.isEmpty()) aConvertToDecSep = GetFormatter().GetLangDecimalSep( rScan.GetNewLnge()); if (aConvertToDecSep != aConvertFromDecSep) { const OUString aStr( sStr.replaceAt( nDecPos, aConvertFromDecSep.getLength(), aConvertToDecSep)); nPos = nPos - nCntChars; sBuff.remove(nPos, nCntChars); sBuff.insert(nPos, aStr); nPos += aStr.getLength(); } } } else { fNumber = 0.0; sBuff.insert(nPos++, '0'); } if (nIndex == 0) { fLimit1 = fNumber; } else { fLimit2 = fNumber; } if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' ) { nPos++; } else { bCancel = true; // break for nCheckPos = nPos; } } nPosOld = nPos; // position before string } else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) { OUString sSymbol( sStr); switch ( eSymbolType ) { case BRACKET_SYMBOLTYPE_COLOR : if ( NumFor[nIndex].GetColor() != nullptr ) { // error, more than one color bCancel = true; // break for nCheckPos = nPosOld; } else { const Color* pColor = pSc->GetColor( sStr); NumFor[nIndex].SetColor( pColor, sStr); if (pColor == nullptr) { // error bCancel = true; // break for nCheckPos = nPosOld; } } break; case BRACKET_SYMBOLTYPE_NATNUM0 : case BRACKET_SYMBOLTYPE_NATNUM1 : case BRACKET_SYMBOLTYPE_NATNUM2 : case BRACKET_SYMBOLTYPE_NATNUM3 : case BRACKET_SYMBOLTYPE_NATNUM4 : case BRACKET_SYMBOLTYPE_NATNUM5 : case BRACKET_SYMBOLTYPE_NATNUM6 : case BRACKET_SYMBOLTYPE_NATNUM7 : case BRACKET_SYMBOLTYPE_NATNUM8 : case BRACKET_SYMBOLTYPE_NATNUM9 : case BRACKET_SYMBOLTYPE_NATNUM10 : case BRACKET_SYMBOLTYPE_NATNUM11 : case BRACKET_SYMBOLTYPE_NATNUM12 : case BRACKET_SYMBOLTYPE_NATNUM13 : case BRACKET_SYMBOLTYPE_NATNUM14 : case BRACKET_SYMBOLTYPE_NATNUM15 : case BRACKET_SYMBOLTYPE_NATNUM16 : case BRACKET_SYMBOLTYPE_NATNUM17 : case BRACKET_SYMBOLTYPE_NATNUM18 : case BRACKET_SYMBOLTYPE_NATNUM19 : if ( NumFor[nIndex].GetNatNum().IsSet() ) { bCancel = true; // break for nCheckPos = nPosOld; } else { OUString sParams; sal_Int32 nSpacePos = sStr.indexOf(' '); if (nSpacePos >= 0) { sParams = o3tl::trim(sStr.subView(nSpacePos+1)); } //! eSymbolType is negative sal_uInt8 nNum = static_cast(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0)); if (!sParams.isEmpty() && !NatNumTakesParameters(nNum)) { bCancel = true; // break for nCheckPos = nPosOld; break; } sStr = "NatNum" + OUString::number(nNum); NumFor[nIndex].SetNatNumNum( nNum, false ); // NatNum12 supports arguments if (nNum == 12) { if (sParams.isEmpty()) sParams = "cardinal"; // default NatNum12 format is "cardinal" else if (sParams.indexOf("CURRENCY") >= 0) sParams = sParams.replaceAll("CURRENCY", rLoc().getCurrBankSymbol()); NumFor[nIndex].SetNatNumParams(sParams); sStr += " " + sParams; } } break; case BRACKET_SYMBOLTYPE_DBNUM1 : case BRACKET_SYMBOLTYPE_DBNUM2 : case BRACKET_SYMBOLTYPE_DBNUM3 : case BRACKET_SYMBOLTYPE_DBNUM4 : case BRACKET_SYMBOLTYPE_DBNUM5 : case BRACKET_SYMBOLTYPE_DBNUM6 : case BRACKET_SYMBOLTYPE_DBNUM7 : case BRACKET_SYMBOLTYPE_DBNUM8 : case BRACKET_SYMBOLTYPE_DBNUM9 : if ( NumFor[nIndex].GetNatNum().IsSet() ) { bCancel = true; // break for nCheckPos = nPosOld; } else { //! eSymbolType is negative sal_uInt8 nNum = static_cast(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1)); sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum)); NumFor[nIndex].SetNatNumNum( nNum, true ); } break; case BRACKET_SYMBOLTYPE_LOCALE : if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW || sBuff[nPos-1] != ']' ) // Check also for ']' to avoid pulling in // locale data for the preview string for not // yet completed LCIDs in the dialog. { bCancel = true; // break for nCheckPos = nPosOld; } else { sal_Int32 nTmp = 2; LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp)); if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW) { bCancel = true; // break for nCheckPos = nPosOld; } else { // Only the first sub format's locale will be // used as the format's overall locale. // Sorts this also under the corresponding // locale for the dialog. // If we don't support the locale this would // result in an unknown (empty) language // listbox entry and the user would never see // this format. if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM || SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage))) { maLocale = aTmpLocale; eLan = aTmpLocale.meLanguage; // return to caller // Set new target locale also at scanner. // We have to do this because switching locale // may make replacing keywords and separators // necessary. // We can do this because it's the first // subformat and we're still at parsing the // modifiers, not keywords. rScan.SetNewLnge( eLan); // We can not force conversion though because // the caller may have explicitly not set it. // In the usual case the target locale is the // originating locale the conversion is not // necessary, when reading alien documents // conversion is enabled anyway. /* TODO: fiddle with scanner to make this * known? A change in the locale may affect * separators and keywords. On the other * hand they may have been entered as used * in the originating locale, there's no * way to predict other than analyzing the * format code, we assume here the current * context is used, which is most likely * the case. * */ // Strip a plain locale identifier if locale // data is available to avoid duplicated // formats with and without LCID for the same // locale. Besides it looks ugly and confusing // and is unnecessary as the format will be // listed for the resulting locale. if (aTmpLocale.isPlainLocale()) sStr.clear(); else sStr = "$-" + aTmpLocale.generateCode(); } else { if (nIndex == 0) // Locale data not available, remember. maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage; sStr = "$-" + aTmpLocale.generateCode(); } NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage)); // "$-NNCCLLLL" Numerals and Calendar if (sSymbol.getLength() > 6) { sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale); } /* NOTE: there can be only one calendar * inserted so the last one wins, though * our own calendar modifiers support * multiple calendars within one sub format * code if at different positions. */ } } break; } if ( !bCancel ) { if (sStr == sSymbol) { nPosOld = nPos; } else { sBuff.remove(nPosOld, nPos - nPosOld); if (!sStr.isEmpty()) { sBuff.insert(nPosOld, "[" + sStr + "]"); nPos = nPosOld + sStr.getLength() + 2; nPosOld = nPos; // position before string } else { nPos = nPosOld; // prefix removed for whatever reason } } } } } while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ); // The remaining format code string if ( !bCancel ) { if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT) { if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO) { eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 } else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO) { eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 } if (sStr.isEmpty()) { // Empty sub format. NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY; } else { if (!sInsertCalendar.isEmpty()) { sStr = sInsertCalendar + sStr; } sal_Int32 nStrPos = pSc->ScanFormat( sStr); sal_uInt16 nCnt = pSc->GetResultStringsCnt(); if (nCnt == 0 && nStrPos == 0) // error { nStrPos = 1; } if (nStrPos == 0) // ok { // e.g. Thai T speciality if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet()) { sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr; NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false ); } // #i53826# #i42727# For the Thai T speciality we need // to freeze the locale and immunize it against // conversions during exports, just in case we want to // save to Xcl. This disables the feature of being able // to convert a NatNum to another locale. You can't // have both. // FIXME: implement a specialized export conversion // that works on tokens (have to tokenize all first) // and doesn't use the format string and // PutandConvertEntry() to LANGUAGE_ENGLISH_US in // sc/source/filter/excel/xestyle.cxx // XclExpNumFmtBuffer::WriteFormatRecord(). LanguageType eLanguage; if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 && ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) && NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW) { sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr; NumFor[nIndex].SetNatNumLang( eLanguage); } sBuff.remove(nPosOld, nPos - nPosOld); sBuff.insert(nPosOld, sStr); nPos = nPosOld + sStr.getLength(); if (nPos < sBuff.getLength()) { sBuff.insert(nPos, ";"); nPos++; } else if (nIndex > 0) { // The last subformat. If it is a trailing text // format the omitted subformats act like they were // not specified and "inherited" the first format, // e.g. 0;@ behaves like 0;-0;0;@ if (pSc->GetScannedType() == SvNumFormatType::TEXT) { // Reset conditions, reverting any set above. if (nIndex == 1) eOp1 = NUMBERFORMAT_OP_NO; else if (nIndex == 2) eOp2 = NUMBERFORMAT_OP_NO; nIndex = 3; } } NumFor[nIndex].Enlarge(nCnt); pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt); // type check if (nIndex == 0) { if ( NumFor[nIndex].GetNatNum().GetNatNum() == 12 && lcl_isNatNum12Currency(NumFor[nIndex].GetNatNum().GetParams()) ) eType = SvNumFormatType::CURRENCY; else eType = NumFor[nIndex].Info().eScannedType; } else if (nIndex == 3) { // #77026# Everything recognized IS text NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT; } else if ( NumFor[nIndex].Info().eScannedType != eType) { eType = SvNumFormatType::DEFINED; } } else { nCheckPos = nPosOld + nStrPos; // error in string bCancel = true; // break for } } } else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error { nCheckPos = nPosOld; bCancel = true; } else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) { nCheckPos = nPosOld + 1; // error, prefix in string bCancel = true; // break for } } if ( bCancel && !nCheckPos ) { nCheckPos = 1; // nCheckPos is used as an error condition } if ( !bCancel ) { if ( NumFor[nIndex].GetNatNum().IsSet() && NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW ) { NumFor[nIndex].SetNatNumLang( eLan ); } } if (sBuff.getLength() == nPos) { if (nIndex < 3 && rString[rString.getLength()-1] == ';') { // A trailing ';' is significant and specifies the following // subformat to be empty. We don't enter the scanning loop // above again though. // Note that the operators apply to the current last scanned // subformat. if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO) { eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 } else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO) { eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 } NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY; if (sBuff[nPos-1] != ';') sBuff.insert( nPos++, ';'); } if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';') { // #83510# A 4th subformat explicitly specified to be empty // hides any text. Need the type here for HasTextFormat() NumFor[3].Info().eScannedType = SvNumFormatType::TEXT; } bCancel = true; } if ( NumFor[nIndex].GetNatNum().IsSet() ) { NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) ); } } if (!nCheckPos && IsSubstituted()) { // For to be substituted formats the scanned type must match the // substitute type. if (IsSystemTimeFormat()) { if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME) nCheckPos = std::max( sBuff.indexOf(']') + 1, 1); } else if (IsSystemLongDateFormat()) { if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE) nCheckPos = std::max( sBuff.indexOf(']') + 1, 1); } else assert(!"unhandled substitute"); } if ( bCondition && !nCheckPos ) { if ( nIndex == 1 && NumFor[0].GetCount() == 0 && sBuff[sBuff.getLength() - 1] != ';' ) { // No format code => GENERAL but not if specified empty OUString aAdd( pSc->GetStandardName() ); if ( !pSc->ScanFormat( aAdd ) ) { sal_uInt16 nCnt = pSc->GetResultStringsCnt(); if ( nCnt ) { NumFor[0].Enlarge(nCnt); pSc->CopyInfo( &(NumFor[0].Info()), nCnt ); sBuff.append(aAdd); } } } else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 && sBuff[sBuff.getLength() - 1] != ';' && (NumFor[0].GetCount() > 1 || (NumFor[0].GetCount() == 1 && NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) ) { // No trailing second subformat => GENERAL but not if specified empty // and not if first subformat is GENERAL OUString aAdd( pSc->GetStandardName() ); if ( !pSc->ScanFormat( aAdd ) ) { sal_uInt16 nCnt = pSc->GetResultStringsCnt(); if ( nCnt ) { NumFor[nIndex].Enlarge(nCnt); pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); sBuff.append(";" + aAdd); } } } else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 && sBuff[sBuff.getLength() - 1] != ';' && eOp2 != NUMBERFORMAT_OP_NO ) { // No trailing third subformat => GENERAL but not if specified empty OUString aAdd( pSc->GetStandardName() ); if ( !pSc->ScanFormat( aAdd ) ) { sal_uInt16 nCnt = pSc->GetResultStringsCnt(); if ( nCnt ) { NumFor[nIndex].Enlarge(nCnt); pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); sBuff.append(";" + aAdd); } } } } rString = sBuff.makeStringAndClear(); sFormatstring = rString; if (NumFor[2].GetCount() == 0 && // No third partial string eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO && fLimit1 == 0.0 && fLimit2 == 0.0) { eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format } } SvNumberformat::~SvNumberformat() { } /** * Next_Symbol * * Splits up the symbols for further processing (by the Turing machine) * * Start state = SsStart, * = Special state * ---------------+-------------------+----------------------------+--------------- * Old State | Symbol read | Event | New state * ---------------+-------------------+----------------------------+--------------- * SsStart | " | Symbol += Character | SsGetQuoted * | ; | Pos-- | SsGetString * | [ | Symbol += Character | SsGetBracketed * | ] | Error | SsStop * | BLANK | | * | Else | Symbol += Character | SsGetString * ---------------+-------------------+----------------------------+--------------- * SsGetString | " | Symbol += Character | SsGetQuoted * | ; | | SsStop * | Else | Symbol += Character | * ---------------+-------------------+----------------------------+--------------- * SsGetQuoted | " | Symbol += Character | SsGetString * | Else | Symbol += Character | * ---------------+-------------------+----------------------------+--------------- * SsGetBracketed | <, > = | del [ | * | | Symbol += Character | SsGetCon * | BLANK | | * | h, H, m, M, s, S | Symbol += Character | SsGetTime * | Else | del [ | * | | Symbol += Character | SsGetPrefix * ---------------+-------------------+----------------------------+--------------- * SsGetTime | ] | Symbol += Character | SsGetString * | h, H, m, M, s, S | Symbol += Character, * | SsGetString * | Else | del [; Symbol += Character | SsGetPrefix * ---------------+-------------------+----------------------------+--------------- * SsGetPrefix | ] | | SsStop * | Else | Symbol += Character | * ---------------+-------------------+----------------------------+--------------- * SsGetCon | >, = | Symbol += Character | * | ] | | SsStop * | Else | Error | SsStop * ---------------+-------------------+----------------------------+--------------- */ namespace { enum ScanState { SsStop, SsStart, SsGetCon, // condition SsGetString, // format string SsGetPrefix, // color or NatNumN SsGetTime, // [HH] for time SsGetBracketed, // any [...] not decided yet SsGetQuoted // quoted text }; } // read a string until ']' and delete spaces in input // static sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString, sal_Int32& nPos, OUString& sSymbol) { sal_Int32 nStartPos = nPos; sal_Unicode cToken; sal_Int32 nLen = rString.getLength(); OUStringBuffer sBuffSymbol; while ( nPos < nLen ) { cToken = rString[nPos]; if (cToken == ']') break; if (cToken == ' ') { // delete spaces rString.remove(nPos,1); nLen--; } else { nPos++; sBuffSymbol.append(cToken); } } sSymbol = sBuffSymbol.makeStringAndClear(); return nPos - nStartPos; } namespace { sal_Unicode toUniChar(sal_uInt8 n) { char c; if (n < 10) { c = '0' + n; } else { c = 'A' + n - 10; } return sal_Unicode(c); } bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos ) { bool bRet = false; while (nPos >= 0) { switch (rStringBuffer[nPos]) { case '*': case '\\': case '_': bRet = !bRet; --nPos; break; default: return bRet; } } return bRet; } } // namespace OUString SvNumberformat::LocaleType::generateCode() const { OUStringBuffer aBuf; #if 0 // TODO: We may re-enable this later. Don't remove it! --Kohei if (mnNumeralShape) { sal_uInt8 nVal = mnNumeralShape; for (sal_uInt8 i = 0; i < 2; ++i) { sal_uInt8 n = (nVal & 0xF0) >> 4; if (n || aBuf.getLength()) { aBuf.append(toUniChar(n)); } nVal = nVal << 4; } } if (mnNumeralShape || mnCalendarType) { sal_uInt8 nVal = mnCalendarType; for (sal_uInt8 i = 0; i < 2; ++i) { sal_uInt8 n = (nVal & 0xF0) >> 4; if (n || aBuf.getLength()) { aBuf.append(toUniChar(n)); } nVal = nVal << 4; } } #endif sal_uInt16 n16 = static_cast( (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage : meLanguageWithoutLocaleData); if (meLanguage == LANGUAGE_SYSTEM) { switch (meSubstitute) { case Substitute::NONE: ; // nothing break; case Substitute::TIME: n16 = static_cast(LANGUAGE_NF_SYSTEM_TIME); break; case Substitute::LONGDATE: n16 = static_cast(LANGUAGE_NF_SYSTEM_DATE); break; } } for (sal_uInt8 i = 0; i < 4; ++i) { sal_uInt8 n = static_cast((n16 & 0xF000) >> 12); // Omit leading zeros for consistency. if (n || !aBuf.isEmpty() || i == 3) { aBuf.append(toUniChar(n)); } n16 = n16 << 4; } return aBuf.makeStringAndClear(); } SvNumberformat::LocaleType::LocaleType() : meLanguage(LANGUAGE_DONTKNOW) , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) , meSubstitute(Substitute::NONE) , mnNumeralShape(0) , mnCalendarType(0) { } SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum) : meLanguage(LANGUAGE_DONTKNOW) , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) , meSubstitute(Substitute::NONE) , mnNumeralShape(0) , mnCalendarType(0) { meLanguage = static_cast(nRawNum & 0x0000FFFF); if (meLanguage == LANGUAGE_NF_SYSTEM_TIME) { meSubstitute = Substitute::TIME; meLanguage = LANGUAGE_SYSTEM; } else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE) { meSubstitute = Substitute::LONGDATE; meLanguage = LANGUAGE_SYSTEM; } nRawNum = (nRawNum >> 16); mnCalendarType = static_cast(nRawNum & 0xFF); nRawNum = (nRawNum >> 8); mnNumeralShape = static_cast(nRawNum & 0xFF); } bool SvNumberformat::LocaleType::isPlainLocale() const { return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape; } // static SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(std::u16string_view rString, sal_Int32& nPos ) { sal_uInt32 nNum = 0; sal_Unicode cToken = 0; sal_Int32 nStart = nPos; sal_Int32 nLen = rString.size(); while ( nPos < nLen && (nPos - nStart < 8) ) { cToken = rString[nPos]; if (cToken == ']') break; if ( '0' <= cToken && cToken <= '9' ) { nNum *= 16; nNum += cToken - '0'; } else if ( 'a' <= cToken && cToken <= 'f' ) { nNum *= 16; nNum += cToken - 'a' + 10; } else if ( 'A' <= cToken && cToken <= 'F' ) { nNum *= 16; nNum += cToken - 'A' + 10; } else { return LocaleType(); // LANGUAGE_DONTKNOW; } ++nPos; } return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType(); } static bool lcl_matchKeywordAndGetNumber( std::u16string_view rString, const sal_Int32 nPos, std::u16string_view rKeyword, sal_Int32 & nNumber ) { if (0 <= nPos && nPos + static_cast(rKeyword.size()) < static_cast(rString.size()) && o3tl::matchIgnoreAsciiCase( rString, rKeyword, nPos)) { nNumber = o3tl::toInt32(rString.substr( nPos + rKeyword.size())); return true; } else { nNumber = 0; return false; } } short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString, sal_Int32& nPos, OUString& sSymbol) const { short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; sal_Unicode cToken; sal_Unicode cLetter = ' '; // Preliminary result sal_Int32 nLen = rString.getLength(); ScanState eState = SsStart; OUStringBuffer sBuffSymbol(128); const NfKeywordTable & rKeywords = rScan.GetKeywords(); while (nPos < nLen && eState != SsStop) { cToken = rString[nPos]; nPos++; switch (eState) { case SsStart: if (cToken == '\"') { eState = SsGetQuoted; sBuffSymbol.append(cToken); } else if (cToken == '[') { eState = SsGetBracketed; sBuffSymbol.append(cToken); } else if (cToken == ';') { eState = SsGetString; nPos--; eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; } else if (cToken == ']') { eState = SsStop; eSymbolType = BRACKET_SYMBOLTYPE_ERROR; } else if (cToken == ' ') // Skip Blanks { nPos--; rString.remove(nPos, 1); nLen--; } else { sBuffSymbol.append(cToken); eState = SsGetString; eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; } break; case SsGetBracketed: switch (cToken) { case '<': case '>': case '=': sBuffSymbol.stripStart('['); sBuffSymbol.append(cToken); cLetter = cToken; eState = SsGetCon; switch (cToken) { case '<': eSymbolType = NUMBERFORMAT_OP_LT; break; case '>': eSymbolType = NUMBERFORMAT_OP_GT; break; case '=': eSymbolType = NUMBERFORMAT_OP_EQ; break; } break; case ' ': nPos--; rString.remove(nPos, 1); nLen--; break; case '$' : if ( nPos < nLen && rString[nPos] == '-' ) { // [$-xxx] locale sBuffSymbol.stripStart('['); eSymbolType = BRACKET_SYMBOLTYPE_LOCALE; eState = SsGetPrefix; } else { // currency eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; eState = SsGetString; } sBuffSymbol.append(cToken); break; case '~' : // calendarID eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; sBuffSymbol.append(cToken); eState = SsGetString; break; default: { static constexpr OUString aNatNum(u"NATNUM"_ustr); static constexpr OUString aDBNum(u"DBNUM"_ustr); const OUString aBufStr( rString.toString()); sal_Int32 nNatNumNum; sal_Int32 nDBNum; if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) && 0 <= nNatNumNum && nNatNumNum <= 19 ) { sBuffSymbol.stripStart('['); sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) ); nPos += aNatNum.getLength()+1; //! SymbolType is negative eSymbolType = static_cast(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum); eState = SsGetPrefix; } else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) && 1 <= nDBNum && nDBNum <= 9 ) { sBuffSymbol.stripStart('['); sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) ); nPos += aDBNum.getLength()+1; //! SymbolType is negative eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) ); eState = SsGetPrefix; } else { sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0]; if ( cUpper == rKeywords[NF_KEY_H][0] || // H cUpper == rKeywords[NF_KEY_MI][0] || // M cUpper == rKeywords[NF_KEY_S][0] ) // S { sBuffSymbol.append(cToken); eState = SsGetTime; cLetter = cToken; } else { sBuffSymbol.stripStart('['); sBuffSymbol.append(cToken); eSymbolType = BRACKET_SYMBOLTYPE_COLOR; eState = SsGetPrefix; } } } } break; case SsGetString: if (cToken == '\"') { eState = SsGetQuoted; sBuffSymbol.append(cToken); } else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2))) { eState = SsStop; } else { sBuffSymbol.append(cToken); } break; case SsGetQuoted: if (cToken == '\"') { eState = SsGetString; sBuffSymbol.append(cToken); } else { sBuffSymbol.append(cToken); } break; case SsGetTime: if (cToken == ']') { sBuffSymbol.append(cToken); eState = SsGetString; eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; } else { sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0]; if (cUpper == rKeywords[NF_KEY_H][0] || // H cUpper == rKeywords[NF_KEY_MI][0] || // M cUpper == rKeywords[NF_KEY_S][0] ) // S { if (cLetter == cToken) { sBuffSymbol.append(cToken); cLetter = ' '; } else { sBuffSymbol.stripStart('['); sBuffSymbol.append(cToken); eState = SsGetPrefix; } } else { sBuffSymbol.stripStart('['); sBuffSymbol.append(cToken); eSymbolType = BRACKET_SYMBOLTYPE_COLOR; eState = SsGetPrefix; } } break; case SsGetCon: switch (cToken) { case '<': eState = SsStop; eSymbolType = BRACKET_SYMBOLTYPE_ERROR; break; case '>': if (cLetter == '<') { sBuffSymbol.append(cToken); cLetter = ' '; eState = SsStop; eSymbolType = NUMBERFORMAT_OP_NE; } else { eState = SsStop; eSymbolType = BRACKET_SYMBOLTYPE_ERROR; } break; case '=': if (cLetter == '<') { sBuffSymbol.append(cToken); cLetter = ' '; eSymbolType = NUMBERFORMAT_OP_LE; } else if (cLetter == '>') { sBuffSymbol.append(cToken); cLetter = ' '; eSymbolType = NUMBERFORMAT_OP_GE; } else { eState = SsStop; eSymbolType = BRACKET_SYMBOLTYPE_ERROR; } break; case ' ': nPos--; rString.remove(nPos,1); nLen--; break; default: eState = SsStop; nPos--; break; } break; case SsGetPrefix: if (cToken == ']') { eState = SsStop; } else { sBuffSymbol.append(cToken); } break; default: break; } // of switch } // of while sSymbol = sBuffSymbol.makeStringAndClear(); return eSymbolType; } void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter, LanguageType eConvertFrom, LanguageType eConvertTo ) { sal_Int32 nCheckPos; sal_uInt32 nKey; SvNumFormatType nType = eType; OUString aFormatString( sFormatstring ); rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType, nKey, eConvertFrom, eConvertTo, false); const SvNumberformat* pFormat = rConverter.GetEntry( nKey ); DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" ); if ( pFormat ) { ImpCopyNumberformat( *pFormat ); // Reset values taken over from Formatter/Scanner // pColor still points to table in temporary Formatter/Scanner for (ImpSvNumFor & rFormatter : NumFor) { OUString aColorName = rFormatter.GetColorName(); const Color* pColor = rScan.GetColor( aColorName ); rFormatter.SetColor( pColor, aColorName ); } } } bool SvNumberformat::HasNewCurrency() const { for (const auto & j : NumFor) { if ( j.HasNewCurrency() ) { return true; } } return false; } bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol, OUString& rExtension ) const { for (const auto & j : NumFor) { if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) ) { return true; } } rSymbol.clear(); rExtension.clear(); return false; } // static OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr ) { OUStringBuffer aTmp(rStr.getLength()); sal_Int32 nStartPos, nPos, nLen; nLen = rStr.getLength(); nStartPos = 0; while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 ) { sal_Int32 nEnd; if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 ) { aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos )); nStartPos = nEnd; } else { aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); nStartPos = nPos + 2; sal_Int32 nDash; nEnd = nStartPos - 1; do { nDash = rStr.indexOf( '-', ++nEnd ); nEnd = GetQuoteEnd( rStr, nDash ); } while ( nEnd >= 0 ); sal_Int32 nClose; nEnd = nStartPos - 1; do { nClose = rStr.indexOf( ']', ++nEnd ); nEnd = GetQuoteEnd( rStr, nClose ); } while ( nEnd >= 0 ); if(nClose < 0) { /* there should always be a closing ] * but the old String class would have hidden * that. so be conservative too */ nClose = nLen; } nPos = nClose; if(nDash >= 0 && nDash < nClose) { nPos = nDash; } aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); nStartPos = nClose + 1; } } if ( nLen > nStartPos ) { aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) ); } return aTmp.makeStringAndClear(); } void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString) const { OUString sTemp; ImpGetOutputStandard(fNumber, sTemp); rOutString = sTemp; } void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString) const { sal_uInt16 nStandardPrec = rScan.GetStandardPrec(); if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND ) { nStandardPrec = ::std::min(nStandardPrec, static_cast(14)); // limits to 14 decimals rOutString = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_E2, nStandardPrec /*2*/, GetFormatter().GetNumDecimalSep()[0]); } else { ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec); } } namespace { template bool checkForAll0s(const T& rString, sal_Int32 nIdx=0) { if (nIdx>=rString.getLength()) return false; do { if (rString[nIdx]!='0') return false; } while (++nIdx(fNumber == fLimit); case NUMBERFORMAT_OP_NE: return static_cast(fNumber != fLimit); case NUMBERFORMAT_OP_LT: return static_cast(fNumber < fLimit); case NUMBERFORMAT_OP_LE: return static_cast(fNumber <= fLimit); case NUMBERFORMAT_OP_GT: return static_cast(fNumber > fLimit); case NUMBERFORMAT_OP_GE: return static_cast(fNumber >= fLimit); default: return -1; } } static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, std::u16string_view rStr ) { // Right during user input the star symbol is the very // last character before the user enters another one. if (rStr.size() > 1) { rBuf.append(u'\x001B'); rBuf.append(rStr[1]); return true; } return false; } static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, std::u16string_view rStr ) { if (rStr.size() > 1) { rBuf.insert( nPos, rStr[1]); rBuf.insert( nPos, u'\x001B'); return true; } return false; } void SvNumberformat::GetOutputString(std::u16string_view sString, OUString& OutString, const Color** ppColor) { OUStringBuffer sOutBuff; sal_uInt16 nIx; if (eType & SvNumFormatType::TEXT) { nIx = 0; } else if (NumFor[3].GetCount() > 0) { nIx = 3; } else { *ppColor = nullptr; // no change of color return; } *ppColor = NumFor[nIx].GetColor(); const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); if (rInfo.eScannedType == SvNumFormatType::TEXT) { const sal_uInt16 nCnt = NumFor[nIx].GetCount(); for (sal_uInt16 i = 0; i < nCnt; i++) { switch (rInfo.nTypeArray[i]) { case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[i].getLength() >= 2) InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] ); break; case NF_KEY_GENERAL : // #77026# "General" is the same as "@" case NF_SYMBOLTYPE_DEL : sOutBuff.append(sString); break; default: sOutBuff.append(rInfo.sStrArray[i]); } } } OutString = sOutBuff.makeStringAndClear(); } namespace { void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount, const SvNumberFormatter& rFormatter, OUString& rOutString) { bool bSign = std::signbit(fNumber); // 1.000E+015 (one digit and the decimal point, and the two chars + // nExpDigit for the exponential part, totalling 6 or 7). double fExp = log10( fabs(fNumber) ); if( fExp < 0.0 ) fExp = 1.0 - fExp; sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0); sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0; if (nPrec && bSign) { // Make room for the negative sign. --nPrec; } nPrec = ::std::min(nPrec, static_cast(14)); // limit to 14 decimals. rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2, nPrec, rFormatter.GetNumDecimalSep()[0], true ); } OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) { sal_Int32 i; OUStringBuffer aPercentString; for( i = 0; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT ) { aPercentString.append( rInfo.sStrArray[i] ); bool bStringFound = false; for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- ) { if( !bStringFound ) { bStringFound = true; aPercentString.insert( 0, "\"" ); } aPercentString.insert( 0, rInfo.sStrArray[i] ); } i = nCnt; if( bStringFound ) aPercentString.insert( 0, "\"" ); } } return aPercentString.makeStringAndClear(); } OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) { sal_Int32 i; OUStringBuffer aDenominatorString; for( i = 0; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) { while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT ); for( ; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ) aDenominatorString.append( rInfo.sStrArray[i] ); else i = nCnt; } } } return aDenominatorString.makeStringAndClear(); } OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) { sal_Int32 i; OUStringBuffer aNumeratorString; for( i = 0; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) { for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- ) { aNumeratorString.insert( 0, rInfo.sStrArray[i] ); } i = nCnt; } } return aNumeratorString.makeStringAndClear(); } OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) { sal_Int32 i; OUStringBuffer aIntegerString; for( i = 0; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) { for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- ) { aIntegerString.insert( 0, rInfo.sStrArray[i] ); } i = nCnt; } } return aIntegerString.makeStringAndClear(); } OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) { sal_uInt16 i; for( i = 0; i < nCnt; i++ ) { if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) { return rInfo.sStrArray[i]; } } return OUString(); } } OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const { const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); return lcl_GetPercentString( rInfo, nCnt ); } OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const { const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); return lcl_GetDenominatorString( rInfo, nCnt ); } OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const { const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); return lcl_GetNumeratorString( rInfo, nCnt ); } OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const { const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt ); } bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString) const { if (eType != SvNumFormatType::NUMBER) { return false; } double fTestNum = fNumber; bool bSign = std::signbit(fTestNum); if (bSign) { fTestNum = -fTestNum; } if (fTestNum < EXP_LOWER_BOUND) { lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); return true; } double fExp = log10(fTestNum); // Values < 1.0 always have one digit before the decimal point. sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast(ceil(fExp)) : 1; if (nDigitPre > 15) { lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); return true; } sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0; if (nPrec && bSign) { // Subtract the negative sign. --nPrec; } if (nPrec) { // Subtract the decimal point. --nPrec; } ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec); if (rOutString.getLength() > nCharCount) { // String still wider than desired. Switch to scientific notation. lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); } return true; } sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const { sal_uInt16 nIx; // Index of the partial format double fLimit_1 = fLimit1; short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1); if (nCheck == -1 || nCheck == 1) // Only 1 String or True { nIx = 0; } else { double fLimit_2 = fLimit2; nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2); if (nCheck == -1 || nCheck == 1) { nIx = 1; } else { nIx = 2; } } return nIx; } bool SvNumberformat::GetOutputString(double fNumber, OUString& OutString, const Color** ppColor) { bool bRes = false; OutString.clear(); *ppColor = nullptr; // No color change if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN]) { if (fNumber) { OutString = rScan.GetTrueString(); } else { OutString = rScan.GetFalseString(); } return false; } OUStringBuffer sBuff(64); if (eType & SvNumFormatType::TEXT) { ImpGetOutputStandard(fNumber, sBuff); OutString = sBuff.makeStringAndClear(); return false; } bool bHadStandard = false; if (bStandard) // Individual standard formats { if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine { ImpGetOutputInputLine(fNumber, OutString); return false; } switch (eType) { case SvNumFormatType::NUMBER: // Standard number format if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION) { if (std::signbit(fNumber)) { if (!(fNumber < 0.0)) fNumber = -fNumber; // do not display -0.0 } if (fNumber == 0.0) { OutString = "0"; } else if (fNumber < 1.0 && fNumber > -1.0) { // Decide whether to display as 0.000000123... or 1.23...e-07 bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber); if (!bFix) { // Arbitrary, not too many 0s visually, start E2 at 1E-10. constexpr sal_Int32 kMaxExp = 9; const sal_Int32 nExp = static_cast(ceil( -log10( fabs( fNumber)))); if (nExp <= kMaxExp && rtl::math::approxEqual( rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16))) { // Not too many significant digits or accuracy // artefacts, otherwise leave everything to E2 // format. bFix = true; } } if (bFix) OutString = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rtl_math_DecimalPlaces_Max, GetFormatter().GetNumDecimalSep()[0], true); else OutString = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_E2, rtl_math_DecimalPlaces_Max, GetFormatter().GetNumDecimalSep()[0], true); } else { OutString = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, GetFormatter().GetNumDecimalSep()[0], true); } return false; } ImpGetOutputStandard(fNumber, sBuff); bHadStandard = true; break; case SvNumFormatType::DATE: bRes |= ImpGetDateOutput(fNumber, 0, sBuff); bHadStandard = true; break; case SvNumFormatType::TIME: bRes |= ImpGetTimeOutput(fNumber, 0, sBuff); bHadStandard = true; break; case SvNumFormatType::DATETIME: bRes |= ImpGetDateTimeOutput(fNumber, 0, sBuff); bHadStandard = true; break; default: break; } } if ( !bHadStandard ) { sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format if (fNumber < 0.0 && ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat { fNumber = -fNumber; // eliminate sign } *ppColor = NumFor[nIx].GetColor(); const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); const sal_uInt16 nCnt = NumFor[nIx].GetCount(); if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY) { return false; // Empty => nothing } else if (nCnt == 0) // Else Standard Format { ImpGetOutputStandard(fNumber, sBuff); OutString = sBuff.makeStringAndClear(); return false; } switch (rInfo.eScannedType) { case SvNumFormatType::TEXT: case SvNumFormatType::DEFINED: for (sal_uInt16 i = 0; i < nCnt; i++) { switch (rInfo.nTypeArray[i]) { case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[i].getLength() >= 2) InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: sBuff.append(rInfo.sStrArray[i]); break; case NF_SYMBOLTYPE_THSEP: if (rInfo.nThousand == 0) { sBuff.append(rInfo.sStrArray[i]); } break; default: break; } } break; case SvNumFormatType::DATE: bRes |= ImpGetDateOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::TIME: bRes |= ImpGetTimeOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::DATETIME: bRes |= ImpGetDateTimeOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::NUMBER: case SvNumFormatType::PERCENT: case SvNumFormatType::CURRENCY: bRes |= ImpGetNumberOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::LOGICAL: bRes |= ImpGetLogicalOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::FRACTION: bRes |= ImpGetFractionOutput(fNumber, nIx, sBuff); break; case SvNumFormatType::SCIENTIFIC: bRes |= ImpGetScientificOutput(fNumber, nIx, sBuff); break; default: break; } } OutString = sBuff.makeStringAndClear(); return bRes; } bool SvNumberformat::ImpGetScientificOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sStr) { bool bRes = false; bool bSign = false; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); const sal_uInt16 nCnt = NumFor[nIx].GetCount(); if (fNumber < 0) { if (nIx == 0) // Not in the ones at the end { bSign = true; // Formats } fNumber = -fNumber; } sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_E, rInfo.nCntPre + rInfo.nCntPost - 1, '.' ); OUStringBuffer ExpStr; short nExpSign = 1; sal_Int32 nExPos = sStr.indexOf('E'); sal_Int32 nDecPos = -1; if ( nExPos >= 0 ) { // split into mantissa and exponent and get rid of "E+" or "E-" sal_Int32 nExpStart = nExPos + 1; switch ( sStr[ nExpStart ] ) { case '-' : nExpSign = -1; [[fallthrough]]; case '+' : ++nExpStart; break; } ExpStr = sStr.subView( nExpStart ); // part following the "E+" sStr.truncate( nExPos ); if ( rInfo.nCntPre != 1 ) // rescale Exp { sal_Int32 nExp = OUString::unacquired(ExpStr).toInt32() * nExpSign; sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast(rInfo.nCntPre) : -1; if( nRescale < 0 && rInfo.nCntPre != 0 ) nRescale += static_cast(rInfo.nCntPre); nExp -= nRescale; if ( nExp < 0 ) { nExpSign = -1; nExp = -nExp; } else { nExpSign = 1; } ExpStr = OUString::number( nExp ); const sal_Unicode cFirstDigit = sStr[0]; // rescale mantissa sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_E, nRescale + rInfo.nCntPost, '.' ); // sStr now may contain a rounded-up value shifted into the next // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995 // (9.9995E+02 rounded to 3 decimals) but we want the final result // to be 100.00E+00 (5 digits), so for the following fill routines // below to work correctly append a zero decimal. /* TODO: this is awkward, could an engineering notation mode be * introduced to rtl_math_doubleToUString()? */ sStr.truncate( sStr.indexOf('E') ); if (sStr[0] == '1' && cFirstDigit != '1') sStr.append('0'); } // cut any decimal delimiter sal_Int32 index = 0; while((index = sStr.indexOf('.', index)) >= 0) { if (nDecPos < 0) nDecPos = index; sStr.remove(index, 1); } } sal_uInt16 j = nCnt-1; // Last symbol sal_Int32 k = ExpStr.getLength() - 1; // Position in ExpStr sal_Int32 nZeros = 0; // Erase leading zeros // erase all leading zeros except last one while (nZeros < k && ExpStr[nZeros] == '0') { ++nZeros; } if (nZeros) { ExpStr.remove( 0, nZeros); } // restore leading zeros or blanks according to format '0' or '?' tdf#156449 bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP); bool bCont = true; if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP) { const OUString& rStr = rInfo.sStrArray[j]; if (nExpSign == -1) { ExpStr.insert(0, '-'); } else if (rStr.getLength() > 1 && rStr[1] == '+') { ExpStr.insert(0, '+'); } ExpStr.insert(0, rStr[0]); if ( j ) { j--; } else { bCont = false; } } // Continue main number: if ( !bCont ) { sStr.truncate(); } else { bRes |= ImpDecimalFill(sStr, fNumber, nDecPos, j, nIx, false); } if (bSign) { sStr.insert(0, '-'); } sStr.append(ExpStr); return bRes; } double SvNumberformat::GetRoundFractionValue ( double fNumber ) const { sal_uInt16 nIx = GetSubformatIndex ( fNumber ); double fIntPart = 0.0; // integer part of fraction sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator double fSign = (fNumber < 0.0) ? -1.0 : 1.0; // fNumber is modified in ImpGetFractionElements to absolute fractional part ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv ); if ( nDiv > 0 ) return fSign * ( fIntPart + static_cast(nFrac) / static_cast(nDiv) ); else return fSign * fIntPart; } void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx, double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const { if ( fNumber < 0.0 ) fNumber = -fNumber; fIntPart = floor(fNumber); // Integral part fNumber -= fIntPart; // Fractional part const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); sal_Int64 nForcedDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32(); if( nForcedDiv > 0 ) { // Forced Denominator nDiv = nForcedDiv; nFrac = static_cast(floor ( fNumber * nDiv )); double fFracNew = static_cast(nFrac) / static_cast(nDiv); double fFracNew1 = static_cast(nFrac + 1) / static_cast(nDiv); double fDiff = fNumber - fFracNew; if( fDiff > ( fFracNew1 - fNumber ) ) { nFrac++; } } else // Calculated Denominator { nDiv = 1; sal_Int64 nBasis = static_cast(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,... sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom; double fRemainder = fNumber; // Use continued fraction representation of fNumber // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations while ( fRemainder > 0.0 ) { double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak nPartialDenom = static_cast(floor(fTemp)); // due to floating point notation with double precision fRemainder = fTemp - static_cast(nPartialDenom); nDivNext = nPartialDenom * nDiv + nDivPrev; if ( nDivNext <= nBasis ) // continue loop { nFracNext = nPartialDenom * nFrac + nFracPrev; nFracPrev = nFrac; nFrac = nFracNext; nDivPrev = nDiv; nDiv = nDivNext; } else // calculate collateral fraction and exit { sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv; if ( 2 * nCollat >= nPartialDenom ) { sal_Int64 nFracTest = nCollat * nFrac + nFracPrev; sal_Int64 nDivTest = nCollat * nDiv + nDivPrev; double fSign = (static_cast(nFrac) > fNumber * static_cast(nDiv))?1.0:-1.0; if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 ) { nFrac = nFracTest; nDiv = nDivTest; } } fRemainder = 0.0; // exit while loop } } } if (nFrac >= nDiv) { ++fIntPart; nFrac = 0; nDiv = ( nForcedDiv > 0 ) ? nForcedDiv : 1; } } bool SvNumberformat::ImpGetFractionOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) { bool bRes = false; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); const sal_uInt16 nCnt = NumFor[nIx].GetCount(); OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt); const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt); const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt); sal_Int64 nFrac = 0, nDiv = 1; double fNum = floor(fNumber); // Integral part if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large { sBuff = ImpSvNumberformatScan::sErrStr; return false; } if (rInfo.nCntExp == 0) { SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0"); sBuff.truncate(); return false; } ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv); if (rInfo.nCntPre == 0) // Improper fraction { double fNum1 = fNum * static_cast(nDiv) + static_cast(nFrac); if (fNum1 > D_MAX_INTEGER) { sBuff = ImpSvNumberformatScan::sErrStr; return false; } nFrac = static_cast(floor(fNum1)); } else if (fNum == 0.0 && nFrac != 0) { } else { char aBuf[100]; o3tl::sprintf( aBuf, "%.f", fNum ); // simple rounded integer sStr.appendAscii( aBuf ); impTransliterate(sStr, NumFor[nIx].GetNatNum()); } bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0 && (sNumeratorFormat.indexOf('0') < 0) && (sDenominatorFormat.indexOf('0') < 0 || sDenominatorFormat.toInt32() > 0) ); if ( bHideFraction ) { sDiv.truncate(); } else // if there are some '0' in format, force display of fraction { sFrac = ImpIntToString( nIx, nFrac ); sDiv = ImpIntToString( nIx, nDiv ); } sal_uInt16 j = nCnt-1; // Last symbol -> backwards sal_Int32 k; // Denominator bRes |= ImpNumberFill(sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, true); bool bCont = true; if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC) { if ( bHideFraction ) { // do not insert blank for fraction if there is no '?' if ( sNumeratorFormat.indexOf('?') >= 0 || sDenominatorFormat.indexOf('?') >= 0 ) sDiv.insert(0, ' '); } else { sDiv.insert(0, rInfo.sStrArray[j][0]); } if ( j ) { j--; } else { bCont = false; } } // Further numerators: if ( !bCont ) { sFrac.truncate(); } else { bRes |= ImpNumberFill(sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK); bCont = false; // there is no integer part? if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK) { if ( j ) { if ( bHideFraction ) { // '?' in any format force display of blank as delimiter if ( sIntegerFormat.indexOf('?') >= 0 || sNumeratorFormat.indexOf('?') >= 0 || sDenominatorFormat.indexOf('?') >= 0 ) { for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) sFrac.insert(0, ' '); } } else { if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 ) sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction else { if ( sIntegerFormat.indexOf('?') >= 0 || sNumeratorFormat.indexOf('?') >= 0 ) { for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) sFrac.insert(0, ' '); } } } j--; bCont = true; // Yes, there is an integer } else sFrac.insert(0, rInfo.sStrArray[j]); } } // Continue integer part if ( !bCont ) { sStr.truncate(); } else { k = sStr.getLength(); // After last figure bRes |= ImpNumberFillWithThousands(sStr, fNumber, k, j, nIx, rInfo.nCntPre); } if (bSign && (nFrac != 0 || fNum != 0.0)) { sBuff.insert(0, '-'); // Not -0 } sBuff.append(sStr); sBuff.append(sFrac); sBuff.append(sDiv); return bRes; } sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond, int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals ) { if (!nFractionDecimals) return 0; // nFractionDecimals+1 to not round up what Time::GetClock() carefully // truncated. rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F, (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.')); rBuf.stripStart('0'); rBuf.stripStart('.'); if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals) rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1 if (nMinimumInputLineDecimals) { rBuf.stripEnd('0'); for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index) { rBuf.append('0'); } impTransliterate(rBuf, NumFor[nIx].GetNatNum()); nFractionDecimals = rBuf.getLength(); } else { impTransliterate(rBuf, NumFor[nIx].GetNatNum()); } return static_cast(nFractionDecimals); } bool SvNumberformat::ImpGetTimeOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) { using namespace ::com::sun::star::i18n; bool bCalendarSet = false; const double fNumberOrig = fNumber; bool bRes = false; bool bSign = false; if (fNumber < 0.0) { fNumber = -fNumber; if (nIx == 0) { bSign = true; } } const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); bool bInputLine; sal_Int32 nCntPost; if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound ) { bInputLine = true; nCntPost = kTimeSignificantRound; } else { bInputLine = false; nCntPost = rInfo.nCntPost; } OUStringBuffer sSecStr; sal_Int32 nSecPos = 0; // For figure by figure processing sal_uInt32 nHour, nMin, nSec; if (!rInfo.bThousand) // No [] format { sal_uInt16 nCHour, nCMinute, nCSecond; double fFractionOfSecond; tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); nHour = nCHour; nMin = nCMinute; nSec = nCSecond; nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, (bInputLine ? rInfo.nCntPost : 0)); } else { const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost)); if (bSign && fTime == 0.0) { bSign = false; // Not -00:00:00 } if (fTime > D_MAX_U_INT32) { sBuff = ImpSvNumberformatScan::sErrStr; return false; } sal_uInt32 nSeconds = static_cast(fTime); nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, (bInputLine ? rInfo.nCntPost : 0)); if (rInfo.nThousand == 3) // [ss] { nHour = 0; nMin = 0; nSec = nSeconds; } else if (rInfo.nThousand == 2) // [mm]:ss { nHour = 0; nMin = nSeconds / 60; nSec = nSeconds % 60; } else if (rInfo.nThousand == 1) // [hh]:mm:ss { nHour = nSeconds / 3600; nMin = (nSeconds%3600) / 60; nSec = nSeconds%60; } else { // TODO What should these be set to? nHour = 0; nMin = 0; nSec = 0; } } sal_Unicode cAmPm = ' '; // a or p if (rInfo.nCntExp) // AM/PM { if (nHour == 0) { nHour = 12; cAmPm = 'a'; } else if (nHour < 12) { cAmPm = 'a'; } else { cAmPm = 'p'; if (nHour > 12) { nHour -= 12; } } } const sal_uInt16 nCnt = NumFor[nIx].GetCount(); for (sal_uInt16 i = 0; i < nCnt; i++) { sal_Int32 nLen; switch (rInfo.nTypeArray[i]) { case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[i].getLength() >= 2) InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: sBuff.append(rInfo.sStrArray[i]); break; case NF_SYMBOLTYPE_DIGIT: nLen = ( bInputLine && i > 0 && (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? nCntPost : rInfo.sStrArray[i].getLength() ); for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) { sBuff.append(sSecStr[nSecPos]); nSecPos++; } break; case NF_KEY_AMPM: // AM/PM if ( !bCalendarSet ) { double fDiff = DateTime::Sub( DateTime(rScan.GetNullDate()), GetCal().getEpochStart()); fDiff += fNumberOrig; GetCal().setLocalDateTime( fDiff ); bCalendarSet = true; } if (cAmPm == 'a') { sBuff.append(GetCal().getDisplayName( CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 )); } else { sBuff.append(GetCal().getDisplayName( CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 )); } break; case NF_KEY_AP: // A/P if (cAmPm == 'a') { sBuff.append('a'); } else { sBuff.append('p'); } break; case NF_KEY_MI: // M sBuff.append(ImpIntToString( nIx, nMin )); break; case NF_KEY_MMI: // MM sBuff.append(ImpIntToString( nIx, nMin, 2 )); break; case NF_KEY_H: // H sBuff.append(ImpIntToString( nIx, nHour )); break; case NF_KEY_HH: // HH sBuff.append(ImpIntToString( nIx, nHour, 2 )); break; case NF_KEY_S: // S sBuff.append(ImpIntToString( nIx, nSec )); break; case NF_KEY_SS: // SS sBuff.append(ImpIntToString( nIx, nSec, 2 )); break; default: break; } } if (bSign && rInfo.bThousand) { sBuff.insert(0, '-'); } return bRes; } /** If a day of month occurs within the format, the month name is in possessive genitive case if the day follows the month, and partitive case if the day precedes the month. If there is no day of month the nominative case (noun) is returned. Also if the month is immediately preceded or followed by a literal string other than space and not followed by a comma, the nominative name is used, this prevents duplicated casing for MMMM\t\a and such in documents imported from (e.g. Finnish) Excel or older LibO/OOo releases. */ // IDEA: instead of eCodeType pass the index to nTypeArray and restrict // inspection of month name around that one, that would enable different month // cases in one format. Though probably the most rare use case ever... sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType ) { using namespace ::com::sun::star::i18n; if (!io_nState) { bool bMonthSeen = false; bool bDaySeen = false; const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); const sal_uInt16 nCount = rNumFor.GetCount(); for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i) { sal_Int32 nLen; switch (rInfo.nTypeArray[i]) { case NF_KEY_D : case NF_KEY_DD : if (bMonthSeen) { io_nState = 2; } else { bDaySeen = true; } break; case NF_KEY_MMM: case NF_KEY_MMMM: case NF_KEY_MMMMM: if ((i < nCount-1 && rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && // Literal following, not empty, space nor comma. !rInfo.sStrArray[i+1].isEmpty() && rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') || (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING && ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) && // Literal preceding, not space. rInfo.sStrArray[i-1][nLen-1] != ' ')) { io_nState = 1; } else if (bDaySeen) { io_nState = 3; } else { bMonthSeen = true; } break; } } if (io_nState == 0) { io_nState = 1; // No day of month } } switch (io_nState) { case 1: // No day of month or forced nominative switch (eCodeType) { case NF_KEY_MMM: return CalendarDisplayCode::SHORT_MONTH_NAME; case NF_KEY_MMMM: return CalendarDisplayCode::LONG_MONTH_NAME; case NF_KEY_MMMMM: return CalendarDisplayCode::NARROW_MONTH_NAME; default: ; // nothing } break; case 2: // Day of month follows month (the month's 17th) switch (eCodeType) { case NF_KEY_MMM: return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME; case NF_KEY_MMMM: return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME; case NF_KEY_MMMMM: return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME; default: ; // Nothing } break; case 3: // Day of month precedes month (17 of month) switch (eCodeType) { case NF_KEY_MMM: return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME; case NF_KEY_MMMM: return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME; case NF_KEY_MMMMM: return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME; default: ; // nothing } break; } SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType"); return CalendarDisplayCode::LONG_MONTH_NAME; } bool SvNumberformat::ImpIsOtherCalendar( const ImpSvNumFor& rNumFor ) const { if ( GetCal().getUniqueID() != GREGORIAN ) { return false; } const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); const sal_uInt16 nCnt = rNumFor.GetCount(); sal_uInt16 i; for ( i = 0; i < nCnt; i++ ) { switch ( rInfo.nTypeArray[i] ) { case NF_SYMBOLTYPE_CALENDAR : return false; case NF_KEY_EC : case NF_KEY_EEC : case NF_KEY_R : case NF_KEY_RR : case NF_KEY_AAA : case NF_KEY_AAAA : case NF_KEY_G : case NF_KEY_GG : case NF_KEY_GGG : return true; } } return false; } void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar, double& fOrgDateTime ) const { CalendarWrapper& rCal = GetCal(); if ( rCal.getUniqueID() != GREGORIAN ) return; using namespace ::com::sun::star::i18n; const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars( rLoc().getLanguageTag().getLocale() ); sal_Int32 nCnt = xCals.getLength(); if ( nCnt <= 1 ) return; auto pCal = std::find_if(xCals.begin(), xCals.end(), [](const OUString& rCalName) { return rCalName != GREGORIAN; }); if (pCal == xCals.end()) return; if ( !rOrgCalendar.getLength() ) { rOrgCalendar = rCal.getUniqueID(); fOrgDateTime = rCal.getDateTime(); } rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() ); rCal.setDateTime( fOrgDateTime ); } void SvNumberformat::SwitchToGregorianCalendar( std::u16string_view rOrgCalendar, double fOrgDateTime ) const { CalendarWrapper& rCal = GetCal(); if ( rOrgCalendar.size() && rCal.getUniqueID() != GREGORIAN ) { rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); rCal.setDateTime( fOrgDateTime ); } } bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar, double& fOrgDateTime ) { using namespace ::com::sun::star::i18n; CalendarWrapper& rCal = GetCal(); if ( rCal.getUniqueID() != GREGORIAN ) { sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" ) { if ( !rOrgCalendar.getLength() ) { rOrgCalendar = rCal.getUniqueID(); fOrgDateTime = rCal.getDateTime(); } else if ( rOrgCalendar == GREGORIAN ) { rOrgCalendar.clear(); } rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); rCal.setDateTime( fOrgDateTime ); return true; } } return false; } #ifdef THE_FUTURE /* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently * unused please don't remove it, it would be needed by * SwitchToSpecifiedCalendar(), see comment in * ImpSvNumberInputScan::GetDateRef() */ bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar, double& fOrgDateTime, const ImpSvNumFor& rNumFor ) const { const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); const sal_uInt16 nCnt = rNumFor.GetCount(); for ( sal_uInt16 i = 0; i < nCnt; i++ ) { if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR ) { CalendarWrapper& rCal = GetCal(); if ( !rOrgCalendar.getLength() ) { rOrgCalendar = rCal.getUniqueID(); fOrgDateTime = rCal.getDateTime(); } rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() ); rCal.setDateTime( fOrgDateTime ); return true; } } return false; } #endif // static void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString, const CalendarWrapper& rCal, sal_Int16 nNatNum ) { using namespace ::com::sun::star::i18n; if ( rCal.getUniqueID() == "gengou" ) { sal_Unicode cEra; sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); switch ( nVal ) { case 1: cEra = 'M'; break; case 2: cEra = 'T'; break; case 3: cEra = 'S'; break; case 4: cEra = 'H'; break; case 5: cEra = 'R'; break; default: cEra = '?'; break; } OutString.append(cEra); } else { OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); } } bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const { bool bIsIso = false; if (eType & SvNumFormatType::DATE) { enum State { eNone, eAtYear, eAtSep1, eAtMonth, eAtSep2, eNotIso }; State eState = eNone; auto & rTypeArray = rNumFor.Info().nTypeArray; sal_uInt16 nCnt = rNumFor.GetCount(); for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i) { switch ( rTypeArray[i] ) { case NF_KEY_YY: // two digits not strictly ISO 8601 case NF_KEY_YYYY: if (eState != eNone) { eState = eNotIso; } else { eState = eAtYear; } break; case NF_KEY_M: // single digit not strictly ISO 8601 case NF_KEY_MM: if (eState != eAtSep1) { eState = eNotIso; } else { eState = eAtMonth; } break; case NF_KEY_D: // single digit not strictly ISO 8601 case NF_KEY_DD: if (eState != eAtSep2) { eState = eNotIso; } else { bIsIso = true; } break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_DATESEP: if (rNumFor.Info().sStrArray[i] == "-") { if (eState == eAtYear) { eState = eAtSep1; } else if (eState == eAtMonth) { eState = eAtSep2; } else { eState = eNotIso; } } else { eState = eNotIso; } break; default: eState = eNotIso; } } } else { SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" ); } return bIsIso; } static bool lcl_hasEra( const ImpSvNumFor& rNumFor ) { const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); const sal_uInt16 nCnt = rNumFor.GetCount(); for ( sal_uInt16 i = 0; i < nCnt; i++ ) { switch ( rInfo.nTypeArray[i] ) { case NF_KEY_RR : case NF_KEY_G : case NF_KEY_GG : case NF_KEY_GGG : return true; } } return false; } static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor ) { return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 && rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor ); } /* XXX: if needed this could be stripped from rEpochStart and diff adding and * moved to tools' DateTime to be reused elsewhere. */ static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber ) { static const DateTime aCE( Date(1,1,1)); static const DateTime aMin( Date(1,1, SAL_MIN_INT16)); static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1)); static const double fMin = DateTime::Sub( aMin, aCE); static const double fMax = DateTime::Sub( aMax, aCE); // Value must be representable in our tools::Date proleptic Gregorian // calendar as well. const double fOff = DateTime::Sub( rNullDate, aCE) + fNumber; // Add diff between epochs to serial date number. const double fDiff = DateTime::Sub( rNullDate, rEpochStart); fNumber += fDiff; return fMin <= fOff && fOff <= fMax; } bool SvNumberformat::ImpGetDateOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) { using namespace ::com::sun::star::i18n; bool bRes = false; CalendarWrapper& rCal = GetCal(); if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) { sBuff = ImpSvNumberformatScan::sErrStr; return false; } rCal.setLocalDateTime( fNumber ); int nUseMonthCase = 0; // Not decided yet OUString aOrgCalendar; // empty => not changed yet double fOrgDateTime(0.0); bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) { bOtherCalendar = false; } const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); const sal_uInt16 nCnt = NumFor[nIx].GetCount(); sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); OUString aStr; // NatNum12: if the date format contains more than a date // field, it needs to specify in NatNum12 argument // which date element needs special formatting: // // '[NatNum12 ordinal-number]D' -> "1st" // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April" // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April" // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety" // // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats. // Additionally for MMMMM, MMM, DDD and NN/AA to support at least // capitalize, upper, lower, title. // XXX It's possible to extend this for other keywords and date + time // combinations, as required. bool bUseSpellout = NatNumTakesParameters(nNatNum) && (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1); for (sal_uInt16 i = 0; i < nCnt; i++) { switch (rInfo.nTypeArray[i]) { case NF_SYMBOLTYPE_CALENDAR : if ( !aOrgCalendar.getLength() ) { aOrgCalendar = rCal.getUniqueID(); fOrgDateTime = rCal.getDateTime(); } rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); rCal.setDateTime( fOrgDateTime ); ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); break; case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[i].getLength() >= 2) InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: sBuff.append(rInfo.sStrArray[i]); break; case NF_KEY_M: // M aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum ); // NatNum12: support variants of preposition, suffixation or article // for example, Catalan "de març", but "d'abril" etc. if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); break; case NF_KEY_MM: // MM sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum )); break; case NF_KEY_MMM: // MMM case NF_KEY_MMMM: // MMMM case NF_KEY_MMMMM: // MMMMM // NatNum12: support variants of preposition, suffixation or // article, or capitalize, upper, lower, title. // Note: result of the "spell out" conversion can depend from the optional // PartitiveMonths or GenitiveMonths defined in the locale data, // see description of ImpUseMonthCase(), and locale data in // i18npool/source/localedata/data/ and libnumbertext aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], static_cast(rInfo.nTypeArray[i])), nNatNum); if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); break; case NF_KEY_Q: // Q sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); break; case NF_KEY_QQ: // QQ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); break; case NF_KEY_D: // D aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ); // NatNum12: support variants of preposition, suffixation or article if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); break; case NF_KEY_DD: // DD sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); break; case NF_KEY_DDD: // DDD if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); // NatNum12: support at least capitalize, upper, lower, title if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_DDDD: // DDDD if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); // NatNum12: support variants of preposition, suffixation or article if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_YY: // YY if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } // Prepend a minus sign if Gregorian BCE and era is not displayed. if (lcl_isSignedYear( rCal, NumFor[nIx] )) { sBuff.append('-'); } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_YYYY: // YYYY if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } // Prepend a minus sign if Gregorian BCE and era is not displayed. if (lcl_isSignedYear( rCal, NumFor[nIx] )) { sBuff.append('-'); } aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) { using namespace comphelper::string; // Ensure that year consists of at least 4 digits, so it // can be distinguished from 2 digits display and edited // without suddenly being hit by the 2-digit year magic. OUStringBuffer aBuf; padToLength(aBuf, 4 - aStr.getLength(), '0'); impTransliterate(aBuf, NumFor[nIx].GetNatNum()); aBuf.append(aStr); aStr = aBuf.makeStringAndClear(); } // NatNum12: support variants of preposition, suffixation or article if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_EC: // E sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); break; case NF_KEY_EEC: // EE case NF_KEY_R: // R sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); break; case NF_KEY_NN: // NN case NF_KEY_AAA: // AAA aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); // NatNum12: support at least capitalize, upper, lower, title if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); break; case NF_KEY_NNN: // NNN case NF_KEY_AAAA: // AAAA aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); // NatNum12: support variants of preposition, suffixation or article if ( bUseSpellout ) { aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); } sBuff.append(aStr); break; case NF_KEY_NNNN: // NNNN sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); sBuff.append(rLoc().getLongDateDayOfWeekSep()); break; case NF_KEY_WW : // WW sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); break; case NF_KEY_G: // G ImpAppendEraG(sBuff, rCal, nNatNum ); break; case NF_KEY_GG: // GG sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); break; case NF_KEY_GGG: // GGG sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); break; case NF_KEY_RR: // RR => GGGEE sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); break; } } if ( aOrgCalendar.getLength() ) { rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar } return bRes; } bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) { using namespace ::com::sun::star::i18n; bool bRes = false; CalendarWrapper& rCal = GetCal(); if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) { sBuff = ImpSvNumberformatScan::sErrStr; return false; } const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); bool bInputLine; sal_Int32 nCntPost, nFirstRounding; if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound ) { bInputLine = true; nCntPost = nFirstRounding = kTimeSignificantRound; } else { bInputLine = false; nCntPost = rInfo.nCntPost; // For clock format (not []) do not round up to seconds and thus days. nFirstRounding = (rInfo.bThousand ? nCntPost : kTimeSignificantRound); } double fTime = (fNumber - floor( fNumber )) * 86400.0; fTime = ::rtl::math::round( fTime, int(nFirstRounding) ); if (fTime >= 86400.0) { // result of fNumber==x.999999999... rounded up, use correct date/time fTime -= 86400.0; fNumber = floor( fNumber + 0.5) + fTime; } rCal.setLocalDateTime( fNumber ); int nUseMonthCase = 0; // Not decided yet OUString aOrgCalendar; // empty => not changed yet double fOrgDateTime(0.0); bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) { bOtherCalendar = false; } sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); OUStringBuffer sSecStr; sal_Int32 nSecPos = 0; // For figure by figure processing sal_uInt32 nHour, nMin, nSec; if (!rInfo.bThousand) // No [] format { sal_uInt16 nCHour, nCMinute, nCSecond; double fFractionOfSecond; tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); nHour = nCHour; nMin = nCMinute; nSec = nCSecond; nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, (bInputLine ? rInfo.nCntPost : 0)); } else { sal_uInt32 nSeconds = static_cast(floor( fTime )); nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, (bInputLine ? rInfo.nCntPost : 0)); if (rInfo.nThousand == 3) // [ss] { nHour = 0; nMin = 0; nSec = nSeconds; } else if (rInfo.nThousand == 2) // [mm]:ss { nHour = 0; nMin = nSeconds / 60; nSec = nSeconds % 60; } else if (rInfo.nThousand == 1) // [hh]:mm:ss { nHour = nSeconds / 3600; nMin = (nSeconds%3600) / 60; nSec = nSeconds%60; } else { nHour = 0; // TODO What should these values be? nMin = 0; nSec = 0; } } sal_Unicode cAmPm = ' '; // a or p if (rInfo.nCntExp) // AM/PM { if (nHour == 0) { nHour = 12; cAmPm = 'a'; } else if (nHour < 12) { cAmPm = 'a'; } else { cAmPm = 'p'; if (nHour > 12) { nHour -= 12; } } } const sal_uInt16 nCnt = NumFor[nIx].GetCount(); sal_Int32 nLen; OUString aYear; for (sal_uInt16 i = 0; i < nCnt; i++) { switch (rInfo.nTypeArray[i]) { case NF_SYMBOLTYPE_CALENDAR : if ( !aOrgCalendar.getLength() ) { aOrgCalendar = rCal.getUniqueID(); fOrgDateTime = rCal.getDateTime(); } rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); rCal.setDateTime( fOrgDateTime ); ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); break; case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[i].getLength() >= 2) InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: sBuff.append(rInfo.sStrArray[i]); break; case NF_SYMBOLTYPE_DIGIT: nLen = ( bInputLine && i > 0 && (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? nCntPost : rInfo.sStrArray[i].getLength() ); for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) { sBuff.append(sSecStr[ nSecPos ]); nSecPos++; } break; case NF_KEY_AMPM: // AM/PM if (cAmPm == 'a') { sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 )); } else { sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 )); } break; case NF_KEY_AP: // A/P if (cAmPm == 'a') { sBuff.append('a'); } else { sBuff.append('p'); } break; case NF_KEY_MI: // M sBuff.append(ImpIntToString( nIx, nMin )); break; case NF_KEY_MMI: // MM sBuff.append(ImpIntToString( nIx, nMin, 2 )); break; case NF_KEY_H: // H sBuff.append(ImpIntToString( nIx, nHour )); break; case NF_KEY_HH: // HH sBuff.append(ImpIntToString( nIx, nHour, 2 )); break; case NF_KEY_S: // S sBuff.append(ImpIntToString( nIx, nSec )); break; case NF_KEY_SS: // SS sBuff.append(ImpIntToString( nIx, nSec, 2 )); break; case NF_KEY_M: // M sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum )); break; case NF_KEY_MM: // MM sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum )); break; case NF_KEY_MMM: // MMM sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], static_cast(rInfo.nTypeArray[i])), nNatNum)); break; case NF_KEY_MMMM: // MMMM sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], static_cast(rInfo.nTypeArray[i])), nNatNum)); break; case NF_KEY_MMMMM: // MMMMM sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], static_cast(rInfo.nTypeArray[i])), nNatNum)); break; case NF_KEY_Q: // Q sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); break; case NF_KEY_QQ: // QQ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); break; case NF_KEY_D: // D sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum )); break; case NF_KEY_DD: // DD sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); break; case NF_KEY_DDD: // DDD if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_DDDD: // DDDD if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_YY: // YY if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } // Prepend a minus sign if Gregorian BCE and era is not displayed. if (lcl_isSignedYear( rCal, NumFor[nIx] )) { sBuff.append('-'); } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_YYYY: // YYYY if ( bOtherCalendar ) { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } // Prepend a minus sign if Gregorian BCE and era is not displayed. if (lcl_isSignedYear( rCal, NumFor[nIx] )) { sBuff.append('-'); } aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) { using namespace comphelper::string; // Ensure that year consists of at least 4 digits, so it // can be distinguished from 2 digits display and edited // without suddenly being hit by the 2-digit year magic. OUStringBuffer aBuf; padToLength(aBuf, 4 - aYear.getLength(), '0'); impTransliterate(aBuf, NumFor[nIx].GetNatNum()); aBuf.append(aYear); sBuff.append(aBuf); } else { sBuff.append(aYear); } if ( bOtherCalendar ) { SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); } break; case NF_KEY_EC: // E sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); break; case NF_KEY_EEC: // EE case NF_KEY_R: // R sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); break; case NF_KEY_NN: // NN case NF_KEY_AAA: // AAA sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); break; case NF_KEY_NNN: // NNN case NF_KEY_AAAA: // AAAA sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); break; case NF_KEY_NNNN: // NNNN sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); sBuff.append(rLoc().getLongDateDayOfWeekSep()); break; case NF_KEY_WW : // WW sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); break; case NF_KEY_G: // G ImpAppendEraG( sBuff, rCal, nNatNum ); break; case NF_KEY_GG: // GG sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); break; case NF_KEY_GGG: // GGG sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); break; case NF_KEY_RR: // RR => GGGEE sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); break; } } if ( aOrgCalendar.getLength() ) { rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar } return bRes; } bool SvNumberformat::ImpGetLogicalOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sStr) { bool bRes = false; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); const sal_uInt16 nCnt = NumFor[nIx].GetCount(); for (sal_uInt16 j = 0; j < nCnt; ++j) { switch (rInfo.nTypeArray[j]) { case NF_KEY_BOOLEAN: sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString()); break; case NF_SYMBOLTYPE_STRING: sStr.append( rInfo.sStrArray[j]); break; } } impTransliterate(sStr, NumFor[nIx].GetNatNum()); return bRes; } bool SvNumberformat::ImpGetNumberOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sStr) { bool bRes = false; bool bSign; if (fNumber < 0.0) { bSign = (nIx == 0); // Not in the ones at the back; fNumber = -fNumber; } else { bSign = false; if ( std::signbit( fNumber ) ) { fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-' } } const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); if (rInfo.eScannedType == SvNumFormatType::PERCENT) { if (fNumber < D_MAX_D_BY_100) { fNumber *= 100.0; } else { sStr = ImpSvNumberformatScan::sErrStr; return false; } } sal_uInt16 i, j; sal_Int32 nDecPos = -1; bool bInteger = false; if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT ) { // Special formatting only if no GENERAL keyword in format code const sal_uInt16 nThousand = rInfo.nThousand; tools::Long nPrecExp; for (i = 0; i < nThousand; i++) { if (fNumber > D_MIN_M_BY_1000) { fNumber /= 1000.0; } else { fNumber = 0.0; } } if (fNumber > 0.0) { nPrecExp = GetPrecExp( fNumber ); } else { nPrecExp = 0; } if (rInfo.nCntPost) // Decimal places { if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15) { sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.'); for (tools::Long l = 15-nPrecExp; l < static_cast(rInfo.nCntPost); l++) { sStr.append('0'); } } else { sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' ); } sStr.stripStart('0'); // Strip leading zeros } else if (fNumber == 0.0) // Null { // Nothing to be done here, keep empty string sStr, // ImpNumberFillWithThousands does the rest } else // Integer { sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.'); sStr.stripStart('0'); // Strip leading zeros } nDecPos = sStr.indexOf('.' ); if ( nDecPos >= 0) { const sal_Unicode* p = sStr.getStr() + nDecPos; while ( *++p == '0' ) ; if ( !*p ) { bInteger = true; } sStr.remove( nDecPos, 1 ); // Remove . } if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000 { bSign = false; // Not -0.00 } } // End of != FLAG_STANDARD_IN_FORMAT // Edit backwards: j = NumFor[nIx].GetCount()-1; // Last symbol // Decimal places: bRes |= ImpDecimalFill( sStr, fNumber, nDecPos, j, nIx, bInteger ); if (bSign) { sStr.insert(0, '-'); } impTransliterate(sStr, NumFor[nIx].GetNatNum()); return bRes; } bool SvNumberformat::ImpDecimalFill( OUStringBuffer& sStr, // number string double& rNumber, // number sal_Int32 nDecPos, // decimals start sal_uInt16 j, // symbol index within format code sal_uInt16 nIx, // subformat index bool bInteger) // is integer { bool bRes = false; bool bFilled = false; // Was filled? const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); sal_Int32 k = sStr.getLength(); // After last figure // Decimal places: if (rInfo.nCntPost > 0) { bool bTrailing = true; // Trailing zeros? short nType; while (j > 0 && // Backwards (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP) { switch ( nType ) { case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[j].getLength() >= 2) /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] ); break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_PERCENT: sStr.insert(k, rInfo.sStrArray[j]); break; case NF_SYMBOLTYPE_THSEP: if (rInfo.nThousand == 0) { sStr.insert(k, rInfo.sStrArray[j]); } break; case NF_SYMBOLTYPE_DIGIT: { const OUString& rStr = rInfo.sStrArray[j]; const sal_Unicode* p1 = rStr.getStr(); const sal_Unicode* p = p1 + rStr.getLength(); // In case the number of decimals passed are less than the // "digits" given, append trailing '0' characters, which here // means insert them because literal strings may have been // appended already. If they weren't to be '0' characters // they'll be changed below, as if decimals with trailing zeros // were passed. if (nDecPos >= 0 && nDecPos <= k) { sal_Int32 nAppend = rStr.getLength() - (k - nDecPos); while (nAppend-- > 0) { sStr.insert( k++, '0'); } } while (k && p1 < p--) { const sal_Unicode c = *p; k--; if ( sStr[k] != '0' ) { bTrailing = false; bFilled = true; } if (bTrailing) { if ( c == '0' ) { bFilled = true; } else if ( c == '-' ) { if ( bInteger ) { sStr[ k ] = '-'; } bFilled = true; } else if ( c == '?' ) { sStr[ k ] = ' '; bFilled = true; } else if ( !bFilled ) // # { sStr.remove(k,1); } } } // of for break; } // of case digi case NF_KEY_CCC: // CCC currency sStr.insert(k, rScan.GetCurAbbrev()); break; case NF_KEY_GENERAL: // Standard in the String { OUStringBuffer sNum; ImpGetOutputStandard(rNumber, sNum); sNum.stripStart('-'); sStr.insert(k, sNum); break; } default: break; } // of switch j--; } // of while } // of decimal places bRes |= ImpNumberFillWithThousands(sStr, rNumber, k, j, nIx, // Fill with . if needed rInfo.nCntPre, bFilled ); return bRes; } bool SvNumberformat::ImpNumberFillWithThousands( OUStringBuffer& sBuff, // number string double& rNumber, // number sal_Int32 k, // position within string sal_uInt16 j, // symbol index within format code sal_uInt16 nIx, // subformat index sal_Int32 nDigCnt, // count of integer digits in format bool bAddDecSep) // add decimal separator if necessary { bool bRes = false; sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number sal_Int32 nDigitCount = 0; // count of integer digits from the right bool bStop = false; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); // no normal thousands separators if number divided by thousands bool bDoThousands = (rInfo.nThousand == 0); utl::DigitGroupingIterator aGrouping( GetFormatter().GetLocaleData()->getDigitGrouping()); while (!bStop) // backwards { if (j == 0) { bStop = true; } switch (rInfo.nTypeArray[j]) { case NF_SYMBOLTYPE_DECSEP: aGrouping.reset(); [[fallthrough]]; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_PERCENT: if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep ) sBuff.insert(k, rInfo.sStrArray[j]); if ( k == 0 ) { nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength(); } break; case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[j].getLength() >= 2) /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); break; case NF_SYMBOLTYPE_THSEP: // #i7284# #102685# Insert separator also if number is divided // by thousands and the separator is specified somewhere in // between and not only at the end. // #i12596# But do not insert if it's a parenthesized negative // format like (#,) // In fact, do not insert if divided and regex [0#,],[^0#] and // no other digit symbol follows (which was already detected // during scan of format code, otherwise there would be no // division), else do insert. Same in ImpNumberFill() below. if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) { bDoThousands = ((j == 0) || (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); } if ( bDoThousands ) { if (k > 0) { sBuff.insert(k, rInfo.sStrArray[j]); } else if (nDigitCount < nDigCnt) { // Leading '#' displays nothing (e.g. no leading // separator for numbers <1000 with #,##0 format). // Leading '?' displays blank. // Everything else, including nothing, displays the // separator. sal_Unicode cLeader = 0; if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT) { const OUString& rStr = rInfo.sStrArray[j-1]; sal_Int32 nLen = rStr.getLength(); if (nLen) { cLeader = rStr[ nLen - 1 ]; } } switch (cLeader) { case '#': ; // nothing break; case '?': // replace thousand separator with blank sBuff.insert(k, ' '); break; default: sBuff.insert(k, rInfo.sStrArray[j]); } } aGrouping.advance(); } break; case NF_SYMBOLTYPE_DIGIT: { const OUString& rStr = rInfo.sStrArray[j]; const sal_Unicode* p1 = rStr.getStr(); const sal_Unicode* p = p1 + rStr.getLength(); while ( p1 < p-- ) { nDigitCount++; if (k > 0) { k--; } else { switch (*p) { case '0': sBuff.insert(0, '0'); break; case '?': sBuff.insert(0, ' '); break; } } if (nDigitCount == nDigCnt && k > 0) { // more digits than specified ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping); } } break; } case NF_KEY_CCC: // CCC currency sBuff.insert(k, rScan.GetCurAbbrev()); break; case NF_KEY_GENERAL: // "General" in string { OUStringBuffer sNum; ImpGetOutputStandard(rNumber, sNum); sNum.stripStart('-'); sBuff.insert(k, sNum); break; } default: break; } // switch j--; // next format code string } // while k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ... if (k > nLeadingStringChars) { ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping); } return bRes; } void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string sal_Int32 nStart, // start of digits sal_Int32 & k, // position within string sal_uInt16 nIx, // subformat index sal_Int32 & nDigitCount, // count of integer digits from the right so far utl::DigitGroupingIterator & rGrouping ) // current grouping { if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators { const OUString& rThousandSep = GetFormatter().GetNumThousandSep(); while (k > nStart) { if (nDigitCount == rGrouping.getPos()) { sStr.insert( k, rThousandSep ); rGrouping.advance(); } nDigitCount++; k--; } } else // simply skip { k = nStart; } } bool SvNumberformat::ImpNumberFill( OUStringBuffer& sBuff, // number string double& rNumber, // number for "General" format sal_Int32& k, // position within string sal_uInt16& j, // symbol index within format code sal_uInt16 nIx, // subformat index short eSymbolType, // type of stop condition bool bInsertRightBlank)// insert blank on right for denominator (default = false) { bool bRes = false; bool bStop = false; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); // no normal thousands separators if number divided by thousands bool bDoThousands = (rInfo.nThousand == 0); bool bFoundNumber = false; short nType; k = sBuff.getLength(); // behind last digit while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards { switch ( nType ) { case NF_SYMBOLTYPE_STAR: if( bStarFlag ) { if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) k = 0; // tdf#100842 jump to beginning of number before inserting something else bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); } break; case NF_SYMBOLTYPE_BLANK: if (rInfo.sStrArray[j].getLength() >= 2) { if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) k = 0; // tdf#100842 jump to beginning of number before inserting something else k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); } break; case NF_SYMBOLTYPE_THSEP: // Same as in ImpNumberFillWithThousands() above, do not insert // if divided and regex [0#,],[^0#] and no other digit symbol // follows (which was already detected during scan of format // code, otherwise there would be no division), else do insert. if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) { bDoThousands = ((j == 0) || (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); } if ( bDoThousands && k > 0 ) { sBuff.insert(k, rInfo.sStrArray[j]); } break; case NF_SYMBOLTYPE_DIGIT: { bFoundNumber = true; sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator const OUString& rStr = rInfo.sStrArray[j]; const sal_Unicode* p1 = rStr.getStr(); const sal_Unicode* p = p1 + rStr.getLength(); while ( p1 < p-- ) { if (k > 0) { k--; } else { switch (*p) { case '0': sBuff.insert(0, '0'); break; case '?': sBuff.insert(nPosInsertBlank, ' '); break; } } } } break; case NF_KEY_CCC: // CCC currency sBuff.insert(k, rScan.GetCurAbbrev()); break; case NF_KEY_GENERAL: // Standard in the String { OUStringBuffer sNum; bFoundNumber = true; ImpGetOutputStandard(rNumber, sNum); sNum.stripStart('-'); sBuff.insert(k, sNum); } break; case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing if (k > 0) { k--; } break; default: if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) k = 0; // tdf#100842 jump to beginning of number before inserting something else sBuff.insert(k, rInfo.sStrArray[j]); break; } // of switch if ( j ) j--; // Next String else bStop = true; } // of while return bRes; } void SvNumberformat::GetFormatSpecialInfo(bool& bThousand, bool& IsRed, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt) const { // as before: take info from nNumFor=0 for whole format (for dialog etc.) SvNumFormatType nDummyType; GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt ); // "negative in red" is only useful for the whole format const Color* pColor = NumFor[1].GetColor(); IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor && (*pColor == ImpSvNumberformatScan::GetRedColor()); } void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType, bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const { // take info from a specified sub-format (for XML export) if ( nNumFor > 3 ) { return; // invalid } const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); rScannedType = rInfo.eScannedType; bThousand = rInfo.bThousand; nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION) ? rInfo.nCntExp // number of denominator digits for fraction : rInfo.nCntPost; sal_Int32 nPosHash = 1; if ( rInfo.eScannedType == SvNumFormatType::FRACTION && ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) ) nPrecision -= nPosHash; if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER) { // StandardFormat nLeadingCnt = 1; } else { nLeadingCnt = 0; bool bStop = false; sal_uInt16 i = 0; const sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); while (!bStop && i < nCnt) { short nType = rInfo.nTypeArray[i]; if ( nType == NF_SYMBOLTYPE_DIGIT) { const sal_Unicode* p = rInfo.sStrArray[i].getStr(); while ( *p == '#' ) { p++; } while ( *p == '0' || *p == '?' ) { nLeadingCnt++; p++; } } else if (nType == NF_SYMBOLTYPE_DECSEP || nType == NF_SYMBOLTYPE_EXP || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part, { // do not count '0' of fraction bStop = true; } i++; } } } const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos, bool bString /* = false */ ) const { if ( nNumFor > 3 ) { return nullptr; } sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); if ( !nCnt ) { return nullptr; } if ( nPos == 0xFFFF ) { nPos = nCnt - 1; if ( bString ) { // Backwards short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) ) { pType--; nPos--; } if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) ) { return nullptr; } } } else if ( nPos > nCnt - 1 ) { return nullptr; } else if ( bString ) { // forward short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) ) { pType++; nPos++; } if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY)) ) { return nullptr; } } return &NumFor[nNumFor].Info().sStrArray[nPos]; } short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const { if ( nNumFor > 3 ) { return 0; } sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); if ( !nCnt ) { return 0; } if ( nPos == 0xFFFF ) { nPos = nCnt - 1; } else if ( nPos > nCnt - 1 ) { return 0; } return NumFor[nNumFor].Info().nTypeArray[nPos]; } bool SvNumberformat::IsNegativeWithoutSign() const { if ( IsSecondSubformatRealNegative() ) { const OUString* pStr = GetNumForString( 1, 0, true ); if ( pStr ) { return !HasStringNegativeSign( *pStr ); } } return false; } bool SvNumberformat::IsNegativeInBracket() const { sal_uInt16 nCnt = NumFor[1].GetCount(); if (!nCnt) { return false; } auto& tmp = NumFor[1].Info().sStrArray; return tmp[0] == "(" && tmp[nCnt-1] == ")"; } bool SvNumberformat::HasPositiveBracketPlaceholder() const { sal_uInt16 nCnt = NumFor[0].GetCount(); return NumFor[0].Info().sStrArray[nCnt-1] == "_)"; } DateOrder SvNumberformat::GetDateOrder() const { if ( eType & SvNumFormatType::DATE ) { auto& rTypeArray = NumFor[0].Info().nTypeArray; sal_uInt16 nCnt = NumFor[0].GetCount(); for ( sal_uInt16 j=0; j 3 ) { return nullptr; } return NumFor[nNumFor].GetColor(); } static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr, SvNumberformatLimitOps eOp, double fLimit, std::u16string_view rDecSep ) { if ( eOp == NUMBERFORMAT_OP_NO ) return; switch ( eOp ) { case NUMBERFORMAT_OP_EQ : rStr = "[="; break; case NUMBERFORMAT_OP_NE : rStr = "[<>"; break; case NUMBERFORMAT_OP_LT : rStr = "[<"; break; case NUMBERFORMAT_OP_LE : rStr = "[<="; break; case NUMBERFORMAT_OP_GT : rStr = "[>"; break; case NUMBERFORMAT_OP_GE : rStr = "[>="; break; default: SAL_WARN( "svl.numbers", "unsupported number format" ); break; } rStr += ::rtl::math::doubleToUString( fLimit, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, rDecSep[0], true); rStr += "]"; } static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted ) { if ( nLCID == 0 ) return; if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted) // No format code, no locale. return; auto aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase(); // Search for only last DBNum which is the last element before insertion position if ( bDBNumInserted && nPosInsertLCID >= 8 && aLCIDString.length > 4 && OUString::unacquired(rFormatStr).match( "[DBNum", nPosInsertLCID-8) ) { // remove DBNumX code if long LCID nPosInsertLCID -= 8; rFormatStr.remove( nPosInsertLCID, 8 ); } rFormatStr.insert( nPosInsertLCID, "[$-" + aLCIDString + "]" ); } /** Increment nAlphabetID for CJK numerals * +1 for financial numerals [NatNum2] * +2 for Arabic fullwidth numerals [NatNum3] * */ static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum ) { if ( nNatNum == 2) // financial nAlphabetID += 1; else if ( nNatNum == 3) nAlphabetID += 2; nAlphabetID = nAlphabetID << 24; } OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords, const LocaleDataWrapper& rLocWrp, LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */, bool bSystemLanguage /* =false */ ) const { OUStringBuffer aStr; if (maLocale.meSubstitute != LocaleType::Substitute::NONE) { // XXX: theoretically this could clash with the first subformat's // lcl_insertLCID() below, in practice as long as it is used for system // time and date modifiers it shouldn't (i.e. there is no calendar or // numeral specified as well). aStr.append("[$-" + maLocale.generateCode() + "]"); } bool bDefault[4]; // 1 subformat matches all if no condition specified, bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO ); // with 2 subformats [>=0];[<0] is implied if no condition specified bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 && eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 ); // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified, // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked. bDefault[2] = ( !bDefault[0] && !bDefault[1] && eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 && eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 ); bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2]; // from now on bDefault[] values are used to append empty subformats at the end bDefault[3] = false; if ( !bDefaults ) { // conditions specified if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO ) { bDefault[0] = bDefault[1] = true; // [];x } else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO && NumFor[2].GetCount() == 0 ) { bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];; } // nothing to do if conditions specified for every subformat } else if ( bDefault[0] ) { bDefault[0] = false; // a single unconditional subformat is never delimited } else { if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 ) { bDefault[3] = true; // special cases x;x;; and ;x;; } for ( int i=0; i<3 && !bDefault[i]; ++i ) { bDefault[i] = true; } } int nSem = 0; // needed ';' delimiters int nSub = 0; // subformats delimited so far for ( int n=0; n<4; n++ ) { if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED ) { nSem++; } OUString aPrefix; if ( !bDefaults ) { switch ( n ) { case 0 : lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1, fLimit1, rLocWrp.getNumDecimalSep() ); break; case 1 : lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2, fLimit2, rLocWrp.getNumDecimalSep() ); break; } } const OUString& rColorName = NumFor[n].GetColorName(); if ( !rColorName.isEmpty() ) { const NfKeywordTable & rKey = rScan.GetKeywords(); for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ ) { if ( rKey[j] == rColorName ) { aPrefix += "[" + rKeywords[j] + "]"; break; // for } } } SvNumberNatNum aNatNum = NumFor[n].GetNatNum(); bool bDBNumInserted = false; if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW)) { // GetFormatStringForExcel() may have changed language to en_US if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW) aNatNum.SetLang( nOriginalLang ); if ( aNatNum.GetDBNum() > 0 ) { aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]"; bDBNumInserted = true; } } sal_uInt16 nCnt = NumFor[n].GetCount(); if ( nSem && (nCnt || !aPrefix.isEmpty()) ) { for ( ; nSem; --nSem ) { aStr.append( ';' ); } for ( ; nSub <= n; ++nSub ) { bDefault[nSub] = false; } } if ( !aPrefix.isEmpty() ) { aStr.append( aPrefix ); } sal_Int32 nPosHaveLCID = -1; sal_Int32 nPosInsertLCID = aStr.getLength(); sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038 constexpr sal_uInt32 kCalGengou = 0x0030000; if ( nCnt ) { auto& rTypeArray = NumFor[n].Info().nTypeArray; auto& rStrArray = NumFor[n].Info().sStrArray; for ( sal_uInt16 j=0; j 1 && rStrArray[j][1] == '+' ) aStr.append( "+" ); else // tdf#102370: Excel code for exponent without sign aStr.append( "-" ); break; case NF_SYMBOLTYPE_DATESEP : aStr.append( rLocWrp.getDateSep() ); break; case NF_SYMBOLTYPE_TIMESEP : aStr.append( rLocWrp.getTimeSep() ); break; case NF_SYMBOLTYPE_TIME100SECSEP : aStr.append( rLocWrp.getTime100SecSep() ); break; case NF_SYMBOLTYPE_FRACBLANK : case NF_SYMBOLTYPE_STRING : if ( rStrArray[j].getLength() == 1 ) { if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING ) aStr.append( '\\' ); aStr.append( rStrArray[j] ); } else { aStr.append( "\"" + rStrArray[j] + "\"" ); } break; case NF_SYMBOLTYPE_CALDEL : if (j + 1 >= nCnt) break; if ( rStrArray[j+1] == "gengou" ) { nCalendarID = kCalGengou; } else if ( rStrArray[j+1] == "hijri" ) { nCalendarID = 0x0060000; } else if ( rStrArray[j+1] == "buddhist" ) { nCalendarID = 0x0070000; } else if ( rStrArray[j+1] == "jewish" ) { nCalendarID = 0x0080000; } // Other calendars (see tdf#36038) not corresponding between LibO and XL. // However, skip any calendar modifier and don't write // as format code (if not as literal string). j += 2; break; case NF_SYMBOLTYPE_CURREXT : nPosHaveLCID = aStr.getLength(); aStr.append( rStrArray[j] ); break; default: aStr.append( rStrArray[j] ); } } } } sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038 LanguageType nLanguageID = LANGUAGE_SYSTEM; if ( aNatNum.IsComplete() ) { nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); if ( aNatNum.GetNatNum() == 0 ) { nAlphabetID = 0x01000000; // Arabic-european numerals } else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() ) { // if no DBNum code then use long LCID // if DBNum value != NatNum value, use DBNum and not extended LCID // if calendar, then DBNum will be removed LanguageType pri = primary(nLanguageID); if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY ) nAlphabetID = 0x02000000; // Arabic-indic numerals else if ( pri == primary(LANGUAGE_FARSI) ) nAlphabetID = 0x03000000; // Farsi numerals else if ( pri.anyOf( primary(LANGUAGE_HINDI), primary(LANGUAGE_MARATHI), primary(LANGUAGE_NEPALI) )) nAlphabetID = 0x04000000; // Devanagari numerals else if ( pri == primary(LANGUAGE_BENGALI) ) nAlphabetID = 0x05000000; // Bengali numerals else if ( pri == primary(LANGUAGE_PUNJABI) ) { if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO ) nAlphabetID = 0x02000000; // Arabic-indic numerals else nAlphabetID = 0x06000000; // Punjabi numerals } else if ( pri == primary(LANGUAGE_GUJARATI) ) nAlphabetID = 0x07000000; // Gujarati numerals else if ( pri == primary(LANGUAGE_ODIA)) nAlphabetID = 0x08000000; // Odia (Oriya) numerals else if ( pri == primary(LANGUAGE_TAMIL)) nAlphabetID = 0x09000000; // Tamil numerals else if ( pri == primary(LANGUAGE_TELUGU)) nAlphabetID = 0x0A000000; // Telugu numerals else if ( pri == primary(LANGUAGE_KANNADA)) nAlphabetID = 0x0B000000; // Kannada numerals else if ( pri == primary(LANGUAGE_MALAYALAM)) nAlphabetID = 0x0C000000; // Malayalam numerals else if ( pri == primary(LANGUAGE_THAI)) { // The Thai T NatNum modifier during Xcl export. if ( rKeywords[NF_KEY_THAI_T] == "T" ) nAlphabetID = 0x0D000000; // Thai numerals } else if ( pri == primary(LANGUAGE_LAO)) nAlphabetID = 0x0E000000; // Lao numerals else if ( pri == primary(LANGUAGE_TIBETAN)) nAlphabetID = 0x0F000000; // Tibetan numerals else if ( pri == primary(LANGUAGE_BURMESE)) nAlphabetID = 0x10000000; // Burmese numerals else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA)) nAlphabetID = 0x11000000; // Tigrigna numerals else if ( pri == primary(LANGUAGE_KHMER)) nAlphabetID = 0x12000000; // Khmer numerals else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA)) { if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO ) nAlphabetID = 0x13000000; // Mongolian numerals } // CJK numerals else if ( pri == primary(LANGUAGE_JAPANESE)) { nAlphabetID = 0x1B; lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); } else if ( pri == primary(LANGUAGE_CHINESE)) { if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL || nLanguageID == LANGUAGE_CHINESE_HONGKONG || nLanguageID == LANGUAGE_CHINESE_MACAU ) { nAlphabetID = 0x21; lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); } else // LANGUAGE_CHINESE_SIMPLIFIED { nAlphabetID = 0x1E; lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); } } else if ( pri == primary(LANGUAGE_KOREAN)) { if ( aNatNum.GetNatNum() == 9 ) // Hangul { nAlphabetID = 0x27000000; } else { nAlphabetID = 0x24; lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); } } } // Add LCID to DBNum if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM ) nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); } else if (nPosHaveLCID < 0) { // Do not insert a duplicated LCID that was already given with a // currency format as [$R-1C09] if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW) { // Explicit locale, write only to the first subformat. if (n == 0) nLanguageID = MsLangId::getRealLanguage( nOriginalLang); } else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW) { // Explicit locale but no locale data thus assigned to system // locale, preserve for roundtrip, write only to the first // subformat. if (n == 0) nLanguageID = maLocale.meLanguageWithoutLocaleData; } } if ( nCalendarID > 0 ) { // Add alphabet and language to calendar if ( nAlphabetID == 0 ) nAlphabetID = 0x01000000; if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW ) nLanguageID = nOriginalLang; } lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast(nLanguageID), nPosInsertLCID, bDBNumInserted); } for ( ; nSub<4 && bDefault[nSub]; ++nSub ) { // append empty subformats aStr.append( ';' ); } return aStr.makeStringAndClear(); } OUString SvNumberformat::ImpGetNatNumString( const SvNumberNatNum& rNum, sal_Int64 nVal, sal_uInt16 nMinDigits ) const { OUString aStr; if ( nMinDigits ) { if ( nMinDigits == 2 ) { // speed up the most common case if ( 0 <= nVal && nVal < 10 ) { sal_Unicode aBuf[2]; aBuf[0] = '0'; aBuf[1] = '0' + nVal; aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf)); } else { aStr = OUString::number( nVal ); } } else { OUString aValStr( OUString::number( nVal ) ); if ( aValStr.getLength() >= nMinDigits ) { aStr = aValStr; } else { OUStringBuffer aBuf; for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index) { aBuf.append('0'); } aBuf.append(aValStr); aStr = aBuf.makeStringAndClear(); } } } else { aStr = OUString::number( nVal ); } return impTransliterate(aStr, rNum); } OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, const SvNumberNatNum& rNum ) const { css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), rNum.GetParams()); } void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr, const SvNumberNatNum& rNum ) const { css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); rStr = GetFormatter().GetNatNum()->getNativeNumberStringParams( OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams()); } OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, const SvNumberNatNum& rNum, const sal_uInt16 nDateKey) const { // no KEYWORD=argument list in NatNum12 if (rNum.GetParams().indexOf('=') == -1) return impTransliterateImpl( rStr, rNum); const NfKeywordTable & rKeywords = rScan.GetKeywords(); // Format: KEYWORD=numbertext_prefix, ..., for example: // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number] sal_Int32 nField = -1; do { nField = rNum.GetParams().indexOf(Concat2View(rKeywords[nDateKey] + "="), ++nField); } while (nField != -1 && nField != 0 && (rNum.GetParams()[nField - 1] != ',' && rNum.GetParams()[nField - 1] != ' ')); // no format specified for actual keyword if (nField == -1) return rStr; sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1; sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField); if (nFieldEnd == -1) nFieldEnd = rNum.GetParams().getLength(); css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); return GetFormatter().GetNatNum()->getNativeNumberStringParams( rStr, aLocale, rNum.GetNatNum(), rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen)); } void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr, sal_uInt16 nNumFor ) const { if ( nNumFor <= 3 ) { const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum(); if ( rNum.IsSet() ) { css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes() * should rather return NativeNumberXmlAttributes2 and places * adapted, and whether to fill Spellout or something different * should be internal there. */ css::i18n::NativeNumberXmlAttributes aTmp( GetFormatter().GetNatNum()->convertToXmlAttributes( aLocale, rNum.GetNatNum())); rAttr.Locale = aTmp.Locale; rAttr.Format = aTmp.Format; rAttr.Style = aTmp.Style; if ( NatNumTakesParameters(rNum.GetNatNum()) ) { // NatNum12 spell out numbers, dates and money amounts rAttr.Spellout = rNum.GetParams(); // Mutually exclusive. rAttr.Format.clear(); rAttr.Style.clear(); } else { rAttr.Spellout.clear(); } } else { rAttr = css::i18n::NativeNumberXmlAttributes2(); } } else { rAttr = css::i18n::NativeNumberXmlAttributes2(); } } OUString SvNumberformat::GetNatNumModifierString( sal_uInt16 nNumFor ) const { if ( nNumFor > 3 ) return ""; const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum(); if ( !rNum.IsSet() ) return ""; const sal_Int32 nNum = rNum.GetNatNum(); OUStringBuffer sNatNumModifier = "[NatNum" + OUString::number( nNum ); if ( NatNumTakesParameters( nNum ) ) { sNatNumModifier.append( " " + rNum.GetParams() ); } sNatNumModifier.append( "]" ); return sNatNumModifier.makeStringAndClear(); } // static bool SvNumberformat::HasStringNegativeSign( const OUString& rStr ) { // For Sign '-' needs to be at the start or at the end of the string (blanks ignored) sal_Int32 nLen = rStr.getLength(); if ( !nLen ) { return false; } const sal_Unicode* const pBeg = rStr.getStr(); const sal_Unicode* const pEnd = pBeg + nLen; const sal_Unicode* p = pBeg; do { // Start if ( *p == '-' ) { return true; } } while ( *p == ' ' && ++p < pEnd ); p = pEnd - 1; do { // End if ( *p == '-' ) { return true; } } while ( *p == ' ' && pBeg < --p ); return false; } // static bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos, sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut ) { sal_Int32 nLen = rStr.getLength(); if ( nPos >= nLen ) { return false; } const sal_Unicode* p0 = rStr.getStr(); const sal_Unicode* p = p0; const sal_Unicode* p1 = p0 + nPos; bool bQuoted = false; while ( p <= p1 ) { if ( *p == cQuote ) { if ( p == p0 ) { bQuoted = true; } else if ( bQuoted ) { if ( *(p-1) != cEscIn ) { bQuoted = false; } } else { if ( *(p-1) != cEscOut ) { bQuoted = true; } } } p++; } return bQuoted; } // static sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos, sal_Unicode cQuote, sal_Unicode cEscIn ) { if ( nPos < 0 ) { return -1; } sal_Int32 nLen = rStr.getLength(); if ( nPos >= nLen ) { return -1; } if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) ) { if ( rStr[ nPos ] == cQuote ) { return nPos; // Closing cQuote } return -1; } const sal_Unicode* p0 = rStr.getStr(); const sal_Unicode* p = p0 + nPos; const sal_Unicode* p1 = p0 + nLen; while ( p < p1 ) { if ( *p == cQuote && p > p0 && *(p-1) != cEscIn ) { return sal::static_int_cast< sal_Int32 >(p - p0); } p++; } return nLen; // End of String } sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const { if ( nNumFor < 4 ) { sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); return nCnt - ImpGetNumForStringElementCount( nNumFor ); } return 0; } sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const { sal_uInt16 nCnt = 0; sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount(); auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray; for ( sal_uInt16 j=0; j 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 ) { j++; sString.append( NumFor[0].Info().sStrArray[j] ); } if (nPrecision > 0) { sString.append( rLoc().getTime100SecSep() ); padToLength(sString, sString.getLength() + nPrecision, '0'); } break; case NF_SYMBOLTYPE_TIME100SECSEP: case NF_SYMBOLTYPE_DIGIT: break; case NF_SYMBOLTYPE_STRING: sString.append( "\"" ); [[fallthrough]]; default: sString.append( NumFor[0].Info().sStrArray[j] ); if (rTypeArray[j] == NF_SYMBOLTYPE_STRING) { sString.append( "\"" ); } } } return sString.makeStringAndClear(); } sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const { if (nIx >= 4) return 0; const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY) return 0; if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT) return SvNumberFormatter::UNLIMITED_PRECISION; return rInfo.nThousand * 3; } const CharClass& SvNumberformat::rChrCls() const { return rScan.GetChrCls(); } const LocaleDataWrapper& SvNumberformat::rLoc() const { return rScan.GetLoc(); } CalendarWrapper& SvNumberformat::GetCal() const { return rScan.GetCal(); } const SvNumberFormatter& SvNumberformat::GetFormatter() const { return *rScan.GetNumberformatter(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */