/* -*- 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 <memory>
#include <osl/diagnose.h>
#include <unotools/charclass.hxx>
#include <rangeutl.hxx>
#include <document.hxx>
#include <global.hxx>
#include <dbdata.hxx>
#include <rangenam.hxx>
#include <convuno.hxx>
#include <externalrefmgr.hxx>
#include <compiler.hxx>
#include <refupdatecontext.hxx>

using ::formula::FormulaGrammar;
using namespace ::com::sun::star;

bool ScRangeUtil::MakeArea( const OUString&   rAreaStr,
                            ScArea&         rArea,
                            const ScDocument& rDoc,
                            SCTAB           nTab,
                            ScAddress::Details const & rDetails )
{
    // Input in rAreaStr: "$Tabelle1.$A1:$D17"

    // BROKEN BROKEN BROKEN
    // but it is only used in the consolidate dialog.  Ignore for now.

    bool        bSuccess    = false;
    sal_Int32       nPointPos   = rAreaStr.indexOf('.');
    sal_Int32       nColonPos   = rAreaStr.indexOf(':');
    OUString      aStrArea( rAreaStr );
    ScRefAddress    startPos;
    ScRefAddress    endPos;

    if ( nColonPos == -1  && nPointPos != -1 )
    {
        aStrArea += OUString::Concat(":") + rAreaStr.subView( nPointPos+1 ); // do not include '.' in copy
    }

    bSuccess = ConvertDoubleRef( rDoc, aStrArea, nTab, startPos, endPos, rDetails );

    if ( bSuccess )
        rArea = ScArea( startPos.Tab(),
                        startPos.Col(), startPos.Row(),
                        endPos.Col(),   endPos.Row() );

    return bSuccess;
}

void ScRangeUtil::CutPosString( const OUString&   theAreaStr,
                                OUString&         thePosStr )
{
    OUString  aPosStr;
    // BROKEN BROKEN BROKEN
    // but it is only used in the consolidate dialog.  Ignore for now.

    sal_Int32  nColonPos = theAreaStr.indexOf(':');

    if ( nColonPos != -1 )
        aPosStr = theAreaStr.copy( 0, nColonPos ); // do not include ':' in copy
    else
        aPosStr = theAreaStr;

    thePosStr = aPosStr;
}

bool ScRangeUtil::IsAbsTabArea( const OUString&   rAreaStr,
                                const ScDocument* pDoc,
                                std::unique_ptr<ScArea[]>*  ppAreas,
                                sal_uInt16*         pAreaCount,
                                bool            /* bAcceptCellRef */,
                                ScAddress::Details const & rDetails )
{
    OSL_ENSURE( pDoc, "No document given!" );
    if ( !pDoc )
        return false;

    // BROKEN BROKEN BROKEN
    // but it is only used in the consolidate dialog.  Ignore for now.

    /*
     * Expects strings like:
     *      "$Tabelle1.$A$1:$Tabelle3.$D$17"
     * If bAcceptCellRef == sal_True then also accept strings like:
     *      "$Tabelle1.$A$1"
     *
     * as result a ScArea-Array is created,
     * which is published via ppAreas and also has to be deleted this route.
     */

    bool    bStrOk = false;
    OUString  aTempAreaStr(rAreaStr);

    if ( -1 == aTempAreaStr.indexOf(':') )
    {
        aTempAreaStr += ":" + rAreaStr;
    }

    sal_Int32   nColonPos = aTempAreaStr.indexOf(':');

    if (   -1 != nColonPos
        && -1 != aTempAreaStr.indexOf('.') )
    {
        ScRefAddress    aStartPos;

        OUString aStartPosStr = aTempAreaStr.copy( 0,           nColonPos  );
        OUString aEndPosStr   = aTempAreaStr.copy( nColonPos+1 );

        if ( ConvertSingleRef( *pDoc, aStartPosStr, 0, aStartPos, rDetails ) )
        {
            ScRefAddress aEndPos;
            if ( ConvertSingleRef( *pDoc, aEndPosStr, aStartPos.Tab(), aEndPos, rDetails ) )
            {
                aStartPos.SetRelCol( false );
                aStartPos.SetRelRow( false );
                aStartPos.SetRelTab( false );
                aEndPos.SetRelCol( false );
                aEndPos.SetRelRow( false );
                aEndPos.SetRelTab( false );

                bStrOk = true;

                if ( ppAreas && pAreaCount ) // Array returned ?
                {
                    SCTAB       nStartTab   = aStartPos.Tab();
                    SCTAB       nEndTab     = aEndPos.Tab();
                    sal_uInt16      nTabCount   = static_cast<sal_uInt16>(nEndTab-nStartTab+1);
                    ppAreas->reset(new ScArea[nTabCount]);
                    SCTAB       nTab        = 0;
                    sal_uInt16      i           = 0;
                    ScArea      theArea( 0, aStartPos.Col(), aStartPos.Row(),
                                            aEndPos.Col(), aEndPos.Row() );

                    nTab = nStartTab;
                    for ( i=0; i<nTabCount; i++ )
                    {
                        (*ppAreas)[i] = theArea;
                        (*ppAreas)[i].nTab = nTab;
                        nTab++;
                    }
                    *pAreaCount = nTabCount;
                }
            }
        }
    }

    return bStrOk;
}

