Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/svl/source/numbers/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 151 kB image not shown  

Quelle  zforfind.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <cstdlib>
#include <dtoa.h>
#include <float.h>
#include <comphelper/string.hxx>
#include <o3tl/string_view.hxx>
#include <sal/log.hxx>
#include <tools/date.hxx>
#include <rtl/math.hxx>
#include <rtl/character.hxx>
#include <unotools/charclass.hxx>
#include <unotools/calendarwrapper.hxx>
#include <unotools/localedatawrapper.hxx>
#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
#include <com/sun/star/i18n/LocaleCalendar2.hpp>
#include <unotools/digitgroupingiterator.hxx>
#include <comphelper/sequence.hxx>

#include <svl/zforlist.hxx>
#include "zforscan.hxx"
#include <svl/zformat.hxx>

#include <memory>

#include "zforfind.hxx"

#ifndef DBG_UTIL
#define NF_TEST_CALENDAR 0
#else
#define NF_TEST_CALENDAR 0
#endif
#if NF_TEST_CALENDAR
#include <comphelper/processfactory.hxx>
#include <com/sun/star/i18n/XCalendar4.hpp>
#endif


const sal_uInt8 ImpSvNumberInputScan::nMatchedEndString    = 0x01;
const sal_uInt8 ImpSvNumberInputScan::nMatchedMidString    = 0x02;
const sal_uInt8 ImpSvNumberInputScan::nMatchedStartString  = 0x04;
const sal_uInt8 ImpSvNumberInputScan::nMatchedVirgin       = 0x08;
const sal_uInt8 ImpSvNumberInputScan::nMatchedUsedAsReturn = 0x10;

