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 *calendarsandnumerals.BUTonlyifourlocaledataallowsit!ForLCID *numeralsandcalendarssee *http://office.microsoft.com/en-us/excel/HA010346351033.aspx *Calendarisinsertedafter *allprefixeshavebeenconsumedasitisactuallyaformatmodifier *andnotaprefix. *Currentlycalendarsaretiedtothelocaleoftheentirenumber *format,e.g.[~buddhist]inen_USdoesn'twork. *=>Havingdifferentlocalesinsubformatsdoesnotwork!
* */ /* TODO: calendars could be tied to a sub format's NatNum info *instead,orevenbetterbeavailableforanylocale.Needsa *differentimplementationofGetCal()andlocaledatacalendars.
* */ switch ( aTmpLocale.mnCalendarType & 0x7F )
{ case0x03 : // 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; case0x05 : // Korean Dangi calendar
sCalendar = "[~dangi]"; // Only Korean language support dangi calendar if ( nLocaleLang != LANGUAGE_KOREAN )
{
nLang = maLocale.meLanguage = LANGUAGE_KOREAN;
} break; case0x06 : // Hijri calendar case0x17 : // 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; case0x07 : // 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; case0x08 : // Hebrew calendar
sCalendar = "[~jewish]"; // Many languages (but not all) support Jewish calendar // Unable to find any logic => keep same language break; case0x0E : // unknown calendar case0x0F : // unknown calendar case0x10 : // Indian calendar (unsupported) case0x11 : // unknown calendar case0x12 : // unknown calendar case0x13 : // 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?Achangeinthelocalemayaffect *separatorsandkeywords.Ontheother *handtheymayhavebeenenteredasused *intheoriginatinglocale,there'sno *waytopredictotherthananalyzingthe *formatcode,weassumeherethecurrent *contextisused,whichismostlikely *thecase.
* */
// 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 *insertedsothelastonewins,though *ourowncalendarmodifierssupport *multiplecalendarswithinonesubformat
* 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
}
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 genitivecaseifthedayfollowsthemonth,andpartitivecaseiftheday precedesthemonth.Ifthereisnodayofmonththenominativecase(noun) isreturned.Alsoifthemonthisimmediatelyprecededorfollowedbya literalstringotherthanspaceandnotfollowedbyacomma,thenominative nameisused,thispreventsduplicatedcasingforMMMM\t\aandsuchin documentsimportedfrom(e.g.Finnish)ExcelorolderLibO/OOoreleases.
*/
// 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)
{ case1: // 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; case2: // 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; case3: // 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 *unusedpleasedon'tremoveit,itwouldbeneededby *SwitchToSpecifiedCalendar(),seecommentin
* 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;
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.361Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-01)
¤
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.