/* -*- 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 .
*/
const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary... constdouble EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value. constdouble 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).
const sal_Unicode cBlankDigit = 0x2007; // tdf#158890 use figure space for '?'
} // namespace
// #121103# when copying between documents, get color pointers from own scanner
ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr;
for (sal_uInt16 i = 0; i < 4; i++)
{
NumFor[i].Copy(rFormat.NumFor[i], pColorSc);
}
}
staticbool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType )
{ if ( nSymbolType > 0 )
{ returntrue; // 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 : returntrue;
} returnfalse;
}
/** 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 */ staticconst 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
};
// is there a 3-letter bank code in NatNum12 param (but not // followed by an equal mark, like in the date code "NNN=")? staticbool lcl_isNatNum12Currency( std::u16string_view sParam )
{
sal_Int32 nUpper = 0;
sal_Int32 nLen = sParam.size(); for (sal_Int32 n = 0; n < nLen; ++n)
{
sal_Unicode c = sParam[n]; if ( 'A' <= c && c <= 'Z' )
{
++nUpper;
} elseif ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') )
{ returntrue;
} else
{
nUpper = 0;
}
}
// 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 = GetCurrentLanguageData().GetNumThousandSep(); if ( rThSep.getLength() == 1)
{ const sal_Unicode cNBSp = 0xA0; const sal_Unicode cNNBSp = 0x202F; if (rThSep[0] == cNBSp )
sBuff.replace( cNBSp, ' '); elseif (rThSep[0] == cNNBSp )
sBuff.replace( cNNBSp, ' ');
}
// 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.GetCurrentLanguageData().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<SvNumberformatLimitOps>(eSymbolType);
} elseif ( nIndex == 1 && bCondition )
{
eOp2 = static_cast<SvNumberformatLimitOps>(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, rNatNum, 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++;
} elseif (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0))
{ if (aConvertToDecSep.isEmpty())
aConvertToDecSep = rScan.GetCurrentLanguageData().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
} elseif ( 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<sal_uInt8>(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" elseif (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<sal_uInt8>(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;
// "$-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
} elseif (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++;
} elseif (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; elseif (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;
} elseif (nIndex == 3)
{ // #77026# Everything recognized IS text
NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT;
} elseif ( NumFor[nIndex].Info().eScannedType != eType)
{
eType = SvNumFormatType::DEFINED;
}
} else
{
nCheckPos = nPosOld + nStrPos; // error in string
bCancel = true; // break for
}
}
} elseif (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error
{
nCheckPos = nPosOld;
bCancel = true;
} elseif ( 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
} elseif (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<sal_Int32>( sBuff.indexOf(']') + 1, 1);
} elseif (IsSystemLongDateFormat())
{ if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE)
nCheckPos = std::max<sal_Int32>( 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);
}
}
} elseif ( 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);
}
}
} elseif ( 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
};
void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision, const NativeNumberWrapper& rNatNum) const
{ // Make sure the precision doesn't go over the maximum allowable precision.
nPrecision = ::std::min(UPPER_PRECISION, nPrecision);
// We decided to strip trailing zeros unconditionally, since binary // double-precision rounding error makes it impossible to determine e.g. // whether 844.10000000000002273737 is what the user has typed, or the // user has typed 844.1 but IEEE 754 represents it that way internally.
short SvNumberformat::ImpCheckCondition(double fNumber, double fLimit,
SvNumberformatLimitOps eOp)
{ switch(eOp)
{ case NUMBERFORMAT_OP_NO: return -1; case NUMBERFORMAT_OP_EQ: returnstatic_cast<short>(fNumber == fLimit); case NUMBERFORMAT_OP_NE: returnstatic_cast<short>(fNumber != fLimit); case NUMBERFORMAT_OP_LT: returnstatic_cast<short>(fNumber < fLimit); case NUMBERFORMAT_OP_LE: returnstatic_cast<short>(fNumber <= fLimit); case NUMBERFORMAT_OP_GT: returnstatic_cast<short>(fNumber > fLimit); case NUMBERFORMAT_OP_GE: returnstatic_cast<short>(fNumber >= fLimit); default: return -1;
}
}
staticbool 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]); returntrue;
} returnfalse;
}
// 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');
}
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(rNatNum, ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP, bStarFlag);
sal_Unicode cAmPm = ' '; // a or p if (rInfo.nCntExp) // AM/PM
{ if (nHour == 0)
{
nHour = 12;
cAmPm = 'a';
} elseif (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
{
CalendarWrapper& rCal = *rCurrentLang.GetCalendar(); if ( !bCalendarSet )
{ double fDiff = DateTime::Sub( DateTime(rScan.GetNullDate()), rCal.getEpochStart());
fDiff += fNumberOrig;
rCal.setLocalDateTime( fDiff );
bCalendarSet = true;
} 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(rNatNum, nIx, nMin )); break; case NF_KEY_MMI: // MM
sBuff.append(ImpIntToString(rNatNum, nIx, nMin, 2 )); break; case NF_KEY_H: // H
sBuff.append(ImpIntToString(rNatNum, nIx, nHour )); break; case NF_KEY_HH: // HH
sBuff.append(ImpIntToString(rNatNum, nIx, nHour, 2 )); break; case NF_KEY_S: // S
sBuff.append(ImpIntToString(rNatNum, nIx, nSec )); break; case NF_KEY_SS: // SS
sBuff.append(ImpIntToString(rNatNum, 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 )
{ usingnamespace ::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;
} elseif (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;
}
namespace {
bool ImpIsOtherCalendar( const ImpSvNumFor& rNumFor, const CalendarWrapper& rCal )
{ if ( rCal.getUniqueID() != GREGORIAN )
{ returnfalse;
} 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 : returnfalse; 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 : returntrue;
}
} returnfalse;
}
}
void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar, double& fOrgDateTime,
CalendarWrapper& rCal ) const
{ if ( rCal.getUniqueID() != GREGORIAN ) return;
#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() */
// 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.
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, *rCurrentLang.GetCalendar() ); 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], rNatNum);
}
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<NfKeywordIndex>(rInfo.nTypeArray[i])),
nNatNum); if ( bUseSpellout )
{
aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
}
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], rNatNum);
}
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, *rCurrentLang.GetCalendar() );
}
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], rNatNum);
}
sBuff.append(aStr); if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
} break; case NF_KEY_DDDD: // DDDD if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
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], rNatNum);
}
sBuff.append(aStr); if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
} break; case NF_KEY_YY: // YY if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
} // 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, *rCurrentLang.GetCalendar() );
} break; case NF_KEY_YYYY: // YYYY if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
} // 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]))
{ usingnamespace 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(), rNatNum);
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], rNatNum);
}
sBuff.append(aStr); if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
} 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], rNatNum);
}
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], rNatNum);
}
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(rNatNum, 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::ImpDecimalFill(const NativeNumberWrapper& rNatNum,
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 bStarFlag) const
{ 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;
} elseif ( c == '-' )
{ if ( bInteger )
{
sStr[ k ] = '-';
}
bFilled = true;
} elseif ( c == '?' )
{
sStr[ k ] = cBlankDigit;
bFilled = true;
} elseif ( !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, rNatNum);
sNum.stripStart('-');
sStr.insert(k, sNum); break;
} default: break;
} // of switch
j--;
} // of while
} // of decimal places
bRes |= ImpNumberFillWithThousands(rNatNum, sStr, rNumber, k, j, nIx, // Fill with . if needed
rInfo.nCntPre, bStarFlag, bFilled );
return bRes;
}
bool SvNumberformat::ImpNumberFillWithThousands( const NativeNumberWrapper& rNatNum,
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 bStarFlag, bool bAddDecSep) const// 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( GetCurrentLanguageData().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: if ( rInfo.nTypeArray[j] == NF_SYMBOLTYPE_STRING && nDigCnt == 0 )
{ // tdf#159930 no integer in format ".###"
k = 0; // insert string at the beginning
}
[[fallthrough]]; 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]);
} elseif (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, cBlankDigit); 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, rNatNum);
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 ) const// current grouping
{ if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators
{ const OUString& rThousandSep = GetCurrentLanguageData().GetNumThousandSep(); while (k > nStart)
{ if (nDigitCount == rGrouping.getPos())
{
sStr.insert( k, rThousandSep );
rGrouping.advance();
}
nDigitCount++;
k--;
}
} else// simply skip
{
k = nStart;
}
}
bool SvNumberformat::ImpNumberFill( const NativeNumberWrapper& rNatNum,
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 bStarFlag, bool bInsertRightBlank) const// 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, cBlankDigit); 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, rNatNum);
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.)
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++;
}
} elseif (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++;
}
}
}
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 += "]";
}
staticvoid 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 + "]" );
}
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
} elseif ( 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
} elseif ( 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 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<nNumForCnt; ++j )
{ switch ( rTypeArray[j] )
{ case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_CURRENCY: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: case NF_SYMBOLTYPE_PERCENT:
++nCnt; break;
}
} return nCnt;
}
bool SvNumberformat::IsMinuteSecondFormat() const
{ if (GetMaskedType() != SvNumFormatType::TIME) returnfalse;
constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch
constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket
constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket
constexpr sal_uInt16 kMM = 0x04; // M or MM
constexpr sal_uInt16 kTS = 0x08; // Time Separator
constexpr sal_uInt16 kSS = 0x10; // S or SS #define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS)) // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits.
sal_uInt16 nState = k00; bool bSep = false;
sal_uInt16 nNumForCnt = NumFor[0].GetCount(); autoconst & rTypeArray = NumFor[0].Info().nTypeArray; for (sal_uInt16 j=0; j < nNumForCnt; ++j)
{ switch (rTypeArray[j])
{ case NF_SYMBOLTYPE_DEL:
{ // '[' or ']' before/after MM or SS const OUString& rStr = NumFor[0].Info().sStrArray[j]; if (rStr == "[")
{ if (nState != k00 && nState != (kMM|kTS)) returnfalse;
nState |= kLB;
} elseif (rStr == "]")
{ if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS)) returnfalse;
nState |= kRB;
} else returnfalse;
} break; case NF_KEY_MI: case NF_KEY_MMI: if (nState != k00 && nState != kLB) returnfalse;
nState |= kMM; break; case NF_SYMBOLTYPE_TIMESEP: if (nState != kMM && nState != (kLB|kMM|kRB)) returnfalse;
nState |= kTS; break; case NF_KEY_S: case NF_KEY_SS: if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB)) returnfalse;
nState |= kSS; break; case NF_SYMBOLTYPE_TIME100SECSEP: // Trailing fraction of seconds allowed. if (!HAS_MINUTE_SECOND(nState)) returnfalse;
bSep = true; break; case NF_SYMBOLTYPE_DIGIT: if (!bSep) returnfalse; break; case NF_SYMBOLTYPE_STRING: // nothing, display literal break; default: returnfalse;
}
} return HAS_MINUTE_SECOND(nState); #undef HAS_MINUTE_SECOND
}
OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const
{
OUStringBuffer sString; using comphelper::string::padToLength;
sal_uInt16 nNumForCnt = NumFor[0].GetCount(); autoconst & rTypeArray = NumFor[0].Info().nTypeArray; for (sal_uInt16 j=0; j < nNumForCnt; ++j)
{ switch (rTypeArray[j])
{ case NF_KEY_S : case NF_KEY_SS:
sString.append( NumFor[0].Info().sStrArray[j] ); if ( 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( "\"" );
}
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.159Bemerkung:
(vorverarbeitet am 2026-05-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.