bool ScRangeUtil::IsAbsArea( const OUString&  rAreaStr,
                             const ScDocument& rDoc,
                             SCTAB          nTab,
                             OUString*      pCompleteStr,
                             ScRefAddress*  pStartPos,
                             ScRefAddress*  pEndPos,
                             ScAddress::Details const & rDetails )
{
    ScRefAddress    startPos;
    ScRefAddress    endPos;

    bool bIsAbsArea = ConvertDoubleRef( rDoc, rAreaStr, nTab, startPos, endPos, rDetails );

    if ( bIsAbsArea )
    {
        startPos.SetRelCol( false );
        startPos.SetRelRow( false );
        startPos.SetRelTab( false );
        endPos  .SetRelCol( false );
        endPos  .SetRelRow( false );
        endPos  .SetRelTab( false );

        if ( pCompleteStr )
        {
            *pCompleteStr  = startPos.GetRefString( rDoc, MAXTAB+1, rDetails );
            *pCompleteStr += ":";
            *pCompleteStr += endPos.GetRefString( rDoc, nTab, rDetails );
        }

        if ( pStartPos && pEndPos )
        {
            *pStartPos = startPos;
            *pEndPos   = endPos;
        }
    }

    return bIsAbsArea;
}

bool ScRangeUtil::IsAbsPos( const OUString&   rPosStr,
                            const ScDocument& rDoc,
                            SCTAB           nTab,
                            OUString*       pCompleteStr,
                            ScRefAddress*   pPosTripel,
                            ScAddress::Details const & rDetails )
{
    ScRefAddress    thePos;

    bool bIsAbsPos = ConvertSingleRef( rDoc, rPosStr, nTab, thePos, rDetails );
    thePos.SetRelCol( false );
    thePos.SetRelRow( false );
    thePos.SetRelTab( false );

    if ( bIsAbsPos )
    {
        if ( pPosTripel )
            *pPosTripel = thePos;
        if ( pCompleteStr )
            *pCompleteStr = thePos.GetRefString( rDoc, MAXTAB+1, rDetails );
    }

    return bIsAbsPos;
}