/* 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;
const bool kDefaultEra = true;     // Gregorian CE, positive year

ImpSvNumberInputScan::ImpSvNumberInputScan(SvNFLanguageData& rCurrentLanguage)
        :
        mrCurrentLanguageData(rCurrentLanguage),
        maNullDate( 30,12,1899 ),
        bTextInitialized( false ),
        bScanGenitiveMonths( false ),
        bScanPartitiveMonths( false ),
        eScannedType( SvNumFormatType::UNDEFINED ),
        eSetType( SvNumFormatType::UNDEFINED )
{
    nYear2000 = SvNumberFormatter::GetYear2000Default();
    Reset();
    ChangeIntl();
}

ImpSvNumberInputScan::~ImpSvNumberInputScan()
{
}


void ImpSvNumberInputScan::Reset()
{
    mpFormat     = nullptr;
    nMonth       = 0;
    nMonthPos    = 0;
    nDayOfWeek   = 0;
    nTimePos     = 0;
    nSign        = 0;
    nESign       = 0;
    nDecPos      = 0;
    bNegCheck    = false;
    nStringsCnt  = 0;
    nNumericsCnt = 0;
    nThousand    = 0;
    eScannedType = SvNumFormatType::UNDEFINED;
    nAmPm        = 0;
    nPosThousandString = 0;
    nLogical     = 0;
    mbEraCE        = kDefaultEra;
    nStringScanNumFor = 0;
    nStringScanSign = 0;
    nMatchedAllStrings = nMatchedVirgin;
    nMayBeIso8601 = 0;
    bIso8601Tsep = false;
    nMayBeMonthDate = 0;
    nAcceptedDatePattern = -2;
    nDatePatternStart = 0;
    nDatePatternNumbers = 0;

    for (sal_uInt32 i = 0; i < SV_MAX_COUNT_INPUT_STRINGS; i++)
    {
        IsNum[i] = false;
        nNums[i] = 0;
    }
}

// native number transliteration if necessary
static void TransformInput(const NativeNumberWrapper& rNatNum, const SvNFLanguageData& rCurrentLanguage, OUString& rStr)
{
    sal_Int32 nPos, nLen;
    for ( nPos = 0, nLen = rStr.getLength(); nPos < nLen; ++nPos )
    {
        if ( 256 <= rStr[ nPos ] &&
             rCurrentLanguage.GetCharClass()->isDigit( rStr, nPos ) )
        {
            break;
        }
    }
    if ( nPos < nLen )
    {
        rStr = rNatNum.getNativeNumberString(rStr, rCurrentLanguage.GetLanguageTag().getLocale(), 0);
    }
}


/**
 * 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
};

}

bool ImpSvNumberInputScan::NextNumberStringSymbol( const sal_Unicode*& pStr,
                                                   OUString& rSymbol )
{
    bool isNumber = false;
    sal_Unicode cToken;
    ScanState eState = SsStart;
    const sal_Unicode* pHere = pStr;
    sal_Int32 nChars = 0;

    for (;;)
    {
        cToken = *pHere;
        if (cToken == 0 || eState == SsStop)
            break;
        pHere++;
        switch (eState)
        {
        case SsStart:
            if ( rtl::isAsciiDigit( cToken ) )
            {
                eState = SsGetValue;
                isNumber = true;
            }
            else
            {
                eState = SsGetString;
            }
            nChars++;
            break;
        case SsGetValue:
            if ( rtl::isAsciiDigit( cToken ) )
            {
                nChars++;
            }
            else
            {
                eState = SsStop;
                pHere--;
            }
            break;
        case SsGetString:
            if ( !rtl::isAsciiDigit( cToken ) )
            {
                nChars++;
            }
            else
            {
                eState = SsStop;
                pHere--;
            }
            break;
        default:
            break;
        } // switch
    } // while

    if ( nChars )
    {
        rSymbol = OUString( pStr, nChars );
    }
    else
    {
        rSymbol.clear();
    }

    pStr = pHere;

    return isNumber;
}


// FIXME: should be grouping; it is only used though in case nStringsCnt is
// near SV_MAX_COUNT_INPUT_STRINGS, in NumberStringDivision().

bool ImpSvNumberInputScan::SkipThousands( const sal_Unicode*& pStr,
                                          OUString& rSymbol ) const
{
    bool res = false;
    OUStringBuffer sBuff(rSymbol);
    sal_Unicode cToken;
    const OUString& rThSep = mrCurrentLanguageData.GetNumThousandSep();
    const sal_Unicode* pHere = pStr;
    ScanState eState = SsStart;
    sal_Int32 nCounter = 0; // counts 3 digits

    for (;;)
    {
        cToken = *pHere;
        if (cToken == 0 || eState == SsStop)
            break;
        pHere++;
        switch (eState)
        {
        case SsStart:
            if ( StringPtrContains( rThSep, pHere-1, 0 ) )
            {
                nCounter = 0;
                eState = SsGetValue;
                pHere += rThSep.getLength() - 1;
            }
            else
            {
                eState = SsStop;
                pHere--;
            }
            break;
        case SsGetValue:
            if ( rtl::isAsciiDigit( cToken ) )
            {
                sBuff.append(cToken);
                nCounter++;
                if (nCounter == 3)
                {
                    eState = SsStart;
                    res = true// .000 combination found
                }
            }
            else
            {
                eState = SsStop;
                pHere--;
            }
            break;
        default:
            break;
        } // switch
    } // while

    if (eState == SsGetValue) // break with less than 3 digits
    {
        if ( nCounter )
        {
            sBuff.remove( sBuff.getLength() - nCounter, nCounter );
        }
        pHere -= nCounter + rThSep.getLength(); // put back ThSep also
    }
    rSymbol = sBuff.makeStringAndClear();
    pStr = pHere;

    return res;
}


void ImpSvNumberInputScan::NumberStringDivision( const OUString& rString )
{
    const sal_Unicode* pStr = rString.getStr();
    const sal_Unicode* const pEnd = pStr + rString.getLength();
    while ( pStr < pEnd && nStringsCnt < SV_MAX_COUNT_INPUT_STRINGS )
    {
        if ( NextNumberStringSymbol( pStr, sStrArray[nStringsCnt] ) )
        {   // Number
            IsNum[nStringsCnt] = true;
            nNums[nNumericsCnt] = nStringsCnt;
            nNumericsCnt++;
            if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS - 7 &&
                nPosThousandString == 0) // Only once
            {
                if ( SkipThousands( pStr, sStrArray[nStringsCnt] ) )
                {
                    nPosThousandString = nStringsCnt;
                }
            }
        }
        else
        {
            IsNum[nStringsCnt] = false;
        }
        nStringsCnt++;
    }
}


/**
 * Whether rString contains rWhat at nPos
 */

