/* -*- 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 .
*/
/* It is not clear how we want timezones to be handled. Convert them to local * time isn't wanted, as it isn't done in any other place and timezone * information isn't stored anywhere. Ignoring them and pretending local time * may be wrong too and might not be what the user expects. Keep the input as * string so that no information is lost. * Anyway, defining NF_RECOGNIZE_ISO8601_TIMEZONES to 1 would be the way how it
* would work, together with the nTimezonePos handling in GetTimeRef(). */ #define NF_RECOGNIZE_ISO8601_TIMEZONES 0
const sal_Unicode cNoBreakSpace = 0xA0; const sal_Unicode cNarrowNoBreakSpace = 0x202F; constbool kDefaultEra = true; // Gregorian CE, positive year
/** * Only simple unsigned floating point values without any error detection, * decimal separator has to be '.'
*/ double ImpSvNumberInputScan::StringToDouble( std::u16string_view aStr, bool bForceFraction )
{
std::unique_ptr<char[]> bufInHeap;
constexpr int bufOnStackSize = 256; char bufOnStack[bufOnStackSize]; char* buf = bufOnStack; const sal_Int32 bufsize = aStr.size() + (bForceFraction ? 2 : 1); if (bufsize > bufOnStackSize)
{
bufInHeap = std::make_unique<char[]>(bufsize);
buf = bufInHeap.get();
} char* p = buf; if (bForceFraction)
*p++ = '.'; for (size_t nPos = 0; nPos < aStr.size(); ++nPos)
{
sal_Unicode c = aStr[nPos]; if (c == '.' || (c >= '0' && c <= '9'))
*p++ = static_cast<char>(c); else break;
}
*p = '\0';
return strtod_nolocale(buf, nullptr);
}
namespace {
/** * Splits up the input into numbers and strings for further processing * (by the Turing machine). * * Starting state = GetChar * ---------------+-------------------+-----------------------------+--------------- * Old State | Character read | Event | New state * ---------------+-------------------+-----------------------------+--------------- * GetChar | Number | Symbol = Character | GetValue * | Else | Symbol = Character | GetString * ---------------|-------------------+-----------------------------+--------------- * GetValue | Number | Symbol = Symbol + Character | GetValue * | Else | Dec(CharPos) | Stop * ---------------+-------------------+-----------------------------+--------------- * GetString | Number | Dec(CharPos) | Stop * | Else | Symbol = Symbol + Character | GetString * ---------------+-------------------+-----------------------------+---------------
*/ enum ScanState // States of the Turing machine
{
SsStop = 0,
SsStart = 1,
SsGetValue = 2,
SsGetString = 3
};
if (StringPtrContainsImpl( rWhat, rString.getStr(), nPos))
{
nPos += rWhat.getLength(); if (nPos == rString.getLength()) returntrue; // word at end of string
/* TODO: we COULD invoke bells and whistles word break iterator to find * the next boundary, but really ... this is called for date input, so * how many languages do not separate the day and month names in some
* form? */
// Check simple ASCII first before invoking i18n or anything else. const sal_Unicode c = rString[nPos];
// Common separating ASCII characters in date context. switch (c)
{ case' ': case'-': case'.': case'/': returntrue; default:
; // nothing
}
if (rtl::isAsciiAlphanumeric( c )) returnfalse; // Alpha or numeric is not word gap.
sal_Int32 nIndex = nPos;
rString.iterateCodePoints( &nIndex); if (nPos+1 < nIndex) returntrue; // Surrogate, assume these to be new words.
/** * jump over rWhat in rString at nPos
*/ inlinebool ImpSvNumberInputScan::SkipString( const OUString& rWhat, const OUString& rString, sal_Int32& nPos )
{ if ( StringContains( rWhat, rString, nPos ) )
{
nPos = nPos + rWhat.getLength(); returntrue;
} returnfalse;
}
/** * Recognizes exactly ,111 in {3} and {3,2} or ,11 in {3,2} grouping
*/ inlinebool ImpSvNumberInputScan::GetThousandSep( std::u16string_view rString,
sal_Int32& nPos,
sal_uInt16 nStringPos ) const
{ const OUString& rSep = mrCurrentLanguageData.GetNumThousandSep(); // Is it an ordinary space instead of a no-break space? bool bSpaceBreak = (rSep[0] == cNoBreakSpace || rSep[0] == cNarrowNoBreakSpace) &&
rString[0] == u' ' &&
rSep.getLength() == 1 && rString.size() == 1; if (!((rString == rSep || bSpaceBreak) && // nothing else
nStringPos < nStringsCnt - 1 && // safety first!
IsNum[ nStringPos + 1 ] )) // number follows
{ returnfalse; // no? => out
}
utl::DigitGroupingIterator aGrouping( mrCurrentLanguageData.GetLocaleData()->getDigitGrouping()); // Match ,### in {3} or ,## in {3,2} /* FIXME: this could be refined to match ,## in {3,2} only if ,##,## or * ,##,### and to match ,### in {3,2} only if it's the last. However, * currently there is no track kept where group separators occur. In {3,2} * #,###,### and #,##,## would be valid input, which maybe isn't even bad
* for #,###,###. Other combinations such as #,###,## maybe not. */
sal_Int32 nLen = sStrArray[ nStringPos + 1 ].getLength(); if (nLen == aGrouping.get() || // with 3 (or so) digits
nLen == aGrouping.advance().get() || // or with 2 (or 3 or so) digits
nPosThousandString == nStringPos + 1 ) // or concatenated
{
nPos = nPos + rSep.getLength(); returntrue;
} returnfalse;
}
/** * Conversion of text to logical value * "true" => 1: * "false"=> -1: * else => 0:
*/ short ImpSvNumberInputScan::GetLogical( std::u16string_view rString ) const
{ short res;
const ImpSvNumberformatScan* pFS = mrCurrentLanguageData.GetFormatScanner(); if ( rString == pFS->GetTrueString() )
{
res = 1;
} elseif ( rString == pFS->GetFalseString() )
{
res = -1;
} else
{
res = 0;
} return res;
}
/** * Converts a string containing a month name (JAN, January) at nPos into the * month number (negative if abbreviated), returns 0 if nothing found
*/ short ImpSvNumberInputScan::GetMonth( const OUString& rString, sal_Int32& nPos )
{ short res = 0; // no month found
if (rString.getLength() > nPos) // only if needed
{ if ( !bTextInitialized )
{
InitText();
}
sal_Int16 nMonths = mrCurrentLanguageData.GetCalendar()->getNumberOfMonthsInYear(); for ( sal_Int16 i = 0; i < nMonths; i++ )
{ if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveMonthText[i], rString, nPos ) )
{ // genitive full names first
nPos = nPos + pUpperGenitiveMonthText[i].getLength();
res = i + 1; break; // for
} elseif ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveAbbrevMonthText[i], rString, nPos ) )
{ // genitive abbreviated
nPos = nPos + pUpperGenitiveAbbrevMonthText[i].getLength();
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
} elseif ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveMonthText[i], rString, nPos ) )
{ // partitive full names
nPos = nPos + pUpperPartitiveMonthText[i].getLength();
res = i+1; break; // for
} elseif ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveAbbrevMonthText[i], rString, nPos ) )
{ // partitive abbreviated
nPos = nPos + pUpperPartitiveAbbrevMonthText[i].getLength();
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
} elseif ( StringContainsWord( pUpperMonthText[i], rString, nPos ) )
{ // noun full names
nPos = nPos + pUpperMonthText[i].getLength();
res = i+1; break; // for
} elseif ( StringContainsWord( pUpperAbbrevMonthText[i], rString, nPos ) )
{ // noun abbreviated
nPos = nPos + pUpperAbbrevMonthText[i].getLength();
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
} elseif (i == 2 && mrCurrentLanguageData.GetLanguageTag().getLanguage() == "de")
{ if (pUpperAbbrevMonthText[i] == u"M\u00C4R" && StringContainsWord( u"MRZ"_ustr, rString, nPos))
{ // Accept MRZ for MÄR
nPos = nPos + 3;
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
} elseif (pUpperAbbrevMonthText[i] == "MRZ" && StringContainsWord( u"M\u00C4R"_ustr, rString, nPos))
{ // And vice versa, accept MÄR for MRZ
nPos = nPos + 3;
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
}
} elseif (i == 8)
{ // This assumes the weirdness is applicable to all locales. // It is the case for at least en-* and de-* locales. if (pUpperAbbrevMonthText[i] == "SEPT" && StringContainsWord( u"SEP"_ustr, rString, nPos))
{ // #102136# The correct English form of month September abbreviated is // SEPT, but almost every data contains SEP instead.
nPos = nPos + 3;
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
} elseif (pUpperAbbrevMonthText[i] == "SEP" && StringContainsWord( u"SEPT"_ustr, rString, nPos))
{ // And vice versa, accept SEPT for SEP
nPos = nPos + 4;
res = sal::static_int_cast< short >(-(i+1)); // negative break; // for
}
}
} if (!res)
{ // Brutal hack for German locales that know "Januar" or "Jänner". /* TODO: add alternative month names to locale data? if there are
* more languages... */ const LanguageTag& rLanguageTag = mrCurrentLanguageData.GetLanguageTag(); if (rLanguageTag.getLanguage() == "de")
{ if (rLanguageTag.getCountry() == "AT")
{ // Locale data has Jänner/Jän
assert(pUpperMonthText[0] == u"J\u00C4NNER"); if (StringContainsWord( u"JANUAR"_ustr, rString, nPos))
{
nPos += 6;
res = 1;
} elseif (StringContainsWord( u"JAN"_ustr, rString, nPos))
{
nPos += 3;
res = -1;
}
} else
{ // Locale data has Januar/Jan
assert(pUpperMonthText[0] == "JANUAR"); if (StringContainsWord( u"J\u00C4NNER"_ustr, rString, nPos))
{
nPos += 6;
res = 1;
} elseif (StringContainsWord( u"J\u00C4N"_ustr, rString, nPos))
{
nPos += 3;
res = -1;
}
}
}
}
}
return res;
}
/** * Converts a string containing a DayOfWeek name (Mon, Monday) at nPos into the * DayOfWeek number + 1 (negative if abbreviated), returns 0 if nothing found
*/ int ImpSvNumberInputScan::GetDayOfWeek( const OUString& rString, sal_Int32& nPos )
{ int res = 0; // no day found
if (rString.getLength() > nPos) // only if needed
{ if ( !bTextInitialized )
{
InitText();
}
sal_Int16 nDays = mrCurrentLanguageData.GetCalendar()->getNumberOfDaysInWeek(); for ( sal_Int16 i = 0; i < nDays; i++ )
{ if ( StringContainsWord( pUpperDayText[i], rString, nPos ) )
{ // full names first
nPos = nPos + pUpperDayText[i].getLength();
res = i + 1; break; // for
} if ( StringContainsWord( pUpperAbbrevDayText[i], rString, nPos ) )
{ // abbreviated
nPos = nPos + pUpperAbbrevDayText[i].getLength();
res = -(i + 1); // negative break; // for
}
}
}
return res;
}
/** * Reading a currency symbol * '$' => true * else => false
*/ bool ImpSvNumberInputScan::GetCurrency( const OUString& rString, sal_Int32& nPos )
{ if ( rString.getLength() > nPos )
{ if ( !aUpperCurrSymbol.getLength() )
{ // If no format specified the currency of the currently active locale.
LanguageType eLang = (mpFormat ? mpFormat->GetLanguage() :
mrCurrentLanguageData.GetLocaleData()->getLanguageTag().getLanguageType());
aUpperCurrSymbol = mrCurrentLanguageData.GetCharClass()->uppercase(
SvNumberFormatter::GetCurrencyEntry( eLang ).GetSymbol() );
} if ( StringContains( aUpperCurrSymbol, rString, nPos ) )
{
nPos = nPos + aUpperCurrSymbol.getLength(); returntrue;
} if ( mpFormat )
{
OUString aSymbol, aExtension; if ( mpFormat->GetNewCurrencySymbol( aSymbol, aExtension ) )
{ if ( aSymbol.getLength() <= rString.getLength() - nPos )
{
aSymbol = mrCurrentLanguageData.GetCharClass()->uppercase(aSymbol); if ( StringContains( aSymbol, rString, nPos ) )
{
nPos = nPos + aSymbol.getLength(); returntrue;
}
}
}
}
}
returnfalse;
}
/** * Reading the time period specifier (AM/PM) for the 12 hour clock * * Returns: * "AM" or "PM" => true * else => false * * nAmPos: * "AM" => 1 * "PM" => -1 * else => 0
*/ bool ImpSvNumberInputScan::GetTimeAmPm( const OUString& rString, sal_Int32& nPos )
{
/** * i counts string portions, j counts numbers thereof. * It should had been called SkipNumber instead.
*/ inlinebool ImpSvNumberInputScan::GetNextNumber( sal_uInt16& i, sal_uInt16& j ) const
{ if ( i < nStringsCnt && IsNum[i] )
{
j++;
i++; returntrue;
} returnfalse;
}
bool ImpSvNumberInputScan::GetTimeRef( double& fOutNumber,
sal_uInt16 nIndex, // j-value of the first numeric time part of input, default 0
sal_uInt16 nCnt, // count of numeric time parts
SvNumInputOptions eInputOptions
) const
{ bool bRet = true;
sal_Int32 nHour;
sal_Int32 nMinute = 0;
sal_Int32 nSecond = 0; double fSecond100 = 0.0;
sal_uInt16 nStartIndex = nIndex;
sal_Int32 nLen = sStrArray[nNums[nIndex]].getLength(); // 16-bit integer year width can have 5 digits, allow for one additional // leading zero as convention. if (nLen <= 6)
{
nYear = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32()); // A year in another, not Gregorian CE era is never expanded. // A year < 100 entered with at least 3 digits with leading 0 is taken // as is without expansion. if (mbEraCE == kDefaultEra && nYear < 100 && nLen < 3)
{
nYear = SvNumberFormatter::ExpandTwoDigitYear( nYear, nYear2000 );
}
}
return nYear;
}
bool ImpSvNumberInputScan::MayBeIso8601()
{ if (nMayBeIso8601 == 0)
{
nMayBeIso8601 = 1;
sal_Int32 nLen = ((nNumericsCnt >= 1 && nNums[0] < nStringsCnt) ? sStrArray[nNums[0]].getLength() : 0); if (nLen)
{
sal_Int32 n; if (nNumericsCnt >= 3 && nNums[2] < nStringsCnt &&
sStrArray[nNums[0]+1] == "-" && // separator year-month
(n = sStrArray[nNums[1]].toInt32()) >= 1 && n <= 12 && // month
sStrArray[nNums[1]+1] == "-" && // separator month-day
(n = sStrArray[nNums[2]].toInt32()) >= 1 && n <= 31) // day
{ // Year (nNums[0]) value not checked, may be anything, but // length (number of digits) is checked.
nMayBeIso8601 = (nLen >= 4 ? 4 : (nLen == 3 ? 3 : (nLen > 0 ? 2 : 1)));
}
}
} return nMayBeIso8601 > 1;
}
bool ImpSvNumberInputScan::CanForceToIso8601( DateOrder eDateOrder )
{ int nCanForceToIso8601 = 0; if (!MayBeIso8601())
{ returnfalse;
} elseif (nMayBeIso8601 >= 3)
{ returntrue; // at least 3 digits in year
} else
{ if (eDateOrder == DateOrder::Invalid)
{ // As if any of the cases below can be applied, but only if a // locale dependent date pattern was not matched. if ((GetDatePatternNumbers() == nNumericsCnt) && IsDatePatternNumberOfType(0,'Y')) returnfalse;
eDateOrder = GetDateOrder();
}
// No date pattern matched at all can be forced to ISO 8601 here as is. if (GetDatePatternNumbers() == 0) returntrue;
nCanForceToIso8601 = 1;
}
sal_Int32 n; switch (eDateOrder)
{ case DateOrder::DMY: // "day" value out of range => ISO 8601 year
n = sStrArray[nNums[0]].toInt32(); if (n < 1 || n > 31)
{
nCanForceToIso8601 = 2;
} break; case DateOrder::MDY: // "month" value out of range => ISO 8601 year
n = sStrArray[nNums[0]].toInt32(); if (n < 1 || n > 12)
{
nCanForceToIso8601 = 2;
} break; case DateOrder::YMD: // always possible
nCanForceToIso8601 = 2; break; default: break;
} return nCanForceToIso8601 > 1;
}
bool ImpSvNumberInputScan::MayBeMonthDate()
{ if (nMayBeMonthDate == 0)
{
nMayBeMonthDate = 1; if (nNumericsCnt >= 2 && nNums[1] < nStringsCnt)
{ // "-Jan-" const OUString& rM = sStrArray[ nNums[ 0 ] + 1 ]; if (rM.getLength() >= 3 && rM[0] == '-' && rM[ rM.getLength() - 1] == '-')
{ // Check year length assuming at least 3 digits (including // leading zero). Two digit years 1..31 are out of luck here // and may be taken as day of month. bool bYear1 = (sStrArray[nNums[0]].getLength() >= 3); bool bYear2 = (sStrArray[nNums[1]].getLength() >= 3);
sal_Int32 n; bool bDay1 = !bYear1; if (bDay1)
{
n = sStrArray[nNums[0]].toInt32();
bDay1 = n >= 1 && n <= 31;
} bool bDay2 = !bYear2; if (bDay2)
{
n = sStrArray[nNums[1]].toInt32();
bDay2 = n >= 1 && n <= 31;
}
if (bDay1 && !bDay2)
{
nMayBeMonthDate = 2; // dd-month-yy
} elseif (!bDay1 && bDay2)
{
nMayBeMonthDate = 3; // yy-month-dd
} elseif (bDay1 && bDay2)
{ // Ambiguous ##-MMM-## date, but some big vendor's database // reports write this crap, assume this always to be
nMayBeMonthDate = 2; // dd-month-yy
}
}
}
} return nMayBeMonthDate > 1;
}
/** If a string is a separator plus '-' minus sign preceding a 'Y' year in a date pattern at position nPat.
*/ staticbool lcl_IsSignedYearSep( std::u16string_view rStr, std::u16string_view rPat, sal_Int32 nPat )
{ bool bOk = false;
sal_Int32 nLen = rStr.size(); if (nLen > 1 && rStr[nLen-1] == '-')
{
--nLen; if (nPat + nLen < static_cast<sal_Int32>(rPat.size()) && rPat[nPat+nLen] == 'Y')
{ // Signed year is possible.
bOk = (rPat.find( rStr.substr( 0, nLen), nPat) == static_cast<size_t>(nPat));
}
} return bOk;
}
/** Length of separator usually is 1 but theoretically could be anything. */ static sal_Int32 lcl_getPatternSeparatorLength( std::u16string_view rPat, sal_Int32 nPat )
{
sal_Int32 nSep = nPat;
sal_Unicode c; while (nSep < static_cast<sal_Int32>(rPat.size()) && (c = rPat[nSep]) != 'D' && c != 'M' && c != 'Y')
++nSep; return nSep - nPat;
}
bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt )
{ if (nAcceptedDatePattern >= -1)
{ return (nAcceptedDatePattern >= 0);
} if (!nNumericsCnt)
{
nAcceptedDatePattern = -1;
} elseif (!sDateAcceptancePatterns.hasElements())
{ // The current locale is the format's locale, if a format is present. const NfEvalDateFormat eEDF = mrCurrentLanguageData.GetEvalDateFormat(); if (!mpFormat || eEDF == NF_EVALDATEFORMAT_FORMAT || mpFormat->GetLanguage() == mrCurrentLanguageData.GetIniLanguage())
{
sDateAcceptancePatterns = mrCurrentLanguageData.GetLocaleData()->getDateAcceptancePatterns();
} else
{
OnDemandLocaleDataWrapper& xLocaleData = mrCurrentLanguageData.GetOnDemandLocaleDataWrapper(
SvNFLanguageData::InputScannerPrivateAccess()); const LanguageTag aSaveLocale( xLocaleData->getLanguageTag() );
assert(mpFormat->GetLanguage() == aSaveLocale.getLanguageType()); // prerequisite // Obtain formatter's locale's (e.g. system) patterns.
xLocaleData.changeLocale( LanguageTag( mrCurrentLanguageData.GetIniLanguage())); const css::uno::Sequence<OUString> aLocalePatterns( xLocaleData->getDateAcceptancePatterns()); // Reset to format's locale.
xLocaleData.changeLocale( aSaveLocale); // When concatenating don't care about duplicates, combining // weeding those out reallocs yet another time and probably doesn't // take less time than looping over two additional patterns below... switch (eEDF)
{ case NF_EVALDATEFORMAT_FORMAT:
assert(!"shouldn't reach here"); break; case NF_EVALDATEFORMAT_INTL:
sDateAcceptancePatterns = aLocalePatterns; break; case NF_EVALDATEFORMAT_INTL_FORMAT:
sDateAcceptancePatterns = comphelper::concatSequences(
aLocalePatterns,
xLocaleData->getDateAcceptancePatterns()); break; case NF_EVALDATEFORMAT_FORMAT_INTL:
sDateAcceptancePatterns = comphelper::concatSequences(
xLocaleData->getDateAcceptancePatterns(),
aLocalePatterns); break;
}
}
SAL_WARN_IF( !sDateAcceptancePatterns.hasElements(), "svl.numbers", "ImpSvNumberInputScan::IsAcceptedDatePattern: no date acceptance patterns");
nAcceptedDatePattern = (sDateAcceptancePatterns.hasElements() ? -2 : -1);
}
for (sal_Int32 nPattern=0; nPattern < sDateAcceptancePatterns.getLength(); ++nPattern)
{ const OUString& rPat = sDateAcceptancePatterns[nPattern]; if (rPat.getLength() == 3)
{ // Ignore a pattern that would match numeric input with decimal // separator. It may had been read from configuration or resulted // from the locales' patterns concatenation above. if ( rPat[1] == mrCurrentLanguageData.GetLocaleData()->getNumDecimalSep().toChar()
|| rPat[1] == mrCurrentLanguageData.GetLocaleData()->getNumDecimalSepAlt().toChar())
{
SAL_WARN("svl.numbers", "ignoring date acceptance pattern with decimal separator ambiguity: " << rPat); continue; // for, next pattern
}
}
sal_uInt16 nNext = nDatePatternStart;
nDatePatternNumbers = 0; bool bOk = true;
sal_Int32 nPat = 0; for ( ; nPat < rPat.getLength() && bOk && nNext < nStringsCnt; ++nPat, ++nNext)
{ const sal_Unicode c = rPat[nPat]; switch (c)
{ case'Y': case'M': case'D':
bOk = IsNum[nNext]; if (bOk && (c == 'M' || c == 'D'))
{ // Check the D and M cases for plausibility. This also // prevents recognition of date instead of number with a // numeric group input if date separator is identical to // group separator, for example with D.M as a pattern and // #.### as a group.
sal_Int32 nMaxLen, nMaxVal; switch (c)
{ case'M':
nMaxLen = 2;
nMaxVal = nMonthsInYear; break; case'D':
nMaxLen = 2;
nMaxVal = 31; break; default: // This merely exists against // -Werror=maybe-uninitialized, which is nonsense // after the (c == 'M' || c == 'D') check above, // but ...
nMaxLen = 2;
nMaxVal = 31;
}
bOk = (sStrArray[nNext].getLength() <= nMaxLen); if (bOk)
{
sal_Int32 nNum = sStrArray[nNext].toInt32();
bOk = (1 <= nNum && nNum <= nMaxVal);
}
} if (bOk)
++nDatePatternNumbers; break; default:
bOk = !IsNum[nNext]; if (bOk)
{ const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat); // Non-numeric input must match separator exactly to be // accepted as such. const sal_Int32 nLen = sStrArray[nNext].getLength();
bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat); if (bOk)
{
nPat += nLen - 1;
} elseif ((bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat)))
{
nPat += nLen - 2;
} elseif (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ')
{ usingnamespace comphelper::string; // Trailing blanks in input.
OUStringBuffer aBuf(sStrArray[nNext]);
aBuf.stripEnd(); // Expand again in case of pattern "M. D. " and // input "M. D. ", maybe fetched far, but...
padToLength(aBuf, rPat.getLength() - nPat, ' ');
bOk = (rPat.indexOf( aBuf, nPat) == nPat); if (bOk)
{
nPat += aBuf.getLength() - 1;
}
}
} break;
}
} if (bOk)
{ // Check for trailing characters mismatch. if (nNext < nStringsCnt)
{ // Pattern end but not input end. // A trailing blank may be part of the current pattern input, // if pattern is "D.M." and input is "D.M. hh:mm" last was // ". ", or may be following the current pattern input, if // pattern is "D.M" and input is "D.M hh:mm" last was "M".
sal_Int32 nPos = 0;
sal_uInt16 nCheck; if (nPat > 0 && nNext > 0)
{ // nPat is one behind after the for loop.
sal_Int32 nPatCheck = nPat - 1; switch (rPat[nPatCheck])
{ case'Y': case'M': case'D':
nCheck = nNext; break; default:
{
nCheck = nNext - 1; // Advance position in input to match length of // non-YMD (separator) characters in pattern.
sal_Unicode c; do
{
++nPos;
c = rPat[--nPatCheck];
} while (c != 'Y' && c != 'M' && c != 'D' && nPatCheck > 0);
}
}
} else
{
nCheck = nNext;
} if (!IsNum[nCheck])
{ // Trailing (or separating if time follows) blanks are ok. // No blank and a following number is not. constbool bBlanks = SkipBlanks( sStrArray[nCheck], nPos); if (nPos == sStrArray[nCheck].getLength() && (bBlanks || !IsNum[nNext]))
{
nAcceptedDatePattern = nPattern; returntrue;
}
}
} elseif (nPat == rPat.getLength())
{ // Input end and pattern end => match.
nAcceptedDatePattern = nPattern; returntrue;
} // else Input end but not pattern end, no match.
}
}
nAcceptedDatePattern = -1; returnfalse;
}
bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear )
{ // If not initialized yet start with first number, if any. if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
{ returnfalse;
} if (nParticle < nDatePatternStart || nParticle >= nStringsCnt || IsNum[nParticle])
{ returnfalse;
}
sal_uInt16 nNext = nDatePatternStart; const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern]; for (sal_Int32 nPat = 0; nPat < rPat.getLength() && nNext < nStringsCnt; ++nPat, ++nNext)
{ switch (rPat[nPat])
{ case'Y': case'M': case'D': break; default: if (nNext == nParticle)
{ const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat); const sal_Int32 nLen = sStrArray[nNext].getLength(); bool bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat); if (!bOk)
{
bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat); if (bOk)
rSignedYear = true;
} if (!bOk && (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' '))
{ // The same ugly trailing blanks check as in // IsAcceptedDatePattern(). usingnamespace comphelper::string;
OUStringBuffer aBuf(sStrArray[nNext]);
aBuf.stripEnd();
padToLength(aBuf, rPat.getLength() - nPat, ' ');
bOk = (rPat.indexOf(aBuf, nPat) == nPat);
} if (bOk)
{
rPos = nLen; // yes, set, not add! returntrue;
} else returnfalse;
}
nPat += sStrArray[nNext].getLength() - 1; break;
}
} returnfalse;
}
sal_uInt16 ImpSvNumberInputScan::GetDatePatternNumbers()
{ // If not initialized yet start with first number, if any. if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
{ return 0;
} return nDatePatternNumbers;
}
LongDateOrder eLDO; const sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0); if (!nExactDateOrder)
eLDO = pLoc->getLongDateOrder(); elseif ((((nExactDateOrder >> 16) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
eLDO = LongDateOrder::YMD; elseif ((((nExactDateOrder >> 16) & 0xff) == 'D') && ((nExactDateOrder & 0xff) == 'Y'))
eLDO = LongDateOrder::DMY; else
eLDO = pLoc->getLongDateOrder(); if (eLDO != LongDateOrder::YMD && eLDO != LongDateOrder::DMY)
{ switch (eDateOrder)
{ case DateOrder::YMD:
eLDO = LongDateOrder::YMD; break; case DateOrder::DMY:
eLDO = LongDateOrder::DMY; break; default:
; // nothing, not a date
}
} elseif (eLDO == LongDateOrder::DMY && eDateOrder == DateOrder::YMD)
{ // Check possible order and maybe switch. if (!ImplGetDay(0) && ImplGetDay(1))
eLDO = LongDateOrder::YMD;
} elseif (eLDO == LongDateOrder::YMD && eDateOrder == DateOrder::DMY)
{ // Check possible order and maybe switch. if (!ImplGetDay(1) && ImplGetDay(0))
eLDO = LongDateOrder::DMY;
} return eLDO;
}
bool ImpSvNumberInputScan::GetDateRef( double& fDays, sal_uInt16& nCounter )
{ usingnamespace ::com::sun::star::i18n;
NfEvalDateFormat eEDF; int nFormatOrder; if ( mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE) )
{
eEDF = mrCurrentLanguageData.GetEvalDateFormat(); switch ( eEDF )
{ case NF_EVALDATEFORMAT_INTL : case NF_EVALDATEFORMAT_FORMAT :
nFormatOrder = 1; // only one loop break; default:
nFormatOrder = 2; if ( nMatchedAllStrings )
{
eEDF = NF_EVALDATEFORMAT_FORMAT_INTL; // we have a complete match, use it
}
}
} else
{
eEDF = NF_EVALDATEFORMAT_INTL;
nFormatOrder = 1;
} bool res = true;
const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData();
CalendarWrapper* pCal = mrCurrentLanguageData.GetCalendar(); const DateTime aToday{ DateTime( Date( Date::SYSTEM))}; for ( int nTryOrder = 1; nTryOrder <= nFormatOrder; nTryOrder++ )
{
pCal->setGregorianDateTime( aToday); // today
OUString aOrgCalendar; // empty => not changed yet
DateOrder DateFmt; bool bFormatTurn; switch ( eEDF )
{ case NF_EVALDATEFORMAT_INTL :
bFormatTurn = false;
DateFmt = GetDateOrder(); break; case NF_EVALDATEFORMAT_FORMAT :
bFormatTurn = true;
DateFmt = mpFormat->GetDateOrder(); break; case NF_EVALDATEFORMAT_INTL_FORMAT : if ( nTryOrder == 1 )
{
bFormatTurn = false;
DateFmt = GetDateOrder();
} else
{
bFormatTurn = true;
DateFmt = mpFormat->GetDateOrder();
} break; case NF_EVALDATEFORMAT_FORMAT_INTL : if ( nTryOrder == 2 )
{
bFormatTurn = false;
DateFmt = GetDateOrder();
} else
{
bFormatTurn = true; // Even if the format pattern is to be preferred, the input may // have matched a pattern of the current locale, which then // again is to be preferred. Both date orders can be different // so we need to obtain the actual match. For example ISO // YYYY-MM-DD format vs locale's DD.MM.YY input. // If no pattern was matched, obtain from format. // Note that patterns may have been constructed from the // format's locale and prepended to the current locale's // patterns, it doesn't necessarily mean a current locale's // pattern was matched, but may if the format's locale's // patterns didn't match, which were tried first.
DateFmt = GetDateOrder(true);
} break; default:
SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetDateRef: unknown NfEvalDateFormat" );
DateFmt = DateOrder::YMD;
bFormatTurn = false;
} if ( bFormatTurn )
{ /* TODO: We are currently not able to fully support a switch to another calendar during input for the following reasons: 1. We do have a problem if both (locale's default and format's) calendars define the same YMD order and use the same date separator, there is no way to distinguish between them if the input results in valid calendar input for both calendars. How to solve? Would NfEvalDateFormat be sufficient? Should it always be set to NF_EVALDATEFORMAT_FORMAT_INTL and thus the format's calendar be preferred? This could be confusing if a Calc cell was formatted different to the locale's default and has no content yet, then the user has no clue about the format or calendar being set. 2. In Calc cell edit mode a date is always displayed and edited using the default edit format of the default calendar (normally being Gregorian). If input was ambiguous due to issue #1 we'd need a mechanism to tell that a date was edited and not newly entered. Not feasible. Otherwise we'd need a mechanism to use a specific edit format with a specific calendar according to the format set. 3. For some calendars like Japanese Gengou we'd need era input, which isn't implemented at all. Though this is a rare and special case, forcing a calendar dependent edit format as suggested in item #2 might require era input, if it shouldn't result in a fallback to Gregorian calendar. 4. Last and least: the GetMonth() method currently only matches month names of the default calendar. Alternating month names of the actual format's calendar would have to be implemented. No problem.
res = true;
nCounter = 0; // For incomplete dates, always assume first day of month if not specified.
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
switch (nNumericsCnt) // count of numbers in string
{ case 0: // none if (nMonthPos) // only month (Jan)
{
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
} else
{
res = false;
} break;
case 1: // only one number
nCounter = 1; switch (nMonthPos) // where is the month
{ case 0: // not found
{ // If input matched a date pattern, use the pattern // to determine if it is a day, month or year. The // pattern should have only one single value then, // 'D-', 'M-' or 'Y-'. If input did not match a // pattern assume the usual day of current month.
sal_uInt32 nDateOrder = (bFormatTurn ?
mpFormat->GetExactDateOrder() :
GetDatePatternOrder()); switch (nDateOrder)
{ case'Y':
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; case'M':
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); break; case'D': default:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); break;
} break;
} case 1: // month at the beginning (Jan 01)
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); switch (DateFmt)
{ case DateOrder::MDY: case DateOrder::YMD: if (sal_uInt16 day = ImplGetDay(0); day > 0 && day <= 32) // Why 32?
{
pCal->setValue(CalendarFieldIndex::DAY_OF_MONTH, day); break;
} // Parse 'june-2007' as June 1 2007
[[fallthrough]]; case DateOrder::DMY:
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; default:
res = false; break;
} break; case 3: // month at the end (10 Jan)
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); switch (DateFmt)
{ case DateOrder::DMY:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); break; case DateOrder::YMD:
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; default:
res = false; break;
} break; default:
res = false; break;
} // switch (nMonthPos) break;
case 2: // 2 numbers
nCounter = 2; switch (nMonthPos) // where is the month
{ case 0: // not found
{
sal_uInt32 nExactDateOrder = (bFormatTurn ?
mpFormat->GetExactDateOrder() :
GetDatePatternOrder()); bool bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff); if (!bIsExact && bFormatTurn && IsAcceptedDatePattern( nNums[0]))
{ // If input does not match format but pattern, use pattern // instead, even if eEDF==NF_EVALDATEFORMAT_FORMAT_INTL. // For example, format has "Y-M-D" and pattern is "D.M.", // input with 2 numbers can't match format and 31.12. would // lead to 1931-12-01 (fdo#54344)
nExactDateOrder = GetDatePatternOrder();
bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff);
} bool bHadExact; if (bIsExact)
{ // formatted as date and exactly 2 parts
bHadExact = true; switch ( (nExactDateOrder >> 8) & 0xff )
{ case'Y':
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; case'M':
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); break; case'D':
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); break; default:
bHadExact = false;
} switch ( nExactDateOrder & 0xff )
{ case'Y':
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); break; case'M':
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); break; case'D':
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); break; default:
bHadExact = false;
}
SAL_WARN_IF( !bHadExact, "svl.numbers", "ImpSvNumberInputScan::GetDateRef: error in exact date order");
} else
{
bHadExact = false;
} // If input matched against a date acceptance pattern // do not attempt to mess around with guessing the // order, either it matches or it doesn't. if ((bFormatTurn || !bIsExact) && (!bHadExact || !pCal->isValid()))
{ if ( !bHadExact && nExactDateOrder )
{
pCal->setGregorianDateTime( aToday); // reset today
} switch (DateFmt)
{ case DateOrder::MDY:
{ // M D constauto month = ImplGetMonth(0);
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::MONTH, month ); if ( !pCal->isValid() ) // 2nd try
{ // M Y
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
pCal->setValue( CalendarFieldIndex::MONTH, month );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
} break;
} case DateOrder::DMY: // D M
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); if ( !pCal->isValid() ) // 2nd try
{ // M Y
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
} break; case DateOrder::YMD: // M D
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); if ( !pCal->isValid() ) // 2nd try
{ // Y M
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
} break; default:
res = false; break;
}
}
} break; case 1: // month at the beginning (Jan 01 01)
{ // The input is valid as MDY in almost any // constellation, there is no date order (M)YD except if // set in a format applied.
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0); if ((((nExactDateOrder >> 8) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
} else
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
} break;
} case 2: // month in the middle (10 Jan 94)
{
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt); switch (eLDO)
{ case LongDateOrder::DMY:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); break; case LongDateOrder::YMD:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; default:
res = false; break;
} break;
} case 3: // month at the end (94 10 Jan) if (pLoc->getLongDateOrder() != LongDateOrder::YDM)
res = false; else
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
} break; default:
res = false; break;
} // switch (nMonthPos) break;
default: // more than two numbers (31.12.94 8:23) (31.12. 8:23) switch (nMonthPos) // where is the month
{ case 0: // not found
{
nCounter = 3; if ( nTimePos > 1 )
{ // find first time number index (should only be 3 or 2 anyway) for ( sal_uInt16 j = 0; j < nNumericsCnt; j++ )
{ if ( nNums[j] == nTimePos - 2 )
{
nCounter = j; break; // for
}
}
} // ISO 8601 yyyy-mm-dd forced recognition
DateOrder eDF = (CanForceToIso8601( DateFmt) ? DateOrder::YMD : DateFmt); switch (eDF)
{ case DateOrder::MDY:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); if ( nCounter > 2 )
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) ); break; case DateOrder::DMY:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); if ( nCounter > 2 )
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) ); break; case DateOrder::YMD: if ( nCounter > 2 )
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(2) );
pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; default:
res = false; break;
} break;
} case 1: // month at the beginning (Jan 01 01 8:23)
{
nCounter = 2; // The input is valid as MDY in almost any // constellation, there is no date order (M)YD except if // set in a format applied.
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0); if ((((nExactDateOrder >> 8) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
} else
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
} break;
} case 2: // month in the middle (10 Jan 94 8:23)
{
nCounter = 2;
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt); switch (eLDO)
{ case LongDateOrder::DMY:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); break; case LongDateOrder::YMD:
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); break; default:
res = false; break;
} break;
} case 3: // month at the end (94 10 Jan 8:23)
nCounter = 2; if (pLoc->getLongDateOrder() != LongDateOrder::YDM)
res = false; else
{
pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
} break; default:
nCounter = 2;
res = false; break;
} // switch (nMonthPos) break;
} // switch (nNumericsCnt)
// First of all, eat leading blanks
SkipBlanks(rString, nPos);
// Yes, nMatchedAllStrings should know about the sign position
nSign = GetSign(rString, nPos); if ( nSign ) // sign?
{
SkipBlanks(rString, nPos);
} // #102371# match against format string only if start string is not a sign character if ( nMatchedAllStrings && !(nSign && rString.getLength() == 1) )
{ // Match against format in any case, so later on for a "x1-2-3" input // we may distinguish between a xy-m-d (or similar) date and a x0-0-0 // format. No sign detection here! if ( ScanStringNumFor( rString, nPos, 0, true ) )
{
nMatchedAllStrings |= nMatchedStartString;
} else
{
nMatchedAllStrings = 0;
}
}
// Bail out early for just a sign. if (nSign && nPos == rString.getLength()) returntrue;
const sal_Int32 nStartBlanks = nPos; if ( GetDecSep(rString, nPos) ) // decimal separator in start string
{ if (SkipBlanks(rString, nPos))
nPos = nStartBlanks; // `. 2` not a decimal separator else
nDecPos = 1; // leading decimal separator
} elseif ( GetCurrency(rString, nPos) ) // currency (DM 1)?
{
eScannedType = SvNumFormatType::CURRENCY; // !!! it IS currency !!!
SkipBlanks(rString, nPos); if (nSign == 0) // no sign yet
{
nSign = GetSign(rString, nPos); if ( nSign ) // DM -1
{
SkipBlanks(rString, nPos);
}
} if ( GetDecSep(rString, nPos) ) // decimal separator follows currency
{ if (SkipBlanks(rString, nPos))
{
nPos = nStartBlanks; // `DM . 2` not a decimal separator
eScannedType = SvNumFormatType::UNDEFINED; // !!! it is NOT currency !!!
} else
nDecPos = 1; // leading decimal separator
}
} else
{ const sal_Int32 nMonthStart = nPos; short nTempMonth = GetMonth(rString, nPos); if (nTempMonth < 0)
{ // Short month and day names may be identical in some locales, e.g. // "mar" for "martes" or "marzo" in Spanish. // Do not let a month name immediately take precedence if a day // name was meant instead. Assume that both could be valid, until // encountered differently or the final evaluation in // IsNumberFormat() checks, but continue with weighing the month // name higher unless we have both day of week and month name here.
sal_Int32 nTempPos = nMonthStart;
nDayOfWeek = GetDayOfWeek( rString, nTempPos); if (nDayOfWeek < 0)
{
SkipChar( '.', rString, nTempPos ); // abbreviated
SkipString( mrCurrentLanguageData.GetLocaleData()->getLongDateDayOfWeekSep(), rString, nTempPos );
SkipBlanks( rString, nTempPos); short nTempTempMonth = GetMonth( rString, nTempPos); if (nTempTempMonth)
{ // Fall into the else branch below that handles both.
nTempMonth = 0;
nPos = nMonthStart;
nDayOfWeek = 0; // Do not set nDayOfWeek hereafter, anywhere.
}
}
} if ( nTempMonth ) // month (Jan 1)?
{ // Jan1 without separator is not a date, unless it is followed by a // separator and a (year) number. if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2))
{
eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!!
nMonth = nTempMonth;
nMonthPos = 1; // month at the beginning if ( nMonth < 0 )
{
SkipChar( '.', rString, nPos ); // abbreviated
}
SkipBlanks(rString, nPos);
} else
{
nPos = nMonthStart; // rewind month
}
} else
{ int nTempDayOfWeek = GetDayOfWeek( rString, nPos ); if ( nTempDayOfWeek )
{ // day of week is just parsed away
eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!! if ( nPos < rString.getLength() )
{ if ( nTempDayOfWeek < 0 )
{ // abbreviated if ( rString[ nPos ] == '.' )
{
++nPos;
}
} else
{ // full long name
SkipBlanks(rString, nPos);
SkipString( mrCurrentLanguageData.GetLocaleData()->getLongDateDayOfWeekSep(), rString, nPos );
}
SkipBlanks(rString, nPos);
nTempMonth = GetMonth(rString, nPos); if ( nTempMonth ) // month (Jan 1)?
{ // Jan1 without separator is not a date, unless it is followed by a // separator and a (year) number. if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2))
{
nMonth = nTempMonth;
nMonthPos = 1; // month at the beginning if ( nMonth < 0 )
{
SkipChar( '.', rString, nPos ); // abbreviated
}
SkipBlanks(rString, nPos);
} else
{
nPos = nMonthStart; // rewind month
}
}
} if (!nMonth)
{ // Determine and remember following date pattern, if any.
IsAcceptedDatePattern( 1);
}
}
} // Skip one trailing '-' or '/' character to recognize June-2007 if (nMonth && nPos + 1 == rString.getLength())
{
SkipChar('-', rString, nPos) || SkipChar('/', rString, nPos);
}
}
if (nPos < rString.getLength()) // not everything consumed
{ // Does input StartString equal StartString of format? // This time with sign detection! if ( !ScanStringNumFor( rString, nPos, 0 ) )
{ return MatchedReturn();
}
}
returntrue;
}
staticbool lcl_isBlanks( const OUString& rStr )
{ if (rStr.isEmpty()) returnfalse;
for (sal_Int32 i = rStr.getLength(); i-- > 0; )
{ if (rStr[i] != ' ') returnfalse;
} returntrue;
}
if ( nMatchedAllStrings )
{ // Match against format in any case, so later on for a "1-2-3-4" input // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0 // format. if ( ScanStringNumFor( rString, 0, nStringPos ) )
{
nMatchedAllStrings |= nMatchedMidString;
} else
{
nMatchedAllStrings = 0;
}
}
const sal_Int32 nStartBlanks = nPos; constbool bBlanks = SkipBlanks(rString, nPos); if (GetDecSep(rString, nPos)) // decimal separator?
{ if (nDecPos == 1 || nDecPos == 3) // .12.4 or 1.E2.1
{ return MatchedReturn();
} elseif (nDecPos == 2) // . dup: 12.4.
{ bool bSignedYear = false; if (bDecSepInDateSeps || // . also date separator
SkipDatePatternSeparator( nStringPos, nPos, bSignedYear))
{ if ( eScannedType != SvNumFormatType::UNDEFINED &&
eScannedType != SvNumFormatType::DATE &&
eScannedType != SvNumFormatType::DATETIME) // already another type
{ return MatchedReturn();
} if (eScannedType == SvNumFormatType::UNDEFINED)
{
eScannedType = SvNumFormatType::DATE; // !!! it IS a date
}
SkipBlanks(rString, nPos);
} else
{ return MatchedReturn();
}
} elseif (bBlanks)
{ // `1 .2` or `1 . 2` not a decimal separator, reset
nPos = nStartBlanks;
} elseif (SkipBlanks(rString, nPos))
{ // `1. 2` not a decimal separator, reset
nPos = nStartBlanks;
} else
{
nDecPos = 2; // . in mid string
}
} elseif ( (eScannedType & SvNumFormatType::TIME) &&
GetTime100SecSep( rString, nPos ) )
{ // hundredth seconds separator if ( nDecPos )
{ return MatchedReturn();
}
nDecPos = 2; // . in mid string
// If this is exactly an ISO 8601 fractional seconds separator, bail // out early to not get confused by later checks for group separator or // other. if (bIso8601Tsep && nPos == rString.getLength() &&
eScannedType == SvNumFormatType::DATETIME && (rString == "." || rString == ",")) returntrue;
SkipBlanks(rString, nPos);
}
if (SkipChar('/', rString, nPos)) // fraction?
{ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
eScannedType != SvNumFormatType::DATE) // except date
{ return MatchedReturn(); // => jan/31/1994
} elseif (eScannedType != SvNumFormatType::DATE && // analyzed no date until now
(eSetType == SvNumFormatType::FRACTION || // and preset was fraction
(nNumericsCnt == 3 && // or 3 numbers
(nStringPos == 3 || // and 4th string particle
(nStringPos == 4 && nSign)) && // or 5th if signed
lcl_isBlanks(sStrArray[nStringPos-2])))) // and not 23/11/1999 // that was not accepted as date yet, // nor anything else than blanks after integer.
{
SkipBlanks(rString, nPos); if (nPos == rString.getLength())
{
eScannedType = SvNumFormatType::FRACTION; // !!! it IS a fraction (so far) if (eSetType == SvNumFormatType::FRACTION &&
nNumericsCnt == 2 &&
(nStringPos == 1 || // for 4/5
(nStringPos == 2 && nSign))) // or signed -4/5
{ returntrue; // don't fall into date trap
}
}
} else
{
nPos--; // put '/' back
}
}
if (GetThousandSep(rString, nPos, nStringPos)) // 1,000
{ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
eScannedType != SvNumFormatType::CURRENCY) // except currency
{ return MatchedReturn();
}
nThousand++;
}
const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData(); bool bSignedYear = false; bool bDate = SkipDatePatternSeparator( nStringPos, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999 if (!bDate)
{ const OUString& rDate = mrCurrentLanguageData.GetDateSep();
SkipBlanks(rString, nPos);
bDate = SkipString( rDate, rString, nPos); // 10. 10- 10/
} if (!bDate && nStringPos == 1 && mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE))
{ // If a DMY format was given and a mid string starts with a literal // ". " dot+space and could contain a following month name and ends // with a space or LongDateMonthSeparator, like it's scanned in // `14". AUG "18`, then it may be a date as well. Regardless whether // defined such by the locale or not. // This *could* check for presence of ". "MMM or ". "MMMM in the actual // format code for further restriction to match only if present, but..
const sal_uInt32 nExactDateOrder = mpFormat->GetExactDateOrder(); // Exactly DMY. if (((nExactDateOrder & 0xff) == 'Y') && (((nExactDateOrder >> 8) & 0xff) == 'M')
&& (((nExactDateOrder >> 16) & 0xff) == 'D'))
{ const sal_Int32 nTmpPos = nPos; if (SkipChar('.', rString, nPos) && SkipBlanks(rString, nPos) && nPos + 2 < rString.getLength()
&& (rString.endsWith(" ") || rString.endsWith( pLoc->getLongDateMonthSep())))
bDate = true; else
nPos = nTmpPos;
}
} if (bDate || ((MayBeIso8601() || MayBeMonthDate()) && // 1999-12-31 31-Dec-1999
SkipChar( '-', rString, nPos)))
{ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
eScannedType != SvNumFormatType::DATE) // except date
{ return MatchedReturn();
}
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::DATE; // !!! it IS a date short nTmpMonth = GetMonth(rString, nPos); // 10. Jan 94 if (nMonth && nTmpMonth) // month dup
{ return MatchedReturn();
} if (nTmpMonth)
{
nMonth = nTmpMonth;
nMonthPos = 2; // month in the middle if ( nMonth < 0 && SkipChar( '.', rString, nPos ) )
; // short month may be abbreviated Jan. elseif ( SkipChar( '-', rString, nPos ) )
; // #79632# recognize 17-Jan-2001 to be a date // #99065# short and long month name else
{
SkipString( pLoc->getLongDateMonthSep(), rString, nPos );
}
SkipBlanks(rString, nPos);
} if (bSignedYear)
{ if (mbEraCE != kDefaultEra) // signed year twice? return MatchedReturn();
mbEraCE = false; // BCE
}
}
const sal_Int32 nMonthStart = nPos; short nTempMonth = GetMonth(rString, nPos); // month in the middle (10 Jan 94) or at the end (94 10 Jan) if (nTempMonth)
{ if (nMonth != 0) // month dup
{ return MatchedReturn();
} if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
eScannedType != SvNumFormatType::DATE) // except date
{ return MatchedReturn();
} if (nMonthStart > 0 && nPos < rString.getLength()) // 10Jan or Jan94 without separator are not dates
{
eScannedType = SvNumFormatType::DATE; // !!! it IS a date
nMonth = nTempMonth; if (nCurNumCount <= 1)
nMonthPos = 2; // month in the middle else
nMonthPos = 3; // month at the end if ( nMonth < 0 )
{
SkipChar( '.', rString, nPos ); // abbreviated
}
SkipString( pLoc->getLongDateMonthSep(), rString, nPos );
SkipBlanks(rString, nPos);
} else
{
nPos = nMonthStart; // rewind month
}
}
if ( SkipChar('E', rString, nPos) || // 10E, 10e, 10,Ee
SkipChar('e', rString, nPos) )
{ if (eScannedType != SvNumFormatType::UNDEFINED) // already another type
{ return MatchedReturn();
} else
{
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::SCIENTIFIC; // !!! it IS scientific if ( nThousand+2 == nNumericsCnt && nDecPos == 2 ) // special case 1.E2
{
nDecPos = 3; // 1,100.E2 1,100,100.E3
}
}
nESign = GetESign(rString, nPos); // signed exponent?
SkipBlanks(rString, nPos);
}
const OUString& rTime = pLoc->getTimeSep(); if ( SkipString(rTime, rString, nPos) ) // time separator?
{ if (nDecPos) // already . => maybe error
{ if (bDecSepInDateSeps) // . also date sep
{ if ( eScannedType != SvNumFormatType::DATE && // already another type than date
eScannedType != SvNumFormatType::DATETIME) // or date time
{ return MatchedReturn();
} if (eScannedType == SvNumFormatType::DATE)
{
nDecPos = 0; // reset for time transition
}
} else
{ return MatchedReturn();
}
} if ((eScannedType == SvNumFormatType::DATE || // already date type
eScannedType == SvNumFormatType::DATETIME) && // or date time
nNumericsCnt > 3) // and more than 3 numbers? (31.Dez.94 8:23)
{
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::DATETIME; // !!! it IS date with time
} elseif ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
eScannedType != SvNumFormatType::TIME) // except time
{ return MatchedReturn();
} else
{
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::TIME; // !!! it IS a time
} if ( !nTimePos )
{
nTimePos = nStringPos + 1;
}
}
if (nPos < rString.getLength())
{ switch (eScannedType)
{ case SvNumFormatType::DATE: if (nMonthPos == 1 && pLoc->getLongDateOrder() == LongDateOrder::MDY)
{ // #68232# recognize long date separators like ", " in "September 5, 1999" if (SkipString( pLoc->getLongDateDaySep(), rString, nPos ))
{
SkipBlanks( rString, nPos );
}
} elseif (nPos == 0 && rString.getLength() == 1 && MayBeIso8601())
{ if ( (nStringPos == 5 && rString[0] == 'T') ||
(nStringPos == 6 && rString[0] == 'T' && sStrArray[0] == "-"))
{ // ISO 8601 combined date and time, yyyy-mm-ddThh:mm or -yyyy-mm-ddThh:mm
++nPos;
bIso8601Tsep = true;
} elseif (nStringPos == 7 && rString[0] == ':')
{ // ISO 8601 combined date and time, the time part; we reach // here if the locale's separator is not ':' so it couldn't // be detected above in the time block. if (nNumericsCnt >= 5)
eScannedType = SvNumFormatType::DATETIME;
++nPos;
}
} break; case SvNumFormatType::DATETIME: if (nPos == 0 && rString.getLength() == 1 && MayBeIso8601())
{ if (nStringPos == 9 && rString[0] == ':')
{ // ISO 8601 combined date and time, the time part continued.
++nPos;
}
} #if NF_RECOGNIZE_ISO8601_TIMEZONES elseif (nPos == 0 && rString.getLength() == 1 && nStringPos >= 9 && MayBeIso8601())
{ // ISO 8601 timezone offset switch (rString[ 0 ])
{ case'+': case'-': if (nStringPos == nStringsCnt - 2 ||
nStringPos == nStringsCnt - 4)
{
++nPos; // yyyy-mm-ddThh:mm[:ss]+xx[[:]yy] // nTimezonePos needed for GetTimeRef() if (!nTimezonePos)
{
nTimezonePos = nStringPos + 1;
}
} break; case':': if (nTimezonePos && nStringPos >= 11 &&
nStringPos == nStringsCnt - 2)
{
++nPos; // yyyy-mm-ddThh:mm[:ss]+xx:yy
} break;
}
} #endif break; default: break;
}
}
if (nPos < rString.getLength()) // not everything consumed?
{ if ( nMatchedAllStrings & ~nMatchedVirgin )
{
eScannedType = eOldScannedType;
} else
{ returnfalse;
}
}
returntrue;
}
/** * Analyze the end * All gone => true * else => false
*/ bool ImpSvNumberInputScan::ScanEndString( const OUString& rString )
{
sal_Int32 nPos = 0;
if ( nMatchedAllStrings )
{ // Match against format in any case, so later on for a "1-2-3-4" input // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0 // format. if ( ScanStringNumFor( rString, 0, 0xFFFF ) )
{
nMatchedAllStrings |= nMatchedEndString;
} else
{
nMatchedAllStrings = 0;
}
}
const sal_Int32 nStartBlanks = nPos; constbool bBlanks = SkipBlanks(rString, nPos); if (GetDecSep(rString, nPos)) // decimal separator?
{ if (nDecPos == 1 || nDecPos == 3) // .12.4 or 12.E4.
{ return MatchedReturn();
} elseif (nDecPos == 2) // . dup: 12.4.
{ bool bSignedYear = false; if (bDecSepInDateSeps || // . also date separator
SkipDatePatternSeparator( nStringsCnt-1, nPos, bSignedYear))
{ if ( eScannedType != SvNumFormatType::UNDEFINED &&
eScannedType != SvNumFormatType::DATE &&
eScannedType != SvNumFormatType::DATETIME) // already another type
{ return MatchedReturn();
} if (eScannedType == SvNumFormatType::UNDEFINED)
{
eScannedType = SvNumFormatType::DATE; // !!! it IS a date
}
SkipBlanks(rString, nPos);
} else
{ return MatchedReturn();
}
} elseif (bBlanks)
{ // not a decimal separator, reset
nPos = nStartBlanks;
} else
{
nDecPos = 3; // . in end string
SkipBlanks(rString, nPos);
}
}
bool bSignDetectedHere = false; if ( nSign == 0 && // conflict - not signed
eScannedType != SvNumFormatType::DATE) // and not date //!? catch time too?
{ // not signed yet
nSign = GetSign(rString, nPos); // 1- DM if (bNegCheck) // '(' as sign
{ return MatchedReturn();
} if (nSign)
{
bSignDetectedHere = true;
}
}
SkipBlanks(rString, nPos); if (bNegCheck && SkipChar(')', rString, nPos)) // skip ')' if appropriate
{
bNegCheck = false;
SkipBlanks(rString, nPos);
}
if ( GetCurrency(rString, nPos) ) // currency symbol?
{ if (eScannedType != SvNumFormatType::UNDEFINED) // currency dup
{ return MatchedReturn();
} else
{
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::CURRENCY;
} // behind currency a '-' is allowed if (nSign == 0) // not signed yet
{
nSign = GetSign(rString, nPos); // DM -
SkipBlanks(rString, nPos); if (bNegCheck) // 3 DM (
{ return MatchedReturn();
}
} if ( bNegCheck && eScannedType == SvNumFormatType::CURRENCY &&
SkipChar(')', rString, nPos) )
{
bNegCheck = false; // ')' skipped
SkipBlanks(rString, nPos); // only if currency
}
}
if ( SkipChar('%', rString, nPos) ) // 1%
{ if (eScannedType != SvNumFormatType::UNDEFINED) // already another type
{ return MatchedReturn();
}
SkipBlanks(rString, nPos);
eScannedType = SvNumFormatType::PERCENT;
}
/** * Recognizes types of number, exponential, fraction, percent, currency, date, time. * Else text => return false
*/ bool ImpSvNumberInputScan::IsNumberFormatMain( const OUString& rString, // string to be analyzed const SvNumberformat* pFormat ) // maybe number format set to match against
{
Reset();
mpFormat = pFormat;
NumberStringDivision( rString ); // breakdown into strings and numbers if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS) // too many elements
{ returnfalse; // Njet, Nope, ...
} if (nNumericsCnt == 0) // no number in input
{ if ( nStringsCnt > 0 )
{ // Here we may change the original, we don't need it anymore. // This saves copies and ToUpper() in GetLogical() and is faster.
sStrArray[0] = comphelper::string::strip(sStrArray[0], ' ');
OUString& rStrArray = sStrArray[0];
nLogical = GetLogical( rStrArray ); if ( nLogical )
{
eScannedType = SvNumFormatType::LOGICAL; // !!! it's a BOOLEAN
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
} else
{ returnfalse; // simple text
}
} else
{ returnfalse; // simple text
}
}
sal_uInt16 i = 0; // mark any symbol
sal_uInt16 j = 0; // mark only numbers
switch ( nNumericsCnt )
{ case 1 : // Exactly 1 number in input // nStringsCnt >= 1 if (GetNextNumber(i,j)) // i=1,0
{ // Number at start if (eSetType == SvNumFormatType::FRACTION) // Fraction 1 = 1/1
{ if (i >= nStringsCnt || // no end string nor decimal separator
mrCurrentLanguageData.IsDecimalSep( sStrArray[i]))
{
eScannedType = SvNumFormatType::FRACTION;
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
}
}
} else
{ // Analyze start string if (!ScanStartString( sStrArray[i] )) // i=0
{ returnfalse; // already an error
}
i++; // next symbol, i=1
}
GetNextNumber(i,j); // i=1,2 if (eSetType == SvNumFormatType::FRACTION) // Fraction -1 = -1/1
{ if (nSign && !bNegCheck && // Sign +, -
eScannedType == SvNumFormatType::UNDEFINED && // not date or currency
nDecPos == 0 && // no previous decimal separator
(i >= nStringsCnt || // no end string nor decimal separator
mrCurrentLanguageData.IsDecimalSep( sStrArray[i]))
)
{
eScannedType = SvNumFormatType::FRACTION;
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
}
} if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
{ returnfalse;
} break; case 2 : // Exactly 2 numbers in input // nStringsCnt >= 3 if (!GetNextNumber(i,j)) // i=1,0
{ // Analyze start string if (!ScanStartString( sStrArray[i] )) returnfalse; // already an error
i++; // i=1
}
GetNextNumber(i,j); // i=1,2 if ( !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++; // next symbol, i=2,3
GetNextNumber(i,j); // i=3,4 if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
{ returnfalse;
} if (eSetType == SvNumFormatType::FRACTION) // -1,200. as fraction
{ if (!bNegCheck && // no sign '('
eScannedType == SvNumFormatType::UNDEFINED &&
(nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
)
{
eScannedType = SvNumFormatType::FRACTION;
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
}
} break; case 3 : // Exactly 3 numbers in input // nStringsCnt >= 5 if (!GetNextNumber(i,j)) // i=1,0
{ // Analyze start string if (!ScanStartString( sStrArray[i] ))
{ returnfalse; // already an error
}
i++; // i=1 if (nDecPos == 1) // decimal separator at start => error
{ returnfalse;
}
}
GetNextNumber(i,j); // i=1,2 if ( !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++; // i=2,3 if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end
{ returnfalse;
}
GetNextNumber(i,j); // i=3,4 if ( !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++; // i=4,5
GetNextNumber(i,j); // i=5,6 if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
{ returnfalse;
} if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction
{ if (!bNegCheck && // no sign '('
eScannedType == SvNumFormatType::UNDEFINED &&
(nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
)
{
eScannedType = SvNumFormatType::FRACTION;
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
}
} if ( eScannedType == SvNumFormatType::FRACTION && nDecPos )
{ returnfalse; // #36857# not a real fraction
} break; default: // More than 3 numbers in input // nStringsCnt >= 7 if (!GetNextNumber(i,j)) // i=1,0
{ // Analyze startstring if (!ScanStartString( sStrArray[i] )) returnfalse; // already an error
i++; // i=1 if (nDecPos == 1) // decimal separator at start => error returnfalse;
}
GetNextNumber(i,j); // i=1,2 if ( !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++; // i=2,3
{
sal_uInt16 nThOld = 10; // just not 0 or 1 while (nThOld != nThousand && j < nNumericsCnt-1) // Execute at least one time // but leave one number.
{ // Loop over group separators
nThOld = nThousand; if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end
{ returnfalse;
}
GetNextNumber(i,j); if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++;
}
} if (eScannedType == SvNumFormatType::DATE || // long date or
eScannedType == SvNumFormatType::TIME || // long time or
eScannedType == SvNumFormatType::UNDEFINED) // long number
{ for (sal_uInt16 k = j; k < nNumericsCnt-1; k++)
{ if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at endd
{ returnfalse;
}
GetNextNumber(i,j); if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i, j ) )
{ returnfalse;
}
i++;
}
}
GetNextNumber(i,j); if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
{ returnfalse;
} if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction
{ if (!bNegCheck && // no sign '('
eScannedType == SvNumFormatType::UNDEFINED &&
(nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
)
{
eScannedType = SvNumFormatType::FRACTION;
nMatchedAllStrings &= ~nMatchedVirgin; returntrue;
}
} if ( eScannedType == SvNumFormatType::FRACTION && nDecPos )
{ returnfalse; // #36857# not a real fraction
} break;
}
if (eScannedType == SvNumFormatType::UNDEFINED)
{
nMatchedAllStrings &= ~nMatchedVirgin; // did match including nMatchedUsedAsReturn bool bDidMatch = (nMatchedAllStrings != 0); if ( nMatchedAllStrings )
{ bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual(
nStringScanNumFor, nStringsCnt, nNumericsCnt ); if ( !bMatch )
{
nMatchedAllStrings = 0;
}
} if ( nMatchedAllStrings )
{ // A type DEFINED means that no category could be assigned to the // overall format because of mixed type subformats. Use the scan // matched subformat's type if any.
SvNumFormatType eForType = eSetType; if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat)
eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor); if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED)
eScannedType = eForType; else
eScannedType = SvNumFormatType::NUMBER;
} elseif ( bDidMatch )
{ // Accept a plain fractional number like 123.45 as there may be a // decimal separator also present as literal like in a 0"."0 weirdo // format. if (nDecPos != 2 || nNumericsCnt != 2) returnfalse;
eScannedType = SvNumFormatType::NUMBER;
} else
{
eScannedType = SvNumFormatType::NUMBER; // everything else should have been recognized by now
}
} elseif ( eScannedType == SvNumFormatType::DATE )
{ // the very relaxed date input checks may interfere with a preset format
nMatchedAllStrings &= ~nMatchedVirgin; bool bWasReturn = ((nMatchedAllStrings & nMatchedUsedAsReturn) != 0); if ( nMatchedAllStrings )
{ bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual(
nStringScanNumFor, nStringsCnt, nNumericsCnt ); if ( !bMatch )
{
nMatchedAllStrings = 0;
}
} if ( nMatchedAllStrings )
{ // A type DEFINED means that no category could be assigned to the // overall format because of mixed type subformats. Do not override // the scanned type in this case. Otherwise in IsNumberFormat() the // first numeric particle would be accepted as number.
SvNumFormatType eForType = eSetType; if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat)
eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor); if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED)
eScannedType = eForType;
} elseif ( bWasReturn )
{ returnfalse;
}
} else
{
nMatchedAllStrings = 0; // reset flag to no substrings matched
} returntrue;
}
/** * Return true or false depending on the nMatched... state and remember usage
*/ bool ImpSvNumberInputScan::MatchedReturn()
{ if ( nMatchedAllStrings & ~nMatchedVirgin )
{
nMatchedAllStrings |= nMatchedUsedAsReturn; returntrue;
} returnfalse;
}
/** * Does rString represent a number (also date, time et al)
*/ bool ImpSvNumberInputScan::IsNumberFormat( const OUString& rString, // string to be analyzed
SvNumFormatType& F_Type, // IN: old type, OUT: new type double& fOutNumber, // OUT: number if convertible const SvNumberformat* pFormat, // maybe a number format to match against const NativeNumberWrapper& rNatNum,
SvNumInputOptions eInputOptions )
{ bool res; // return value
sal_uInt16 k;
eSetType = F_Type; // old type set
if ( !rString.getLength() )
{
res = false;
} elseif (rString.getLength() > 308) // arbitrary
{
res = false;
} else
{ // NoMoreUpperNeeded, all comparisons on UpperCase
OUString aString = mrCurrentLanguageData.GetCharClass()->uppercase( rString ); // convert native number to ASCII if necessary
TransformInput(rNatNum, mrCurrentLanguageData, aString);
res = IsNumberFormatMain( aString, pFormat );
}
if (res)
{ // Accept signed date only for ISO date with at least four digits in // year to not have an input of -M-D-Y arbitrarily recognized. The // final order is only determined in GetDateRef(). // Also accept for Y/M/D date pattern match, i.e. if the first number // is year. // Accept only if the year immediately follows the sign character with // no space in between. if (nSign && (eScannedType == SvNumFormatType::DATE ||
eScannedType == SvNumFormatType::DATETIME) && mbEraCE == kDefaultEra &&
(IsDatePatternNumberOfType(0,'Y') || (MayBeIso8601() && sStrArray[nNums[0]].getLength() >= 4)))
{ const sal_Unicode c = sStrArray[0][sStrArray[0].getLength()-1]; if (c == '-' || c == '+')
{ // A '+' sign doesn't change the era. if (nSign < 0)
mbEraCE = false; // BCE
nSign = 0;
}
} if ( bNegCheck || // ')' not found for '('
(nSign && (eScannedType == SvNumFormatType::DATE ||
eScannedType == SvNumFormatType::DATETIME))) // signed date/datetime
{
res = false;
} else
{ // check count of partial number strings switch (eScannedType)
{ case SvNumFormatType::PERCENT: case SvNumFormatType::CURRENCY: case SvNumFormatType::NUMBER: if (nDecPos == 1) // .05
{ // Matched MidStrings function like group separators, but // there can't be an integer part numeric input, so // effectively 0 thousands groups. if ( nMatchedAllStrings )
{
nThousand = 0;
} elseif ( nNumericsCnt != 1 )
{
res = false;
}
} elseif (nDecPos == 2) // 1.05
{ // Matched MidStrings function like group separators, but // let a decimal separator override a literal separator // string; like 0"." with input 123.45 if ( nMatchedAllStrings )
{ if (nNumericsCnt == 2)
nThousand = 0; else
{ // Assume that if there was a decimal separator // matching also a literal string then it was the // last. We could find the last possible match to // support literals in fractions, but really..
nThousand = nNumericsCnt - 1;
}
} elseif ( nNumericsCnt != nThousand+2 )
{
res = false;
}
} else// 1,100 or 1,100.
{ // matched MidStrings function like group separators if ( nMatchedAllStrings )
{
nThousand = nNumericsCnt - 1;
} elseif ( nNumericsCnt != nThousand+1 )
{
res = false;
}
} break;
case SvNumFormatType::SCIENTIFIC: // 1.0e-2 if (nDecPos == 1) // .05
{ if (nNumericsCnt != 2)
{
res = false;
}
} elseif (nDecPos == 2) // 1.05
{ if (nNumericsCnt != nThousand+3)
{
res = false;
}
} else// 1,100 or 1,100.
{ if (nNumericsCnt != nThousand+2)
{
res = false;
}
} break;
case SvNumFormatType::DATE: if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt == 3)
{ // If both, short month name and day of week name were // detected, and also numbers for full date, assume that we // have a day of week instead of month name.
nMonth = 0;
nMonthPos = 0;
} if (nMonth)
{ // month name and numbers if (nNumericsCnt > 2)
{
res = false;
}
} else
{ if (nNumericsCnt > 3)
{
res = false;
} else
{ // Even if a date pattern was matched, for abbreviated // pattern like "D.M." an input of "D.M. #" was // accepted because # could had been a time. Here we do // not have a combined date/time input though and # // would be taken as Year in this example, which it is // not. The count of numbers in pattern must match the // count of numbers in input.
res = (GetDatePatternNumbers() == nNumericsCnt)
|| IsAcceptableIso8601() || nMatchedAllStrings;
}
} break;
case SvNumFormatType::TIME: if (nDecPos)
{ // hundredth seconds included if (nNumericsCnt > 4)
{
res = false;
}
} else
{ if (nNumericsCnt > 3)
{
res = false;
}
} break;
case SvNumFormatType::DATETIME: if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt >= 5)
{ // If both, abbreviated month name and day of week name // were detected, and also at least numbers for full date // plus time including minutes, assume that we have a day // of week instead of month name.
nMonth = 0;
nMonthPos = 0;
} if (nMonth)
{ // month name and numbers if (nDecPos)
{ // hundredth seconds included if (nNumericsCnt > 6)
{
res = false;
}
} else
{ if (nNumericsCnt > 5)
{
res = false;
}
}
} else
{ if (nDecPos)
{ // hundredth seconds included if (nNumericsCnt > 7)
{
res = false;
}
} else
{ if (nNumericsCnt > 6)
{
res = false;
}
} if (res)
{
res = IsAcceptedDatePattern( nNums[0]) || MayBeIso8601() || nMatchedAllStrings;
}
} break;
if (res)
{ // we finally have a number switch (eScannedType)
{ case SvNumFormatType::LOGICAL: if (nLogical == 1)
{
fOutNumber = 1.0; // True
} elseif (nLogical == -1)
{
fOutNumber = 0.0; // False
} else
{
res = false; // Oops
} break;
case SvNumFormatType::PERCENT: case SvNumFormatType::CURRENCY: case SvNumFormatType::NUMBER: case SvNumFormatType::SCIENTIFIC: case SvNumFormatType::DEFINED: // if no category detected handle as number if ( nDecPos == 1 ) // . at start
{
sResString.append("0.");
}
for ( k = 0; k <= nThousand; k++)
{
sResString.append(sStrArray[nNums[k]]); // integer part
} if ( nDecPos == 2 && k < nNumericsCnt ) // . somewhere
{
sResString.append('.');
sal_uInt16 nStop = (eScannedType == SvNumFormatType::SCIENTIFIC ?
nNumericsCnt-1 : nNumericsCnt); for ( ; k < nStop; k++)
{
sResString.append(sStrArray[nNums[k]]); // fractional part
}
}
case SvNumFormatType::TIME:
res = GetTimeRef(fOutNumber, 0, nNumericsCnt, eInputOptions); if ( nSign < 0 )
{
fOutNumber = -fOutNumber;
} break;
case SvNumFormatType::DATE:
res = GetDateRef( fOutNumber, k ); break;
case SvNumFormatType::DATETIME:
res = GetDateRef( fOutNumber, k ); if ( res )
{ double fTime;
res = GetTimeRef( fTime, k, nNumericsCnt - k, eInputOptions);
fOutNumber += fTime;
} break;
default:
SAL_WARN( "svl.numbers", "Some number recognized but what's it?" );
fOutNumber = 0.0; break;
}
}
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.