bool ScRangeUtil::MakeRangeFromName (
    const OUString& rName,
    const ScDocument& rDoc,
    SCTAB           nCurTab,
    ScRange&        rRange,
    RutlNameScope   eScope,
    ScAddress::Details const & rDetails,
    bool bUseDetailsPos )
{
    bool bResult = false;
    if (rName.isEmpty())
        return bResult;

    SCTAB nTab = 0;
    SCCOL nColStart = 0;
    SCCOL nColEnd = 0;
    SCROW nRowStart = 0;
    SCROW nRowEnd = 0;

    if (eScope == RUTL_NAMES || eScope == RUTL_NAMES_LOCAL || eScope == RUTL_NAMES_GLOBAL)
    {
        OUString aName(rName);
        SCTAB nTable = nCurTab;

        if (eScope != RUTL_NAMES_GLOBAL)
        {
            // First handle UI names like "local1 (Sheet1)", which point to a
            // local range name.
            const sal_Int32 nEndPos = aName.getLength() - 1;
            if (rName[nEndPos] == ')')
            {
                const sal_Int32 nStartPos = aName.indexOf(" (");
                if (nStartPos != -1)
                {
                    OUString aSheetName = aName.copy(nStartPos+2, nEndPos-nStartPos-2);
                    if (rDoc.GetTable(aSheetName, nTable))
                    {
                        aName = aName.copy(0, nStartPos);
                        eScope = RUTL_NAMES_LOCAL;
                    }
                    else
                        nTable = nCurTab;
                }
            }
        }

        aName = ScGlobal::getCharClass().uppercase(aName);
        ScRangeData* pData = nullptr;
        if (eScope != RUTL_NAMES_GLOBAL)
        {
            // Check for local range names.
            ScRangeName* pRangeNames = rDoc.GetRangeName( nTable );
            if ( pRangeNames )
                pData = pRangeNames->findByUpperName(aName);
        }
        if (!pData && eScope != RUTL_NAMES_LOCAL)
            pData = rDoc.GetRangeName()->findByUpperName(aName);
        if (pData)
        {
            OUString         aStrArea;
            ScRefAddress     aStartPos;
            ScRefAddress     aEndPos;

            // tdf#138646: use the current grammar of the document and passed
            // address convention.
            // tdf#145077: create range string according to current cell cursor
            // position if expression has relative references and details say so.
            if (bUseDetailsPos)
                aStrArea = pData->GetSymbol( ScAddress( rDetails.nCol, rDetails.nRow, nCurTab),
                        FormulaGrammar::mergeToGrammar(rDoc.GetGrammar(), rDetails.eConv));
            else
                aStrArea = pData->GetSymbol(
                        FormulaGrammar::mergeToGrammar(rDoc.GetGrammar(), rDetails.eConv));

            if ( IsAbsArea( aStrArea, rDoc, nTable,
                            nullptr, &aStartPos, &aEndPos, rDetails ) )
            {
                nTab       = aStartPos.Tab();
                nColStart  = aStartPos.Col();
                nRowStart  = aStartPos.Row();
                nColEnd    = aEndPos.Col();
                nRowEnd    = aEndPos.Row();
                bResult    = true;
            }
            else
            {
                CutPosString( aStrArea, aStrArea );

                if ( IsAbsPos( aStrArea, rDoc, nTable,
                                          nullptr, &aStartPos, rDetails ) )
                {
                    nTab       = aStartPos.Tab();
                    nColStart  = nColEnd = aStartPos.Col();
                    nRowStart  = nRowEnd = aStartPos.Row();
                    bResult    = true;
                }
            }
        }
    }
    else if( eScope==RUTL_DBASE )
    {
        ScDBCollection::NamedDBs& rDbNames = rDoc.GetDBCollection()->getNamedDBs();
        ScDBData* pData = rDbNames.findByUpperName(ScGlobal::getCharClass().uppercase(rName));
        if (pData)
        {
            pData->GetArea(nTab, nColStart, nRowStart, nColEnd, nRowEnd);
            bResult = true;
        }
    }
    else
    {
        OSL_FAIL( "ScRangeUtil::MakeRangeFromName" );
    }

    if( bResult )
    {
        rRange = ScRange( nColStart, nRowStart, nTab, nColEnd, nRowEnd, nTab );
    }

    return bResult;
}

void ScRangeStringConverter::AssignString(
        OUString& rString,
        const OUString& rNewStr,
        bool bAppendStr,
        sal_Unicode cSeparator)
{
    if( bAppendStr )
    {
        if( !rNewStr.isEmpty() )
        {
            if( !rString.isEmpty() )
                rString += OUStringChar(cSeparator);
            rString += rNewStr;
        }
    }
    else
        rString = rNewStr;
}

sal_Int32 ScRangeStringConverter::IndexOf(
        std::u16string_view rString,
        sal_Unicode cSearchChar,
        sal_Int32 nOffset,
        sal_Unicode cQuote )
{
    sal_Int32       nLength     = rString.size();
    sal_Int32       nIndex      = nOffset;
    bool            bQuoted     = false;
    bool        bExitLoop   = false;

    while( !bExitLoop && (nIndex >= 0 && nIndex < nLength) )
    {
        sal_Unicode cCode = rString[ nIndex ];
        bExitLoop = (cCode == cSearchChar) && !bQuoted;
        bQuoted = (bQuoted != (cCode == cQuote));
        if( !bExitLoop )
            nIndex++;
    }
    return (nIndex < nLength) ? nIndex : -1;
}

sal_Int32 ScRangeStringConverter::IndexOfDifferent(
        std::u16string_view rString,
        sal_Unicode cSearchChar,
        sal_Int32 nOffset )
{
    sal_Int32       nLength     = rString.size();
    sal_Int32       nIndex      = nOffset;
    bool        bExitLoop   = false;

    while( !bExitLoop && (nIndex >= 0 && nIndex < nLength) )
    {
        bExitLoop = (rString[ nIndex ] != cSearchChar);
        if( !bExitLoop )
            nIndex++;
    }
    return (nIndex < nLength) ? nIndex : -1;
}