bool ImpSvNumberInputScan::StringContainsImpl( const OUString& rWhat,
                                               const OUString& rString, sal_Int32 nPos )
{
    if ( nPos + rWhat.getLength() <= rString.getLength() )
    {
        return StringPtrContainsImpl( rWhat, rString.getStr(), nPos );
    }
    return false;
}


/**
 * Whether pString contains rWhat at nPos
 */

bool ImpSvNumberInputScan::StringPtrContainsImpl( const OUString& rWhat,
                                                  const sal_Unicode* pString, sal_Int32 nPos )
{
    if ( rWhat.isEmpty() )
    {
        return false;
    }
    const sal_Unicode* pWhat = rWhat.getStr();
    const sal_Unicode* const pEnd = pWhat + rWhat.getLength();
    const sal_Unicode* pStr = pString + nPos;
    while ( pWhat < pEnd )
    {
        if ( *pWhat != *pStr )
        {
            return false;
        }
        pWhat++;
        pStr++;
    }
    return true;
}


/**
 * Whether rString contains word rWhat at nPos
 */

bool ImpSvNumberInputScan::StringContainsWord( const OUString& rWhat,
                                               const OUString& rString, sal_Int32 nPos ) const
{
    if (rWhat.isEmpty() || rString.getLength() < nPos + rWhat.getLength())
        return false;

    if (StringPtrContainsImpl( rWhat, rString.getStr(), nPos))
    {
        nPos += rWhat.getLength();
        if (nPos == rString.getLength())
            return true;    // 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 '/':
                return true;
            default:
                ;   // nothing
        }

        if (rtl::isAsciiAlphanumeric( c ))
            return false;   // Alpha or numeric is not word gap.

        sal_Int32 nIndex = nPos;
        rString.iterateCodePoints( &nIndex);
        if (nPos+1 < nIndex)
            return true;    // Surrogate, assume these to be new words.

        const sal_Int32 nType = mrCurrentLanguageData.GetCharClass()->getCharacterType( rString, nPos);
        using namespace ::com::sun::star::i18n;

        if ((nType & (KCharacterType::UPPER | KCharacterType::LOWER | KCharacterType::DIGIT)) != 0)
            return false;   // Alpha or numeric is not word gap.

        if (nType & KCharacterType::LETTER)
            return true;    // Letter other than alpha is new word. (Is it?)

        return true;        // Catch all remaining as gap until we know better.
    }

    return false;
}


/**
 * Skips the supplied char
 */

inline bool ImpSvNumberInputScan::SkipChar( sal_Unicode c, std::u16string_view rString,
                                            sal_Int32& nPos )
{
    if ((nPos < static_cast<sal_Int32>(rString.size())) && (rString[nPos] == c))
    {
        nPos++;
        return true;
    }
    return false;
}


/**
 * Skips blanks
 */

inline bool ImpSvNumberInputScan::SkipBlanks( const OUString& rString,
                                              sal_Int32& nPos )
{
    sal_Int32 nHere = nPos;
    if ( nPos < rString.getLength() )
    {
        const sal_Unicode* p = rString.getStr() + nPos;
        while ( *p == ' ' || *p == cNoBreakSpace || *p == cNarrowNoBreakSpace )
        {
            nPos++;
            p++;
        }
    }
    return nHere < nPos;
}


/**
 * jump over rWhat in rString at nPos
 */

inline bool ImpSvNumberInputScan::SkipString( const OUString& rWhat,
                                              const OUString& rString, sal_Int32& nPos )
{
    if ( StringContains( rWhat, rString, nPos ) )
    {
        nPos = nPos + rWhat.getLength();
        return true;
    }
    return false;
}


/**
 * Recognizes exactly ,111 in {3} and {3,2} or ,11 in {3,2} grouping
 */

inline bool 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
    {
        return false// 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();
        return true;
    }
    return false;
}