void ScRangeStringConverter::GetTokenByOffset(
        OUString& rToken,
        std::u16string_view rString,
        sal_Int32& nOffset,
        sal_Unicode cSeparator,
        sal_Unicode cQuote)
{
    sal_Int32 nLength = rString.size();
    if( nOffset == -1 || nOffset >= nLength )
    {
        rToken.clear();
        nOffset = -1;
    }
    else
    {
        sal_Int32 nTokenEnd = IndexOf( rString, cSeparator, nOffset, cQuote );
        if( nTokenEnd < 0 )
            nTokenEnd = nLength;
        rToken = rString.substr( nOffset, nTokenEnd - nOffset );

        sal_Int32 nNextBegin = IndexOfDifferent( rString, cSeparator, nTokenEnd );
        nOffset = (nNextBegin < 0) ? nLength : nNextBegin;
    }
}

void ScRangeStringConverter::AppendTableName(OUStringBuffer& rBuf, const OUString& rTabName)
{
    // quote character is always "'"
    OUString aQuotedTab(rTabName);
    ScCompiler::CheckTabQuotes(aQuotedTab);
    rBuf.append(aQuotedTab);
}

sal_Int32 ScRangeStringConverter::GetTokenCount( std::u16string_view rString, sal_Unicode cSeparator )
{
    OUString    sToken;
    sal_Int32   nCount = 0;
    sal_Int32   nOffset = 0;
    while( nOffset >= 0 )
    {
        GetTokenByOffset( sToken, rString, nOffset, '\'', cSeparator );
        if( nOffset >= 0 )
            nCount++;
    }
    return nCount;
}

bool ScRangeStringConverter::GetAddressFromString(
        ScAddress& rAddress,
        std::u16string_view rAddressStr,
        const ScDocument& rDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Int32& nOffset,
        sal_Unicode cSeparator,
        sal_Unicode cQuote )
{
    OUString sToken;
    GetTokenByOffset( sToken, rAddressStr, nOffset, cSeparator, cQuote );
    if( nOffset >= 0 )
    {
        if ((rAddress.Parse( sToken, rDocument, eConv ) & ScRefFlags::VALID) == ScRefFlags::VALID)
            return true;
        ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention();
        if (eConv != eConvUI)
            return ((rAddress.Parse(sToken, rDocument, eConvUI) & ScRefFlags::VALID) == ScRefFlags::VALID);
    }
    return false;
}

bool ScRangeStringConverter::GetRangeFromString(
        ScRange& rRange,
        std::u16string_view rRangeStr,
        const ScDocument& rDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Int32& nOffset,
        sal_Unicode cSeparator,
        sal_Unicode cQuote )
{
    OUString sToken;
    bool bResult(false);
    GetTokenByOffset( sToken, rRangeStr, nOffset, cSeparator, cQuote );
    if( nOffset >= 0 )
    {
        sal_Int32 nIndex = IndexOf( sToken, ':', 0, cQuote );
        OUString aUIString(sToken);

        if( nIndex < 0 )
        {
            if ( aUIString[0] == '.' )
                aUIString = aUIString.copy( 1 );
            bResult = (rRange.aStart.Parse( aUIString, rDocument, eConv) & ScRefFlags::VALID) ==
                                                                                                     ScRefFlags::VALID;
            ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention();
            if (!bResult && eConv != eConvUI)
                bResult = (rRange.aStart.Parse(aUIString, rDocument, eConvUI) & ScRefFlags::VALID) ==
                                                                                                         ScRefFlags::VALID;
            rRange.aEnd = rRange.aStart;
        }
        else
        {
            if ( aUIString[0] == '.' )
            {
                aUIString = aUIString.copy( 1 );
                --nIndex;
            }

            if ( nIndex < aUIString.getLength() - 1 &&
                    aUIString[ nIndex + 1 ] == '.' )
                aUIString = aUIString.replaceAt( nIndex + 1, 1, u"" );

            bResult = ((rRange.Parse(aUIString, rDocument, eConv) & ScRefFlags::VALID) ==
                                                                                              ScRefFlags::VALID);

            // #i77703# chart ranges in the file format contain both sheet names, even for an external reference sheet.
            // This isn't parsed by ScRange, so try to parse the two Addresses then.
            if (!bResult)
            {
                bResult = ((rRange.aStart.Parse( aUIString.copy(0, nIndex), rDocument, eConv)
                               & ScRefFlags::VALID) == ScRefFlags::VALID)
                          &&
                          ((rRange.aEnd.Parse( aUIString.copy(nIndex+1), rDocument, eConv)
                               & ScRefFlags::VALID) == ScRefFlags::VALID);

                ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention();
                if (!bResult && eConv != eConvUI)
                {
                    bResult = ((rRange.aStart.Parse( aUIString.copy(0, nIndex), rDocument, eConvUI)
                                   & ScRefFlags::VALID) == ScRefFlags::VALID)
                              &&
                              ((rRange.aEnd.Parse( aUIString.copy(nIndex+1), rDocument, eConvUI)
                                   & ScRefFlags::VALID) == ScRefFlags::VALID);
                }
            }
        }
    }
    return bResult;
}