/**
 * 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;
    }
    else if ( 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
            }
            else if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveAbbrevMonthText[i], rString, nPos ) )
            {   // genitive abbreviated
                nPos = nPos + pUpperGenitiveAbbrevMonthText[i].getLength();
                res = sal::static_int_cast< short >(-(i+1)); // negative
                break;  // for
            }
            else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveMonthText[i], rString, nPos ) )
            {   // partitive full names
                nPos = nPos + pUpperPartitiveMonthText[i].getLength();
                res = i+1;
                break;  // for
            }
            else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveAbbrevMonthText[i], rString, nPos ) )
            {   // partitive abbreviated
                nPos = nPos + pUpperPartitiveAbbrevMonthText[i].getLength();
                res = sal::static_int_cast< short >(-(i+1)); // negative
                break;  // for
            }
            else if ( StringContainsWord( pUpperMonthText[i], rString, nPos ) )
            {   // noun full names
                nPos = nPos + pUpperMonthText[i].getLength();
                res = i+1;
                break;  // for
            }
            else if ( StringContainsWord( pUpperAbbrevMonthText[i], rString, nPos ) )
            {   // noun abbreviated
                nPos = nPos + pUpperAbbrevMonthText[i].getLength();
                res = sal::static_int_cast< short >(-(i+1)); // negative
                break;  // for
            }
            else if (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
                }
                else if (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
                }
            }
            else if (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
                }
                else if (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;
                    }
                    else if (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;
                    }
                    else if (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();
            return true;
        }
        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();
                        return true;
                    }
                }
            }
        }
    }

    return false;
}


/**
 * 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 )
{

    if ( rString.getLength() > nPos )
    {
        const CharClass* pChr = mrCurrentLanguageData.GetCharClass();
        const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData();
        if ( StringContains( pChr->uppercase( pLoc->getTimeAM() ), rString, nPos ) )
        {
            nAmPm = 1;
            nPos = nPos + pLoc->getTimeAM().getLength();
            return true;
        }
        else if ( StringContains( pChr->uppercase( pLoc->getTimePM() ), rString, nPos ) )
        {
            nAmPm = -1;
            nPos = nPos + pLoc->getTimePM().getLength();
            return true;
        }
    }

    return false;
}


/**
 * Read a decimal separator (',')
 * ','   => true
 * else => false
 */

inline bool ImpSvNumberInputScan::GetDecSep( std::u16string_view rString, sal_Int32&&nbsp;nPos ) const
{
    if ( static_cast<sal_Int32>(rString.size()) > nPos )
    {
        const OUString& rSep = mrCurrentLanguageData.GetNumDecimalSep();
        if ( o3tl::starts_with(rString.substr(nPos), rSep) )
        {
            nPos = nPos + rSep.getLength();
            return true;
        }
        const OUString& rSepAlt = mrCurrentLanguageData.GetNumDecimalSepAlt();
        if ( !rSepAlt.isEmpty() && o3tl::starts_with(rString.substr(nPos), rSepAlt) )
        {
            nPos = nPos + rSepAlt.getLength();
            return true;
        }
    }
    return false;
}


/**
 * Reading a hundredth seconds separator
 */

inline bool ImpSvNumberInputScan::GetTime100SecSep( std::u16string_view rString, sal_Int32& nPos ) const
{
    if ( static_cast<sal_Int32>(rString.size()) > nPos )
    {
        if (bIso8601Tsep)
        {
            // ISO 8601 specifies both '.' dot and ',' comma as fractional
            // separator.
            if (rString[nPos] == '.' || rString[nPos] == ',')
            {
                ++nPos;
                return true;
            }
        }
        // Even in an otherwise ISO 8601 string be lenient and accept the
        // locale defined separator.
        const OUString& rSep = mrCurrentLanguageData.GetLocaleData()->getTime100SecSep();
        if ( o3tl::starts_with(rString.substr(nPos), rSep))
        {
            nPos = nPos + rSep.getLength();
            return true;
        }
    }
    return false;
}


/**
 * Read a sign including brackets
 * '+'   =>  1
 * '-'   => -1
 * u'−'   => -1
 *  '('   => -1, bNegCheck = 1
 * else =>  0
 */

int ImpSvNumberInputScan::GetSign( std::u16string_view rString, sal_Int32& nPos )
{
    if (static_cast<sal_Int32>(rString.size()) > nPos)
        switch (rString[ nPos ])
        {
        case '+':
            nPos++;
            return 1;
        case '('// '(' similar to '-' ?!?
            bNegCheck = true;
            [[fallthrough]];
        case '-':
        // tdf#117037 - unicode minus (0x2212)
        case u'−':
            nPos++;
            return -1;
        default:
            break;
        }

    return 0;
}