bool ScRangeStringConverter::GetRangeListFromString(
        ScRangeList& rRangeList,
        std::u16string_view rRangeListStr,
        const ScDocument& rDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        sal_Unicode cQuote )
{
    bool bRet = true;
    OSL_ENSURE( !rRangeListStr.empty(), "ScXMLConverter::GetRangeListFromString - empty string!" );
    sal_Int32 nOffset = 0;
    while( nOffset >= 0 )
    {
        ScRange aRange;
        if (
             GetRangeFromString( aRange, rRangeListStr, rDocument, eConv, nOffset, cSeparator, cQuote ) &&
             (nOffset >= 0)
           )
        {
            rRangeList.push_back( aRange );
        }
        else if (nOffset > -1)
            bRet = false;
    }
    return bRet;
}

bool ScRangeStringConverter::GetAreaFromString(
        ScArea& rArea,
        std::u16string_view rRangeStr,
        const ScDocument& rDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Int32& nOffset,
        sal_Unicode cSeparator )
{
    ScRange aScRange;
    bool bResult(false);
    if( GetRangeFromString( aScRange, rRangeStr, rDocument, eConv, nOffset, cSeparator ) && (nOffset >= 0) )
    {
        rArea.nTab = aScRange.aStart.Tab();
        rArea.nColStart = aScRange.aStart.Col();
        rArea.nRowStart = aScRange.aStart.Row();
        rArea.nColEnd = aScRange.aEnd.Col();
        rArea.nRowEnd = aScRange.aEnd.Row();
        bResult = true;
    }
    return bResult;
}

bool ScRangeStringConverter::GetRangeFromString(
        table::CellRangeAddress& rRange,
        std::u16string_view rRangeStr,
        const ScDocument& rDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Int32& nOffset,
        sal_Unicode cSeparator )
{
    ScRange aScRange;
    bool bResult(false);
    if( GetRangeFromString( aScRange, rRangeStr, rDocument, eConv, nOffset, cSeparator ) && (nOffset >= 0) )
    {
        ScUnoConversion::FillApiRange( rRange, aScRange );
        bResult = true;
    }
    return bResult;
}

void ScRangeStringConverter::GetStringFromAddress(
        OUString& rString,
        const ScAddress& rAddress,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        bool bAppendStr,
        ScRefFlags nFormatFlags )
{
    if (pDocument && pDocument->HasTable(rAddress.Tab()))
    {
        OUString sAddress(rAddress.Format(nFormatFlags, pDocument, eConv));
        AssignString( rString, sAddress, bAppendStr, cSeparator );
    }
}

void ScRangeStringConverter::GetStringFromRange(
        OUString& rString,
        const ScRange& rRange,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        bool bAppendStr,
        ScRefFlags nFormatFlags )
{
    if (pDocument && pDocument->HasTable(rRange.aStart.Tab()))
    {
        ScAddress aStartAddress( rRange.aStart );
        ScAddress aEndAddress( rRange.aEnd );
        OUString sStartAddress(aStartAddress.Format(nFormatFlags, pDocument, eConv));
        OUString sEndAddress(aEndAddress.Format(nFormatFlags, pDocument, eConv));
        AssignString(
            rString, sStartAddress + ":" + sEndAddress, bAppendStr, cSeparator);
    }
}

void ScRangeStringConverter::GetStringFromRangeList(
        OUString& rString,
        const ScRangeList* pRangeList,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator )
{
    OUString sRangeListStr;
    if( pRangeList )
    {
        for( size_t nIndex = 0, nCount = pRangeList->size(); nIndex < nCount; nIndex++ )
        {
            const ScRange & rRange = (*pRangeList)[nIndex];
            GetStringFromRange( sRangeListStr, rRange, pDocument, eConv, cSeparator, true );
        }
    }
    rString = sRangeListStr;
}