/**
 * Read a sign with an exponent
 * '+'   =>  1
 * '-'   => -1
 * else =>  0
 */

short ImpSvNumberInputScan::GetESign( std::u16string_view rString, sal_Int32& nPos )
{
    if (static_cast<sal_Int32>(rString.size()) > nPos)
    {
        switch (rString[nPos])
        {
        case '+':
            nPos++;
            return 1;
        case '-':
            nPos++;
            return -1;
        default:
            break;
        }
    }
    return 0;
}


/**
 * i counts string portions, j counts numbers thereof.
 * It should had been called SkipNumber instead.
 */

inline bool ImpSvNumberInputScan::GetNextNumber( sal_uInt16& i, sal_uInt16& j ) const
{
    if ( i < nStringsCnt && IsNum[i] )
    {
        j++;
        i++;
        return true;
    }
    return false;
}


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;

    if (nDecPos == 2 && (nCnt == 3 || nCnt == 2)) // 20:45.5 or 45.5
    {
        nHour = 0;
    }
    else if (mpFormat && nDecPos == 0 && nCnt == 2 && mpFormat->IsMinuteSecondFormat())
    {
        // Input on MM:SS format, instead of doing HH:MM:00
        nHour = 0;
    }
    else if (nIndex - nStartIndex < nCnt)
    {
        const OUString& rValStr = sStrArray[nNums[nIndex++]];
        nHour = rValStr.toInt32();
        if (nHour == 0 && rValStr != "0" && rValStr != "00")
            bRet = false;   // overflow -> Text
    }
    else
    {
        nHour = 0;
        bRet = false;
        SAL_WARN( "svl.numbers""ImpSvNumberInputScan::GetTimeRef: bad number index");
    }

    // 0:123 or 0:0:123 or 0:123:59 is valid
    bool bAllowDuration = (nHour == 0 && !nAmPm);

    if (nAmPm && nHour > 12) // not a valid AM/PM clock time
    {
        bRet = false;
    }
    else if (nAmPm == -1 && nHour != 12) // PM
    {
        nHour += 12;
    }
    else if (nAmPm == 1 && nHour == 12) // 12 AM
    {
        nHour = 0;
    }

    if (nDecPos == 2 && nCnt == 2) // 45.5
    {
        nMinute = 0;
    }
    else if (nIndex - nStartIndex < nCnt)
    {
        const OUString& rValStr = sStrArray[nNums[nIndex++]];
        nMinute = rValStr.toInt32();
        if (nMinute == 0 && rValStr != "0" && rValStr != "00")
            bRet = false;   // overflow -> Text
        if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration
                && nIndex > 1 && nMinute > 59)
            bRet = false;   // 1:60 or 1:123 is invalid, 123:1 or 0:123 is valid
        if (bAllowDuration)
            bAllowDuration = (nMinute == 0);
    }
    if (nIndex - nStartIndex < nCnt)
    {
        const OUString& rValStr = sStrArray[nNums[nIndex++]];
        nSecond = rValStr.toInt32();
        if (nSecond == 0 && rValStr != "0" && rValStr != "00")
            bRet = false;   // overflow -> Text
        if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration
                && nIndex > 1 && nSecond > 59 && !(nHour == 23 && nMinute == 59 && nSecond == 60))
            bRet = false;   // 1:60 or 1:123 or 1:1:123 is invalid, 123:1 or 123:1:1 or 0:0:123 is valid, or leap second
    }
    if (nIndex - nStartIndex < nCnt)
    {
        fSecond100 = StringToDouble( sStrArray[nNums[nIndex]], true );
    }
    fOutNumber = (static_cast<double>(nHour)*3600 +
                  static_cast<double>(nMinute)*60 +
                  static_cast<double>(nSecond) +
                  fSecond100)/86400.0;
    return bRet;
}