void ScRangeStringConverter::GetStringFromArea(
        OUString& rString,
        const ScArea& rArea,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        bool bAppendStr,
        ScRefFlags nFormatFlags )
{
    ScRange aRange( rArea.nColStart, rArea.nRowStart, rArea.nTab, rArea.nColEnd, rArea.nRowEnd, rArea.nTab );
    GetStringFromRange( rString, aRange, pDocument, eConv, cSeparator, bAppendStr, nFormatFlags );
}

void ScRangeStringConverter::GetStringFromAddress(
        OUString& rString,
        const table::CellAddress& rAddress,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        bool bAppendStr )
{
    ScAddress aScAddress( static_cast<SCCOL>(rAddress.Column), static_cast<SCROW>(rAddress.Row), rAddress.Sheet );
    GetStringFromAddress( rString, aScAddress, pDocument, eConv, cSeparator, bAppendStr );
}

void ScRangeStringConverter::GetStringFromRange(
        OUString& rString,
        const table::CellRangeAddress& rRange,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator,
        bool bAppendStr,
        ScRefFlags nFormatFlags )
{
    ScRange aScRange( static_cast<SCCOL>(rRange.StartColumn), static_cast<SCROW>(rRange.StartRow), rRange.Sheet,
        static_cast<SCCOL>(rRange.EndColumn), static_cast<SCROW>(rRange.EndRow), rRange.Sheet );
    GetStringFromRange( rString, aScRange, pDocument, eConv, cSeparator, bAppendStr, nFormatFlags );
}

void ScRangeStringConverter::GetStringFromRangeList(
        OUString& rString,
        const uno::Sequence< table::CellRangeAddress >& rRangeSeq,
        const ScDocument* pDocument,
        FormulaGrammar::AddressConvention eConv,
        sal_Unicode cSeparator )
{
    OUString sRangeListStr;
    for( const table::CellRangeAddress& rRange : rRangeSeq )
    {
        GetStringFromRange( sRangeListStr, rRange, pDocument, eConv, cSeparator, true );
    }
    rString = sRangeListStr;
}

static void lcl_appendCellAddress(
    OUStringBuffer& rBuf, const ScDocument& rDoc, const ScAddress& rCell,
    const ScAddress::ExternalInfo& rExtInfo)
{
    if (rExtInfo.mbExternal)
    {
        ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
        const OUString* pFilePath = pRefMgr->getExternalFileName(rExtInfo.mnFileId, true);
        if (!pFilePath)
            return;

        sal_Unicode cQuote = '\'';
        rBuf.append(cQuote);
        rBuf.append(*pFilePath);
        rBuf.append(cQuote);
        rBuf.append('#');
        rBuf.append('$');
        ScRangeStringConverter::AppendTableName(rBuf, rExtInfo.maTabName);
        rBuf.append('.');

        OUString aAddr(rCell.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention()));
        rBuf.append(aAddr);
    }
    else
    {
        OUString aAddr(rCell.Format(ScRefFlags::ADDR_ABS_3D, &rDoc, rDoc.GetAddressConvention()));
        rBuf.append(aAddr);
    }
}

static void lcl_appendCellRangeAddress(
    OUStringBuffer& rBuf, const ScDocument& rDoc, const ScAddress& rCell1, const ScAddress& rCell2,
    const ScAddress::ExternalInfo& rExtInfo1, const ScAddress::ExternalInfo& rExtInfo2)
{
    if (rExtInfo1.mbExternal)
    {
        OSL_ENSURE(rExtInfo2.mbExternal, "2nd address is not external!?");
        OSL_ENSURE(rExtInfo1.mnFileId == rExtInfo2.mnFileId, "File IDs do not match between 1st and 2nd addresses.");

        ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
        const OUString* pFilePath = pRefMgr->getExternalFileName(rExtInfo1.mnFileId, true);
        if (!pFilePath)
            return;

        sal_Unicode cQuote = '\'';
        rBuf.append(cQuote);
        rBuf.append(*pFilePath);
        rBuf.append(cQuote);
        rBuf.append('#');
        rBuf.append('$');
        ScRangeStringConverter::AppendTableName(rBuf, rExtInfo1.maTabName);
        rBuf.append('.');

        OUString aAddr(rCell1.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention()));
        rBuf.append(aAddr);

        rBuf.append(":");

        if (rExtInfo1.maTabName != rExtInfo2.maTabName)
        {
            rBuf.append('$');
            ScRangeStringConverter::AppendTableName(rBuf, rExtInfo2.maTabName);
            rBuf.append('.');
        }

        aAddr = rCell2.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention());
        rBuf.append(aAddr);
    }
    else
    {
        ScRange aRange;
        aRange.aStart = rCell1;
        aRange.aEnd   = rCell2;
        OUString aAddr(aRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention()));
        rBuf.append(aAddr);
    }
}