sal_uInt16 ImpSvNumberInputScan::ImplGetDay( sal_uInt16 nIndex ) const
{
    sal_uInt16 nRes = 0;

    if (sStrArray[nNums[nIndex]].getLength() <= 2)
    {
        sal_uInt16 nNum = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32());
        if (nNum <= 31)
        {
            nRes = nNum;
        }
    }

    return nRes;
}


sal_uInt16 ImpSvNumberInputScan::ImplGetMonth( sal_uInt16 nIndex ) const
{
    // Preset invalid month number
    sal_uInt16 nRes = mrCurrentLanguageData.GetCalendar()->getNumberOfMonthsInYear();

    if (sStrArray[nNums[nIndex]].getLength() <= 2)
    {
        sal_uInt16 nNum = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32());
        if ( 0 < nNum && nNum <= nRes )
        {
            nRes = nNum - 1; // zero based for CalendarFieldIndex::MONTH
        }
    }

    return nRes;
}


/**
 * 30 -> 1930, 29 -> 2029, or 56 -> 1756, 55 -> 1855, ...
 */

sal_uInt16 ImpSvNumberInputScan::ImplGetYear( sal_uInt16 nIndex )
{
    sal_uInt16 nYear = 0;

    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())
    {
        return false;
    }
    else if (nMayBeIso8601 >= 3)
    {
        return true;    // 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'))
                return false;
            eDateOrder = GetDateOrder();
        }

        // No date pattern matched at all can be forced to ISO 8601 here as is.
        if (GetDatePatternNumbers() == 0)
            return true;

        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;
        defaultbreak;
    }
    return nCanForceToIso8601 > 1;
}


bool ImpSvNumberInputScan::IsAcceptableIso8601()
{
    if (mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE))
    {
        switch (mrCurrentLanguageData.GetEvalDateFormat())
        {
            case NF_EVALDATEFORMAT_INTL:
                return CanForceToIso8601( GetDateOrder());
            case NF_EVALDATEFORMAT_FORMAT:
                return CanForceToIso8601( mpFormat->GetDateOrder());
            default:
                return CanForceToIso8601( GetDateOrder()) || CanForceToIso8601( mpFormat->GetDateOrder());
        }
    }
    return CanForceToIso8601( GetDateOrder());
}


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
                }
                else if (!bDay1 && bDay2)
                {
                    nMayBeMonthDate = 3;        // yy-month-dd
                }
                else if (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.
 */

static bool 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;
    }
    else if (!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);
    }

    if (nAcceptedDatePattern == -1)
    {
        return false;
    }
    nDatePatternStart = nStartPatternAt; // remember start particle

    const sal_Int32 nMonthsInYear = mrCurrentLanguageData.GetCalendar()->getNumberOfMonthsInYear();

    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;
                    }
                    else if ((bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat)))
                    {
                        nPat += nLen - 2;
                    }
                    else if (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ')
                    {
                        using namespace 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.
                    const bool bBlanks = SkipBlanks( sStrArray[nCheck], nPos);
                    if (nPos == sStrArray[nCheck].getLength() && (bBlanks || !IsNum[nNext]))
                    {
                        nAcceptedDatePattern = nPattern;
                        return true;
                    }
                }
            }
            else if (nPat == rPat.getLength())
            {
                // Input end and pattern end => match.
                nAcceptedDatePattern = nPattern;
                return true;
            }
            // else Input end but not pattern end, no match.
        }
    }
    nAcceptedDatePattern = -1;
    return false;
}


bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 &&nbsp;rPos, bool & rSignedYear )
{
    // If not initialized yet start with first number, if any.
    if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
    {
        return false;
    }
    if (nParticle < nDatePatternStart || nParticle >= nStringsCnt || IsNum[nParticle])
    {
        return false;
    }
    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().
                    using namespace 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!
                    return true;
                }
                else
                    return false;
            }
            nPat += sStrArray[nNext].getLength() - 1;
            break;
        }
    }
    return false;
}


sal_uInt16 ImpSvNumberInputScan::GetDatePatternNumbers()
{
    // If not initialized yet start with first number, if any.
    if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
    {
        return 0;
    }
    return nDatePatternNumbers;
}