void ScRangeStringConverter::GetStringFromXMLRangeString( OUString& rString, std::u16string_view rXMLRange, const ScDocument& rDoc )
{
    FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
    const sal_Unicode cSepNew = ScCompiler::GetNativeSymbolChar(ocSep);

    OUStringBuffer aRetStr;
    sal_Int32 nOffset = 0;
    bool bFirst = true;

    while (nOffset >= 0)
    {
        OUString aToken;
        GetTokenByOffset(aToken, rXMLRange, nOffset);
        if (nOffset < 0)
            break;

        sal_Int32 nSepPos = IndexOf(aToken, ':', 0);
        if (nSepPos >= 0)
        {
            // Cell range
            OUString aBeginCell = aToken.copy(0, nSepPos);
            OUString aEndCell   = aToken.copy(nSepPos+1);

            if (aBeginCell.isEmpty() || aEndCell.isEmpty())
                // both cell addresses must exist for this to work.
                continue;

            sal_Int32 nEndCellDotPos = aEndCell.indexOf('.');
            if (nEndCellDotPos <= 0)
            {
                // initialize buffer with table name...
                sal_Int32 nDotPos = IndexOf(aBeginCell, '.', 0);
                OUStringBuffer aBuf(aBeginCell.subView(0, nDotPos));

                if (nEndCellDotPos == 0)
                {
                    // workaround for old syntax (probably pre-chart2 age?)
                    // e.g. Sheet1.A1:.B2
                    aBuf.append(aEndCell);
                }
                else if (nEndCellDotPos < 0)
                {
                    // sheet name in the end cell is omitted (e.g. Sheet2.A1:B2).
                    aBuf.append("." + aEndCell);
                }
                aEndCell = aBuf.makeStringAndClear();
            }

            ScAddress::ExternalInfo aExtInfo1, aExtInfo2;
            ScAddress aCell1, aCell2;
            ScRefFlags nRet = aCell1.Parse(aBeginCell, rDoc, FormulaGrammar::CONV_OOO, &aExtInfo1);
            if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO)
            {
                // first cell is invalid.
                if (eConv == FormulaGrammar::CONV_OOO)
                    continue;

                nRet = aCell1.Parse(aBeginCell, rDoc, eConv, &aExtInfo1);
                if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO)
                    // first cell is really invalid.
                    continue;
            }

            nRet = aCell2.Parse(aEndCell, rDoc, FormulaGrammar::CONV_OOO, &aExtInfo2);
            if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO)
            {
                // second cell is invalid.
                if (eConv == FormulaGrammar::CONV_OOO)
                    continue;

                nRet = aCell2.Parse(aEndCell, rDoc, eConv, &aExtInfo2);
                if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO)
                    // second cell is really invalid.
                    continue;
            }

            if (aExtInfo1.mnFileId != aExtInfo2.mnFileId || aExtInfo1.mbExternal != aExtInfo2.mbExternal)
                // external info inconsistency.
                continue;

            // All looks good!

            if (bFirst)
                bFirst = false;
            else
                aRetStr.append(cSepNew);

            lcl_appendCellRangeAddress(aRetStr, rDoc, aCell1, aCell2, aExtInfo1, aExtInfo2);
        }
        else
        {
            // Chart always saves ranges using CONV_OOO convention.
            ScAddress::ExternalInfo aExtInfo;
            ScAddress aCell;
            ScRefFlags nRet = aCell.Parse(aToken, rDoc, ::formula::FormulaGrammar::CONV_OOO, &aExtInfo);
            if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO )
            {
                nRet = aCell.Parse(aToken, rDoc, eConv, &aExtInfo);
                if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO)
                    continue;
            }

            // Looks good!

            if (bFirst)
                bFirst = false;
            else
                aRetStr.append(cSepNew);

            lcl_appendCellAddress(aRetStr, rDoc, aCell, aExtInfo);
        }
    }

    rString = aRetStr.makeStringAndClear();
}