bool ImpSvNumberInputScan::IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType )
{
    if (GetDatePatternNumbers() <= nNumber)
        return false;

    sal_uInt16 nNum = 0;
    const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern];
    for (sal_Int32 nPat = 0; nPat < rPat.getLength(); ++nPat)
    {
        switch (rPat[nPat])
        {
            case 'Y':
            case 'M':
            case 'D':
                if (nNum == nNumber)
                    return rPat[nPat] == cType;
                ++nNum;
            break;
        }
    }
    return false;
}


sal_uInt32 ImpSvNumberInputScan::GetDatePatternOrder()
{
    // If not initialized yet start with first number, if any.
    if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
    {
        return 0;
    }
    sal_uInt32 nOrder = 0;
    const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern];
    for (sal_Int32 nPat = 0; nPat < rPat.getLength() && !(nOrder & 0xff0000); ++nPat)
    {
        switch (rPat[nPat])
        {
        case 'Y':
        case 'M':
        case 'D':
            nOrder = (nOrder << 8) | rPat[nPat];
            break;
        }
    }
    return nOrder;
}


DateOrder ImpSvNumberInputScan::GetDateOrder( bool bFromFormatIfNoPattern )
{
    sal_uInt32 nOrder = GetDatePatternOrder();
    if (!nOrder)
    {
        if (bFromFormatIfNoPattern && mpFormat)
            return mpFormat->GetDateOrder();
        else
            return mrCurrentLanguageData.GetLocaleData()->getDateOrder();
    }
    switch ((nOrder & 0xff0000) >> 16)
    {
    case 'Y':
        if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'D'))
        {
            return DateOrder::YMD;
        }
        break;
    case 'M':
        if ((((nOrder & 0xff00) >> 8) == 'D') && ((nOrder & 0xff) == 'Y'))
        {
            return DateOrder::MDY;
        }
        break;
    case 'D':
        if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'Y'))
        {
            return DateOrder::DMY;
        }
        break;
    default:
    case 0:
        switch ((nOrder & 0xff00) >> 8)
        {
        case 'Y':
            switch (nOrder & 0xff)
            {
            case 'M':
                return DateOrder::YMD;
            }
            break;
        case 'M':
            switch (nOrder & 0xff)
            {
            case 'Y':
                return DateOrder::DMY;
            case 'D':
                return DateOrder::MDY;
            }
            break;
        case 'D':
            switch (nOrder & 0xff)
            {
            case 'Y':
                return DateOrder::MDY;
            case 'M':
                return DateOrder::DMY;
            }
            break;
        default:
        case 0:
            switch (nOrder & 0xff)
            {
            case 'Y':
                return DateOrder::YMD;
            case 'M':
                return DateOrder::MDY;
            case 'D':
                return DateOrder::DMY;
            }
            break;
        }
    }
    SAL_WARN( "svl.numbers""ImpSvNumberInputScan::GetDateOrder: undefined, falling back to locale's default");
    return mrCurrentLanguageData.GetLocaleData()->getDateOrder();
}

LongDateOrder ImpSvNumberInputScan::GetMiddleMonthLongDateOrder( bool bFormatTurn,
                                                                 const LocaleDataWrapper* pLoc,
                                                                 DateOrder eDateOrder )
{
    if (MayBeMonthDate())
        return (nMayBeMonthDate == 2) ? LongDateOrder::DMY : LongDateOrder::YMD;

    LongDateOrder eLDO;
    const sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0);
    if (!nExactDateOrder)
        eLDO = pLoc->getLongDateOrder();
    else if ((((nExactDateOrder >> 16) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
        eLDO = LongDateOrder::YMD;
    else if ((((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
        }
    }
    else if (eLDO == LongDateOrder::DMY && eDateOrder == DateOrder::YMD)
    {
        // Check possible order and maybe switch.
        if (!ImplGetDay(0) && ImplGetDay(1))
            eLDO = LongDateOrder::YMD;
    }
    else if (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 )
{
    using namespace ::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.

*/

#ifdef THE_FUTURE
            if ( mpFormat->IsOtherCalendar( nStringScanNumFor ) )
            {
                mpFormat->SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
            }
            else
            {
                mpFormat->SwitchToSpecifiedCalendar( aOrgCalendar, fOrgDateTime,
                                                    nStringScanNumFor );
            }
#endif
        }

        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);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.31 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.