ScRangeData* ScRangeStringConverter::GetRangeDataFromString( const OUString& rString, const SCTAB nTab,
        const ScDocument& rDoc, formula::FormulaGrammar::AddressConvention eConv )
{
    // This may be called with an external 'doc'#name but wouldn't find any.

    // Dot '.' is not allowed in range names, if present only lookup if it's a
    // sheet-local name. Same for '!' Excel syntax.
    // If eConv == FormulaGrammar::CONV_A1_XL_A1 then try both, first our own.
    sal_Int32 nIndex = -1;
    if (eConv == FormulaGrammar::CONV_OOO || eConv == FormulaGrammar::CONV_A1_XL_A1)
        nIndex = ScGlobal::FindUnquoted( rString, '.');
    if (nIndex < 0 && (eConv == FormulaGrammar::CONV_A1_XL_A1
                || eConv == FormulaGrammar::CONV_XL_A1
                || eConv == FormulaGrammar::CONV_XL_R1C1
                || eConv == FormulaGrammar::CONV_XL_OOX))
        nIndex = ScGlobal::FindUnquoted( rString, '!');

    if (nIndex >= 0)
    {
        if (nIndex == 0)
            return nullptr;     // Can't be a name.

        OUString aTab( rString.copy( 0, nIndex));
        ScGlobal::EraseQuotes( aTab, '\'');
        SCTAB nLocalTab;
        if (!rDoc.GetTable( aTab, nLocalTab))
            return nullptr;

        ScRangeName* pLocalRangeName = rDoc.GetRangeName(nLocalTab);
        if (!pLocalRangeName)
            return nullptr;

        const OUString aName( rString.copy( nIndex+1));
        return pLocalRangeName->findByUpperName( ScGlobal::getCharClass().uppercase( aName));
    }

    ScRangeName* pLocalRangeName = rDoc.GetRangeName(nTab);
    ScRangeData* pData = nullptr;
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rString);
    if(pLocalRangeName)
    {
        pData = pLocalRangeName->findByUpperName(aUpperName);
    }
    if (!pData)
    {
        ScRangeName* pGlobalRangeName = rDoc.GetRangeName();
        if (pGlobalRangeName)
        {
            pData = pGlobalRangeName->findByUpperName(aUpperName);
        }
    }
    return pData;
}

ScArea::ScArea( SCTAB tab,
                SCCOL colStart, SCROW rowStart,
                SCCOL colEnd,   SCROW rowEnd ) :
        nTab     ( tab ),
        nColStart( colStart ),  nRowStart( rowStart ),
        nColEnd  ( colEnd ),    nRowEnd  ( rowEnd )
{
}

bool ScArea::operator==( const ScArea& r ) const
{
    return (   (nTab        == r.nTab)
            && (nColStart   == r.nColStart)
            && (nRowStart   == r.nRowStart)
            && (nColEnd     == r.nColEnd)
            && (nRowEnd     == r.nRowEnd) );
}

ScAreaNameIterator::ScAreaNameIterator( const ScDocument& rDoc ) :
    pRangeName(rDoc.GetRangeName()),
    pDBCollection(rDoc.GetDBCollection()),
    bFirstPass(true)
{
    if (pRangeName)
    {
        maRNPos = pRangeName->begin();
        maRNEnd = pRangeName->end();
    }
}

bool ScAreaNameIterator::Next( OUString& rName, ScRange& rRange )
{
    for (;;)
    {
        if ( bFirstPass )                                   // first the area names
        {
            if ( pRangeName && maRNPos != maRNEnd )
            {
                const ScRangeData& rData = *maRNPos->second;
                ++maRNPos;
                bool bValid = rData.IsValidReference(rRange);
                if (bValid)
                {
                    rName = rData.GetName();
                    return true;                            // found
                }
            }
            else
            {
                bFirstPass = false;
                if (pDBCollection)
                {
                    const ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
                    maDBPos = rDBs.begin();
                    maDBEnd = rDBs.end();
                }
            }
        }

        if ( !bFirstPass )                                  // then the DB areas
        {
            if (pDBCollection && maDBPos != maDBEnd)
            {
                const ScDBData& rData = **maDBPos;
                ++maDBPos;
                rData.GetArea(rRange);
                rName = rData.GetName();
                return true;                                // found
            }
            else
                return false;                               // nothing left
        }
    }
}

void ScRangeUpdater::UpdateInsertTab(ScAddress& rAddr, const sc::RefUpdateInsertTabContext& rCxt)
{
    if (rCxt.mnInsertPos <= rAddr.Tab())
    {
        rAddr.IncTab(rCxt.mnSheets);
    }
}

void ScRangeUpdater::UpdateDeleteTab(ScAddress& rAddr, const sc::RefUpdateDeleteTabContext& rCxt)
{
    if (rCxt.mnDeletePos <= rAddr.Tab())
    {
        rAddr.SetTab( std::max<SCTAB>(0, rAddr.Tab() - rCxt.mnSheets));
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
