/* -*- 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 <sal/config.h>

#include <string_view>

#include <address.hxx>
#include <global.hxx>
#include <compiler.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <externalrefmgr.hxx>

#include <osl/diagnose.h>
#include <o3tl/underlyingenumvalue.hxx>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/sheet/ExternalLinkInfo.hpp>
#include <com/sun/star/sheet/ExternalLinkType.hpp>
#include <sfx2/objsh.hxx>
#include <tools/urlobj.hxx>
#include <sal/log.hxx>
#include <rtl/character.hxx>
#include <unotools/charclass.hxx>

using namespace css;

const ScAddress::Details ScAddress::detailsOOOa1( formula::FormulaGrammar::CONV_OOO, 0, 0 );

ScAddress::Details::Details ( const ScDocument& rDoc,
                              const ScAddress& rAddr ) :
    eConv( rDoc.GetAddressConvention() ),
    nRow( rAddr.Row() ),
    nCol( rAddr.Col() )
{}

namespace {

const sal_Unicode* parseQuotedNameWithBuffer( const sal_Unicode* pStart, const sal_Unicode* p, OUString& rName )
{
    // The current character must be on the 2nd quote.

    // Push all the characters up to the current, but skip the very first
    // character which is the opening quote.
    OUStringBuffer aBuf(std::u16string_view(pStart+1, p-pStart-1));

    ++p; // Skip the 2nd quote.
    sal_Unicode cPrev = 0;
    for (; *p; ++p)
    {
        if (*p == '\'')
        {
            if (cPrev == '\'')
            {
                // double single-quote equals one single quote.
                aBuf.append(*p);
                cPrev = 0;
                continue;
            }
        }
        else if (cPrev == '\'')
        {
            // We are past the closing quote.  We're done!
            rName = aBuf.makeStringAndClear();
            return p;
        }
        else
            aBuf.append(*p);
        cPrev = *p;
    }

    return pStart;
}

/**
 * Parse from the opening single quote to the closing single quote.  Inside
 * the quotes, a single quote character is encoded by double single-quote
 * characters.
 *
 * @param p pointer to the first character to begin parsing.
 * @param rName (reference) parsed name within the quotes.  If the name is
 *              empty, either the parsing failed or it's an empty quote.
 *
 * @return pointer to the character immediately after the closing single
 *         quote.
 */
const sal_Unicode* parseQuotedName( const sal_Unicode* p, OUString& rName )
{
    if (*p != '\'')
        return p;

    const sal_Unicode* pStart = p;
    sal_Unicode cPrev = 0;
    for (++p; *p; ++p)
    {
        if (*p == '\'')
        {
            if (cPrev == '\'')
            {
                // double single-quote equals one single quote.
                return parseQuotedNameWithBuffer(pStart, p, rName);
            }
        }
        else if (cPrev == '\'')
        {
            // We are past the closing quote.  We're done!  Skip the opening
            // and closing quotes.
            rName = OUString(pStart+1, p - pStart-2);
            return p;
        }

        cPrev = *p;
    }

    rName.clear();
    return pStart;
}

}

static sal_Int64 sal_Unicode_strtol ( const sal_Unicode*  p, const sal_Unicode** pEnd )
{
    sal_Int64 accum = 0;
    bool is_neg = false;

    if( *p == '-' )
    {
        is_neg = true;
        p++;
    }
    else if( *p == '+' )
        p++;

    const sal_Int64 cutoff = is_neg ? std::numeric_limits<sal_Int64>::min() / 10
                                    : -(std::numeric_limits<sal_Int64>::max() / 10);
    const int cutlim = is_neg ? -(std::numeric_limits<sal_Int64>::min() % 10)
                              : std::numeric_limits<sal_Int64>::max() % 10;

    while (rtl::isAsciiDigit( *p ))
    {
        int val = *p - '0';
        if (accum < cutoff || (accum == cutoff && val > cutlim))
        {
            *pEnd = nullptr;
            return 0;
        }
        accum = accum * 10 - val;
        p++;
    }

    *pEnd = p;
    return is_neg ? accum : -accum;
}

static const sal_Unicode* lcl_eatWhiteSpace( const sal_Unicode* p )
{
    if ( p )
    {
        while(  *p == ' ' )
            ++p;
    }
    return p;
}

// Compare ignore case ASCII.
static bool lcl_isString( const sal_Unicode* p1, const OUString& rStr )
{
    const size_t n = rStr.getLength();
    if (!n)
        return false;
    const sal_Unicode* p2 = rStr.getStr();
    for (size_t i=0; i<n; ++i)
    {
        if (!p1[i])
            return false;
        if (p1[i] != p2[i])
        {
            sal_Unicode c1 = p1[i];
            if ('A' <= c1 && c1 <= 'Z')
                c1 += 0x20;
            if (c1 < 'a' || 'z' < c1)
                return false;   // not a letter

            sal_Unicode c2 = p2[i];
            if ('A' <= c2 && c2 <= 'Z')
                c2 += 0x20;
            if (c2 < 'a' || 'z' < c2)
                return false;   // not a letter to match

            if (c1 != c2)
                return false;   // lower case doesn't match either
        }
    }
    return true;
}

/** Determines the number of sheets an external reference spans and sets
    rRange.aEnd.nTab accordingly. If a sheet is not found, the corresponding
    bits in rFlags are cleared. pExtInfo is filled if it wasn't already. If in
    cached order rStartTabName comes after rEndTabName, pExtInfo->maTabName
    is set to rEndTabName.
    @returns <FALSE/> if pExtInfo is already filled and rExternDocName does not
             result in the identical file ID. Else <TRUE/>.
 */
static bool lcl_ScRange_External_TabSpan(
                            ScRange & rRange,
                            ScRefFlags & rFlags,
                            ScAddress::ExternalInfo* pExtInfo,
                            const OUString & rExternDocName,
                            const OUString & rStartTabName,
                            const OUString & rEndTabName,
                            const ScDocument& rDoc )
{
    if (rExternDocName.isEmpty())
        return !pExtInfo || !pExtInfo->mbExternal;

    ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
    if (pRefMgr->isOwnDocument( rExternDocName))
    {
        // This is an internal document.  Get the sheet positions from the
        // ScDocument instance.
        if (!rStartTabName.isEmpty())
        {
            SCTAB nTab;
            if (rDoc.GetTable(rStartTabName, nTab))
                rRange.aStart.SetTab(nTab);
        }

        if (!rEndTabName.isEmpty())
        {
            SCTAB nTab;
            if (rDoc.GetTable(rEndTabName, nTab))
                rRange.aEnd.SetTab(nTab);
        }
        return !pExtInfo || !pExtInfo->mbExternal;
    }

    sal_uInt16 nFileId = pRefMgr->getExternalFileId( rExternDocName);

    if (pExtInfo)
    {
        if (pExtInfo->mbExternal)
        {
            if (pExtInfo->mnFileId != nFileId)
                return false;
        }
        else
        {
            pExtInfo->mbExternal = true;
            pExtInfo->maTabName = rStartTabName;
            pExtInfo->mnFileId = nFileId;
        }
    }

    if (rEndTabName.isEmpty() || rStartTabName == rEndTabName)
    {
        rRange.aEnd.SetTab( rRange.aStart.Tab());
        return true;
    }

    SCTAB nSpan = pRefMgr->getCachedTabSpan( nFileId, rStartTabName, rEndTabName);
    if (nSpan == -1)
        rFlags &= ~ScRefFlags(ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID);
    else if (nSpan == 0)
        rFlags &= ~ScRefFlags::TAB2_VALID;
    else if (nSpan >= 1)
        rRange.aEnd.SetTab( rRange.aStart.Tab() + nSpan - 1);
    else // (nSpan < -1)
    {
        rRange.aEnd.SetTab( rRange.aStart.Tab() - nSpan - 1);
        if (pExtInfo)
            pExtInfo->maTabName = rEndTabName;
    }
    return true;
}

/** Returns NULL if the string should be a sheet name, but is invalid.
    Returns a pointer to the first character after the sheet name, if there was
    any, else pointer to start.
    @param pMsoxlQuoteStop
        Starting _within_ a quoted name, but still may be 3D; quoted name stops
        at pMsoxlQuoteStop
 */
static const sal_Unicode * lcl_XL_ParseSheetRef( const sal_Unicode* start,
                                                 OUString& rExternTabName,
                                                 bool bAllow3D,
                                                 const sal_Unicode* pMsoxlQuoteStop,
                                                 const OUString* pErrRef )
{
    OUString aTabName;
    const sal_Unicode *p = start;

    // XL only seems to use single quotes for sheet names.
    if (pMsoxlQuoteStop)
    {
        const sal_Unicode* pCurrentStart = p;
        while (p < pMsoxlQuoteStop)
        {
            if (*p == '\'')
            {
                // We pre-analyzed the quoting, no checks needed here.
                if (*++p == '\'')
                {
                    aTabName += std::u16string_view( pCurrentStart,
                            sal::static_int_cast<sal_Int32>( p - pCurrentStart));
                    pCurrentStart = ++p;
                }
            }
            else if (*p == ':')
            {
                break;  // while
            }
            else
                ++p;
        }
        if (pCurrentStart < p)
            aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast<sal_Int32>( p - pCurrentStart));
        if (aTabName.isEmpty())
            return nullptr;
        if (p == pMsoxlQuoteStop && *pMsoxlQuoteStop == '\'')
            ++p;    // position on ! of ...'!...
        if( *p != '!' && ( !bAllow3D || *p != ':' ) )
            return (!bAllow3D && *p == ':') ? p : start;
    }
    else if( *p == '\'')
    {
        p = parseQuotedName(p, aTabName);
        if (aTabName.isEmpty())
            return nullptr;
    }
    else if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '!')
    {
        p += pErrRef->getLength();  // position after "#REF!" on '!'
        // XXX NOTE: caller has to check the name and that it wasn't quoted.
        aTabName = *pErrRef;
    }
    else
    {
        bool only_digits = true;

        /*
         * Valid: Normal!a1
         * Valid: x.y!a1
         * Invalid: .y!a1
         *
         * Some names starting with digits are actually valid, but
         * unparse quoted. Things are quite tricky: most sheet names
         * starting with a digit are ok, but not those starting with
         * "[0-9]*\." or "[0-9]+[eE]".
         *
         * Valid: 42!a1
         * Valid: 4x!a1
         * Invalid: 1.!a1
         * Invalid: 1e!a1
         */
        while( true )
        {
            const sal_Unicode uc = *p;
            if( rtl::isAsciiAlpha( uc ) || uc == '_' )
            {
                if( only_digits && p != start &&
                   (uc == 'e' || uc == 'E' ) )
                {
                    p = start;
                    break;
                }
                only_digits = false;
                p++;
            }
            else if( rtl::isAsciiDigit( uc ))
            {
                p++;
            }
            else if( uc == '.' )
            {
                if( only_digits ) // Valid, except after only digits.
                {
                    p = start;
                    break;
                }
                p++;
            }
            else if (uc > 127)
            {
                // non ASCII character is allowed.
                ++p;
            }
            else
                break;
        }

        if( *p != '!' && ( !bAllow3D || *p != ':' ) )
            return (!bAllow3D && *p == ':') ? p : start;

        aTabName += std::u16string_view( start, sal::static_int_cast<sal_Int32>( p - start ) );
    }

    rExternTabName = aTabName;
    return p;
}

/** Tries to obtain the external document index and replace by actual document
    name.

    @param ppErrRet
           Contains the default pointer the caller would return if this method
           returns FALSE, may be replaced by NULL for type or data errors.

    @returns FALSE only if the input name is numeric and not within the index
    sequence, or the link type cannot be determined or data mismatch. Returns
    TRUE in all other cases, also when there is no index sequence or the input
    name is not numeric.
 */
static bool lcl_XL_getExternalDoc( const sal_Unicode** ppErrRet, OUString& rExternDocName,
                                   const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
{
    // 1-based, sequence starts with an empty element.
    if (pExternalLinks && pExternalLinks->hasElements())
    {
        // A numeric "document name" is an index into the sequence.
        if (CharClass::isAsciiNumeric( rExternDocName))
        {
            sal_Int32 i = rExternDocName.toInt32();
            if (i < 0 || i >= pExternalLinks->getLength())
                return false;   // with default *ppErrRet
            const sheet::ExternalLinkInfo & rInfo = (*pExternalLinks)[i];
            switch (rInfo.Type)
            {
                case sheet::ExternalLinkType::DOCUMENT :
                    {
                        OUString aStr;
                        if (!(rInfo.Data >>= aStr))
                        {
                            SAL_INFO(
                                "sc.core",
                                "Data type mismatch for ExternalLinkInfo "
                                    << i);
                            *ppErrRet = nullptr;
                            return false;
                        }
                        rExternDocName = aStr;
                    }
                    break;
                    case sheet::ExternalLinkType::SELF :
                        return false;   // ???
                    case sheet::ExternalLinkType::SPECIAL :
                        // silently return nothing (do not assert), caller has to handle this
                        *ppErrRet = nullptr;
                        return false;
                default:
                    SAL_INFO(
                        "sc.core",
                        "unhandled ExternalLinkType " << rInfo.Type
                            << " for index " << i);
                    *ppErrRet = nullptr;
                    return false;
            }
        }
    }
    return true;
}

const sal_Unicode* ScRange::Parse_XL_Header(
                                const sal_Unicode* p,
                                const ScDocument& rDoc,
                                OUString& rExternDocName,
                                OUString& rStartTabName,
                                OUString& rEndTabName,
                                ScRefFlags& nFlags,
                                bool bOnlyAcceptSingle,
                                const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
                                const OUString* pErrRef )
{
    const sal_Unicode* startTabs, *start = p;
    ScRefFlags nSaveFlags = nFlags;

    // Is this an external reference ?
    rStartTabName.clear();
    rEndTabName.clear();
    rExternDocName.clear();
    const sal_Unicode* pMsoxlQuoteStop = nullptr;
    const sal_Unicode* pQuoted3DStop = nullptr;
    if (*p == '[')
    {
        ++p;
        // Only single quotes are correct, and a double single quote escapes a
        // single quote text inside the quoted text.
        if (*p == '\'')
        {
            p = parseQuotedName(p, rExternDocName);
            if (*p != ']' || rExternDocName.isEmpty())
            {
                rExternDocName.clear();
                return start;
            }
        }
        else
        {
            // non-quoted file name.
            p = ScGlobal::UnicodeStrChr( start+1, ']' );
            if( p == nullptr )
                return start;
            rExternDocName += std::u16string_view( start+1, sal::static_int_cast<sal_Int32>( p-(start+1) ) );
        }
        ++p;

        const sal_Unicode* pErrRet = start;
        if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
            return pErrRet;

        rExternDocName = ScGlobal::GetAbsDocName(rExternDocName, rDoc.GetDocumentShell());
    }
    else if (*p == '\'')
    {
        // Sickness in Excel's ODF msoxl namespace:
        // 'E:\[EXTDATA8.XLS]Sheet1'!$A$7  or
        // 'E:\[EXTDATA12B.XLSB]Sheet1:Sheet3'!$A$11
        // But, 'Sheet1'!B3 would also be a valid!
        // Then again, 'Sheet1':'Sheet2'!C4 would be logical and Calc wrote
        // that to OOXML but Excel instead does 'Sheet1:Sheet2'!C4 which is
        // the worse you can get.
        // Excel does not allow [ and ] and : characters in sheet names though.
        // But, more sickness comes with MOOXML as there may be
        // '[1]Sheet 4'!$A$1  where [1] is the external doc's index.
        p = parseQuotedName(p, rExternDocName);
        if (*p == ':')
        {
            // The incorrect 'Sheet1':'Sheet2' case. Just fall through.
        }
        else if (*p != '!')
        {
            rExternDocName.clear();
            return start;
        }
        if (!rExternDocName.isEmpty())
        {
            sal_Int32 nOpen = rExternDocName.indexOf( '[');
            if (nOpen == -1)
            {
                rExternDocName.clear();
                // Look for 'Sheet1:Sheet2'!
                if (*p == '!')
                {
                    const sal_Unicode* pQ = start + 1;
                    do
                    {
                        if (*pQ == ':')
                        {
                            pMsoxlQuoteStop = pQ;
                            pQuoted3DStop = p - 1;
                            break;
                        }
                    }
                    while (++pQ < p);
                }
            }
            else
            {
                sal_Int32 nClose = rExternDocName.indexOf( ']', nOpen+1);
                if (nClose == -1)
                    rExternDocName.clear();
                else
                {
                    rExternDocName = rExternDocName.copy(0, nClose);
                    rExternDocName = rExternDocName.replaceAt( nOpen, 1, u"");
                    pMsoxlQuoteStop = p - 1;    // the ' quote char
                    // There may be embedded escaped quotes, just matching the
                    // doc name's length may not work.
                    for (p = start; *p != '['; ++p)
                        ;
                    for ( ; *p != ']'; ++p)
                        ;
                    ++p;

                    // Handle '[1]Sheet 4'!$A$1
                    if (nOpen == 0)
                    {
                        const sal_Unicode* pErrRet = start;
                        if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
                            return pErrRet;
                    }
                }
            }
        }
        if (rExternDocName.isEmpty())
            p = (pQuoted3DStop ? start + 1 : start);
    }

    startTabs = p;
    p = lcl_XL_ParseSheetRef( p, rStartTabName, !bOnlyAcceptSingle, pMsoxlQuoteStop, pErrRef);
    if( nullptr == p )
        return start;       // invalid tab
    if (bOnlyAcceptSingle && *p == ':')
        return nullptr;        // 3D
    const sal_Unicode* startEndTabs = nullptr;
    if( p != startTabs )
    {
        nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D | ScRefFlags::TAB_ABS;
        if( *p == ':' ) // 3d ref
        {
            startEndTabs = p + 1;
            p = lcl_XL_ParseSheetRef( startEndTabs, rEndTabName, false,
                    (pQuoted3DStop ? pQuoted3DStop : pMsoxlQuoteStop), pErrRef);
            if( p == nullptr )
            {
                nFlags = nSaveFlags;
                return start; // invalid tab
            }
            nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS;
        }
        else
        {
            // If only one sheet is given, the full reference is still valid,
            // only the second 3D flag is not set.
            nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_ABS;
            aEnd.SetTab( aStart.Tab() );
        }

        if( *p++ != '!' )
        {
            nFlags = nSaveFlags;
            return start;   // syntax error
        }
        else
            p = lcl_eatWhiteSpace( p );
    }
    else
    {
        nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID;
        // Use the current tab, it needs to be passed in. : aEnd.SetTab( .. );
    }

    if (!rExternDocName.isEmpty())
    {
        ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
        pRefMgr->convertToAbsName(rExternDocName);
    }
    else
    {
        // Internal reference.
        if (rStartTabName.isEmpty())
        {
            nFlags = nSaveFlags;
            return start;
        }

        SCTAB nTab;
        if ((pErrRef && *startTabs != '\'' && rStartTabName == *pErrRef) || !rDoc.GetTable(rStartTabName, nTab))
        {
            // invalid table name.
            nFlags &= ~ScRefFlags::TAB_VALID;
            nTab = -1;
        }

        aStart.SetTab(nTab);
        aEnd.SetTab(nTab);

        if (!rEndTabName.isEmpty())
        {
            if ((pErrRef && startEndTabs && *startEndTabs != '\'' && rEndTabName == *pErrRef) ||
                    !rDoc.GetTable(rEndTabName, nTab))
            {
                // invalid table name.
                nFlags &= ~ScRefFlags::TAB2_VALID;
                nTab = -1;
            }

            aEnd.SetTab(nTab);
        }
    }
    return p;
}

static const sal_Unicode* lcl_r1c1_get_col( const ScSheetLimits& rSheetLimits,
                                            const sal_Unicode* p,
                                            const ScAddress::Details& rDetails,
                                            ScAddress* pAddr, ScRefFlags* nFlags )
{
    const sal_Unicode *pEnd;
    bool isRelative;

    if( p[0] == '\0' )
        return nullptr;

    p++;
    isRelative = *p == '[';
    if( isRelative )
        p++;
    sal_Int64 n = sal_Unicode_strtol( p, &pEnd );
    if( nullptr == pEnd )
        return nullptr;

    if( p == pEnd ) // C is a relative ref with offset 0
    {
        if( isRelative )
            return nullptr;
        n = rDetails.nCol;

        if (n < 0 || n >= rSheetLimits.GetMaxColCount())
            return nullptr;
    }
    else if( isRelative )
    {
        if( *pEnd != ']' )
            return nullptr;
        n += rDetails.nCol;

        if (n < 0 || n >= rSheetLimits.GetMaxColCount())
            return nullptr;

        pEnd++;
    }
    else
    {
        *nFlags |= ScRefFlags::COL_ABS;

        if (n <= 0 || n > rSheetLimits.GetMaxColCount())
            return nullptr;

        n--;
    }

    pAddr->SetCol( static_cast<SCCOL>( n ) );
    *nFlags |= ScRefFlags::COL_VALID;

    return pEnd;
}

static const sal_Unicode* lcl_r1c1_get_row(
                                    const ScSheetLimits& rSheetLimits,
                                    const sal_Unicode* p,
                                    const ScAddress::Details& rDetails,
                                    ScAddress* pAddr, ScRefFlags* nFlags )
{
    const sal_Unicode *pEnd;
    bool isRelative;

    if( p[0] == '\0' )
        return nullptr;

    p++;
    isRelative = *p == '[';
    if( isRelative )
        p++;
    sal_Int64 n = sal_Unicode_strtol( p, &pEnd );
    if( nullptr == pEnd )
        return nullptr;

    if( p == pEnd ) // R is a relative ref with offset 0
    {
        if( isRelative )
            return nullptr;
        n = rDetails.nRow;

        if (n < 0 || n >= rSheetLimits.GetMaxRowCount())
            return nullptr;
    }
    else if( isRelative )
    {
        if( *pEnd != ']' )
            return nullptr;
        n += rDetails.nRow;
        pEnd++;

        if (n < 0 || n >= rSheetLimits.GetMaxRowCount())
            return nullptr;
    }
    else
    {
        *nFlags |= ScRefFlags::ROW_ABS;

        if (n <= 0)
            return nullptr;

        n--;

        if (n >= rSheetLimits.GetMaxRowCount())
            return nullptr;
    }

    pAddr->SetRow( static_cast<SCROW>( n ) );
    *nFlags |= ScRefFlags::ROW_VALID;

    return pEnd;
}

static ScRefFlags lcl_ScRange_Parse_XL_R1C1( ScRange& r,
                                             const sal_Unicode* p,
                                             const ScDocument& rDoc,
                                             const ScAddress::Details& rDetails,
                                             bool bOnlyAcceptSingle,
                                             ScAddress::ExternalInfo* pExtInfo,
                                             sal_Int32* pSheetEndPos )
{
    const sal_Unicode* const pStart = p;
    if (pSheetEndPos)
        *pSheetEndPos = 0;
    const sal_Unicode* pTmp = nullptr;
    OUString aExternDocName, aStartTabName, aEndTabName;
    ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID;
    // Keep in mind that nFlags2 gets left-shifted by 4 bits before being merged.
    ScRefFlags nFlags2 = ScRefFlags::TAB_VALID;

    p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName,
            aEndTabName, nFlags, bOnlyAcceptSingle );

    ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
    if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
    {
        *pSheetEndPos = p - pStart;
        nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
    }

    if (!aExternDocName.isEmpty())
        lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
                aStartTabName, aEndTabName, rDoc);

    if( nullptr == p )
        return ScRefFlags::ZERO;

    if( *p == 'R' || *p == 'r' )
    {
        if( nullptr == (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )) )
            return nBailOutFlags;

        if( *p != 'C' && *p != 'c' )    // full row R#
        {
            if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r' ) ||
                nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )))
            {
                // Only the initial row number is given, or the second row
                // number is invalid. Fallback to just the initial R
                applyStartToEndFlags(nFlags);
                r.aEnd.SetRow( r.aStart.Row() );
            }
            else // pTmp != nullptr
            {
                // Full row range successfully parsed.
                applyStartToEndFlags(nFlags, nFlags2);
                p = pTmp;
            }

            if (p[0] != 0)
            {
                // any trailing invalid character must invalidate the whole address.
                nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
                            ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
                return nFlags;
            }

            nFlags |=
                ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID |
                ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS;
            r.aStart.SetCol( 0 );
            r.aEnd.SetCol( rDoc.MaxCol() );

            return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
        }
        else if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )))
        {
            return ScRefFlags::ZERO;
        }

        if( p[0] != ':' ||
            (p[1] != 'R' && p[1] != 'r') ||
            nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )) ||
            (*pTmp != 'C' && *pTmp != 'c') ||
            nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), pTmp, rDetails, &r.aEnd, &nFlags2 )))
        {
            // single cell reference

            if (p[0] != 0)
            {
                // any trailing invalid character must invalidate the whole address.
                nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID);
                return nFlags;
            }

            return bOnlyAcceptSingle ? nFlags : ScRefFlags::ZERO;
        }
        assert(pTmp);
        p = pTmp;

        // double reference

        if (p[0] != 0)
        {
            // any trailing invalid character must invalidate the whole range.
            nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
                        ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
            return nFlags;
        }

        applyStartToEndFlags(nFlags, nFlags2);
        return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
    }
    else if( *p == 'C' || *p == 'c' )   // full col C#
    {
        if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )))
            return nBailOutFlags;

        if( p[0] != ':' || (p[1] != 'C' && p[1] != 'c') ||
            nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )))
        {    // Fallback to just the initial C
            applyStartToEndFlags(nFlags);
            r.aEnd.SetCol( r.aStart.Col() );
        }
        else // pTmp != nullptr
        {
            applyStartToEndFlags(nFlags, nFlags2);
            p = pTmp;
        }

        if (p[0] != 0)
        {
            // any trailing invalid character must invalidate the whole address.
            nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
                        ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
            return nFlags;
        }

        nFlags |=
            ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID |
            ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS;
        r.aStart.SetRow( 0 );
        r.aEnd.SetRow( rDoc.MaxRow() );

        return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
    }

    return nBailOutFlags;
}

static const sal_Unicode* lcl_a1_get_col( const ScDocument& rDoc,
                                                const sal_Unicode* p,
                                                 ScAddress* pAddr,
                                                 ScRefFlags* nFlags,
                                                 const OUString* pErrRef )
{
    if( *p == '$' )
    {
        *nFlags |= ScRefFlags::COL_ABS;
        p++;
    }

    if (pErrRef && lcl_isString( p, *pErrRef))
    {
        p += pErrRef->getLength();
        *nFlags &= ~ScRefFlags::COL_VALID;
        pAddr->SetCol(-1);
        return p;
    }

    if( !rtl::isAsciiAlpha( *p ) )
        return nullptr;

    sal_Int64 nCol = rtl::toAsciiUpperCase( *p++ ) - 'A';
    const SCCOL nMaxCol = rDoc.MaxCol();
    while (nCol <= nMaxCol && rtl::isAsciiAlpha(*p))
        nCol = ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A';
    if( nCol > nMaxCol || nCol < 0 || rtl::isAsciiAlpha( *p ) )
        return nullptr;

    *nFlags |= ScRefFlags::COL_VALID;
    pAddr->SetCol( sal::static_int_cast<SCCOL>( nCol ));

    return p;
}

static const sal_Unicode* lcl_a1_get_row( const ScDocument& rDoc,
                                                 const sal_Unicode* p,
                                                 ScAddress* pAddr,
                                                 ScRefFlags* nFlags,
                                                 const OUString* pErrRef )
{
    const sal_Unicode *pEnd;

    if( *p == '$' )
    {
        *nFlags |= ScRefFlags::ROW_ABS;
        p++;
    }

    if (pErrRef && lcl_isString( p, *pErrRef))
    {
        p += pErrRef->getLength();
        *nFlags &= ~ScRefFlags::ROW_VALID;
        pAddr->SetRow(-1);
        return p;
    }

    sal_Int64 n = sal_Unicode_strtol(p, &pEnd);
    if (nullptr == pEnd || p == pEnd || n < 1)
        return nullptr;
    n -= 1;
    if (n > rDoc.MaxRow())
        return nullptr;

    *nFlags |= ScRefFlags::ROW_VALID;
    pAddr->SetRow( sal::static_int_cast<SCROW>(n) );

    return pEnd;
}

/// B:B or 2:2, but not B:2 or 2:B or B2:B or B:B2 or ...
static bool isValidSingleton( ScRefFlags nFlags, ScRefFlags nFlags2 )
{
    bool bCols = (nFlags & ScRefFlags::COL_VALID) && ((nFlags & ScRefFlags::COL2_VALID) || (nFlags2 & ScRefFlags::COL_VALID));
    bool bRows = (nFlags & ScRefFlags::ROW_VALID) && ((nFlags & ScRefFlags::ROW2_VALID) || (nFlags2 & ScRefFlags::ROW_VALID));
    return bCols != bRows;
}

static ScRefFlags lcl_ScRange_Parse_XL_A1( ScRange& r,
                                           const sal_Unicode* p,
                                           const ScDocument& rDoc,
                                           bool bOnlyAcceptSingle,
                                           ScAddress::ExternalInfo* pExtInfo,
                                           const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
                                           sal_Int32* pSheetEndPos,
                                           const OUString* pErrRef )
{
    const sal_Unicode* const pStart = p;
    if (pSheetEndPos)
        *pSheetEndPos = 0;
    const sal_Unicode* tmp1, *tmp2;
    OUString aExternDocName, aStartTabName, aEndTabName; // for external link table
    ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID, nFlags2 = ScRefFlags::TAB_VALID;

    p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName,
            aEndTabName, nFlags, bOnlyAcceptSingle, pExternalLinks, pErrRef );

    ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
    if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
    {
        *pSheetEndPos = p - pStart;
        nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
    }

    if (!aExternDocName.isEmpty())
        lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
                aStartTabName, aEndTabName, rDoc);

    if( nullptr == p )
        return nBailOutFlags;

    tmp1 = lcl_a1_get_col( rDoc, p, &r.aStart, &nFlags, pErrRef);
    if( tmp1 == nullptr )          // Is it a row only reference 3:5
    {
        if( bOnlyAcceptSingle ) // by definition full row refs are ranges
            return nBailOutFlags;

        tmp1 = lcl_a1_get_row( rDoc, p, &r.aStart, &nFlags, pErrRef);

        tmp1 = lcl_eatWhiteSpace( tmp1 );
        if( !tmp1 || *tmp1++ != ':' ) // Even a singleton requires ':' (eg 2:2)
            return nBailOutFlags;

        tmp1 = lcl_eatWhiteSpace( tmp1 );
        tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
        if( !tmp2 || *tmp2 != 0 )   // Must have fully parsed a singleton.
            return nBailOutFlags;

        r.aStart.SetCol( 0 ); r.aEnd.SetCol( rDoc.MaxCol() );
        nFlags |=
            ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID |
            ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS;
        applyStartToEndFlags(nFlags, nFlags2);
        return nFlags;
    }

    tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aStart, &nFlags, pErrRef);
    if( tmp2 == nullptr )          // check for col only reference F:H
    {
        if( bOnlyAcceptSingle ) // by definition full col refs are ranges
            return nBailOutFlags;

        tmp1 = lcl_eatWhiteSpace( tmp1 );
        if( *tmp1++ != ':' )    // Even a singleton requires ':' (eg F:F)
            return nBailOutFlags;

        tmp1 = lcl_eatWhiteSpace( tmp1 );
        tmp2 = lcl_a1_get_col( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
        if( !tmp2 || *tmp2 != 0 )   // Must have fully parsed a singleton.
            return nBailOutFlags;

        r.aStart.SetRow( 0 ); r.aEnd.SetRow( rDoc.MaxRow() );
        nFlags |=
            ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID |
            ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS;
        applyStartToEndFlags(nFlags, nFlags2);
        return nFlags;
    }

    // prepare as if it's a singleton, in case we want to fall back */
    r.aEnd.SetCol( r.aStart.Col() );
    r.aEnd.SetRow( r.aStart.Row() );    // don't overwrite sheet number as parsed in Parse_XL_Header()

    if ( bOnlyAcceptSingle )
    {
        if ( *tmp2 == 0 )
            return nFlags;
        else
        {
            // any trailing invalid character must invalidate the address.
            nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID);
            return nFlags;
        }
    }

    tmp2 = lcl_eatWhiteSpace( tmp2 );
    if( *tmp2 != ':' )
    {
        // Sheet1:Sheet2!C4 is a valid range, without a second sheet it is
        // not. Any trailing invalid character invalidates the range.
        if (*tmp2 == 0 && (nFlags & ScRefFlags::TAB2_3D))
        {
            if (nFlags & ScRefFlags::COL_ABS)
                nFlags |= ScRefFlags::COL2_ABS;
            if (nFlags & ScRefFlags::ROW_ABS)
                nFlags |= ScRefFlags::ROW2_ABS;
        }
        else
            nFlags &= ~ScRefFlags(ScRefFlags::VALID |
                    ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
                    ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
        return nFlags;
    }

    p = lcl_eatWhiteSpace( tmp2+1 );   // after ':'
    tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef);
    if( !tmp1 && aEndTabName.isEmpty() )     // Probably the aEndTabName was specified after the first range
    {
        p = lcl_XL_ParseSheetRef( p, aEndTabName, false, nullptr, pErrRef);
        if( p )
        {
            SCTAB nTab = 0;
            if( !aEndTabName.isEmpty() && rDoc.GetTable( aEndTabName, nTab ) )
            {
                r.aEnd.SetTab( nTab );
                nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS;
            }
            if (*p == '!' || *p == ':')
                p = lcl_eatWhiteSpace( p+1 );
            tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef);
        }
    }
    if( !tmp1 ) // strange, but maybe valid singleton
        return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID);

    tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
    if( !tmp2 ) // strange, but maybe valid singleton
        return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID);

    if ( *tmp2 != 0 )
    {
        // any trailing invalid character must invalidate the range.
        nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
                    ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
        return nFlags;
    }

    applyStartToEndFlags(nFlags, nFlags2);
    return nFlags;
}

/**
    @param p        pointer to null-terminated sal_Unicode string
    @param rRawRes  returns ScRefFlags::... flags without the final check for full
                    validity that is applied to the return value, with which
                    two addresses that form a column or row singleton range,
                    e.g. A:A or 1:1, can be detected. Used in
                    lcl_ScRange_Parse_OOo().
    @param pRange   pointer to range where rAddr effectively is *pRange->aEnd,
                    used in conjunction with pExtInfo to determine the tab span
                    of a 3D reference.
 */
static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr,
                                           ScRefFlags& rRawRes,
                                           ScAddress::ExternalInfo* pExtInfo,
                                           ScRange* pRange,
                                           sal_Int32* pSheetEndPos,
                                           const OUString* pErrRef )
{
    const sal_Unicode* const pStart = p;
    if (pSheetEndPos)
        *pSheetEndPos = 0;
    ScRefFlags  nRes = ScRefFlags::ZERO;
    rRawRes = ScRefFlags::ZERO;
    OUString aDocName;       // the pure Document Name
    OUString aTab;
    bool    bExtDoc = false;
    bool    bExtDocInherited = false;

    // Let's see if this is a reference to something in an external file.  A
    // document name is always quoted and has a trailing #.
    if (*p == '\'')
    {
        OUString aTmp;
        p = parseQuotedName(p, aTmp);
        aDocName = aTmp;
        if (*p++ == SC_COMPILER_FILE_TAB_SEP)
            bExtDoc = true;
        else
            // This is not a document name.  Perhaps a quoted relative table
            // name.
            p = pStart;
    }
    else if (pExtInfo && pExtInfo->mbExternal)
    {
        // This is an external reference.
        bExtDoc = bExtDocInherited = true;
    }

    SCCOL   nCol = 0;
    SCROW   nRow = 0;
    SCTAB   nTab = 0;
    ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
    ScRefFlags nBits = ScRefFlags::TAB_VALID;
    const sal_Unicode* q;
    if ( ScGlobal::FindUnquoted( p, '.') )
    {
        nRes |= ScRefFlags::TAB_3D;
        if ( bExtDoc )
            nRes |= ScRefFlags::TAB_ABS;
        if (*p == '$')
        {
            nRes |= ScRefFlags::TAB_ABS;
            p++;
        }

        if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '.')
        {
            // #REF! particle of an invalidated reference plus sheet separator.
            p += pErrRef->getLength() + 1;
            nRes &= ~ScRefFlags::TAB_VALID;
            nTab = -1;
        }
        else
        {
            if (*p == '\'')
            {
                // Tokens that start at ' can have anything in them until a final
                // ' but '' marks an escaped '.  We've earlier guaranteed that a
                // string containing '' will be surrounded by '.
                p = parseQuotedName(p, aTab);
            }
            else
            {
                OUStringBuffer aTabAcc;
                while (*p)
                {
                    if( *p == '.')
                        break;

                    if( *p == '\'' )
                    {
                        p++; break;
                    }
                    aTabAcc.append(*p);
                    p++;
                }
                aTab = aTabAcc.makeStringAndClear();
            }
            if (*p != '.')
                nBits = ScRefFlags::ZERO;
            else
            {
                ++p;
                if (!bExtDoc && !rDoc.GetTable( aTab, nTab ))
                    nBits = ScRefFlags::ZERO;
            }
        }

        if (pSheetEndPos && (nBits & ScRefFlags::TAB_VALID))
        {
            *pSheetEndPos = p - pStart;
            nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
        }
    }
    else
    {
        if (bExtDoc && !bExtDocInherited)
            return nRes;        // After a document a sheet must follow.
        nTab = rAddr.Tab();
    }
    nRes |= nBits;

    q = p;
    if (*p)
    {
        nBits = ScRefFlags::COL_VALID;
        if (*p == '$')
        {
            nBits |= ScRefFlags::COL_ABS;
            p++;
        }

        if (pErrRef && lcl_isString( p, *pErrRef))
        {
            // #REF! particle of an invalidated reference.
            p += pErrRef->getLength();
            nBits &= ~ScRefFlags::COL_VALID;
            nCol = -1;
        }
        else
        {
            if (rtl::isAsciiAlpha( *p ))
            {
                const SCCOL nMaxCol = rDoc.MaxCol();
                sal_Int64 n = rtl::toAsciiUpperCase( *p++ ) - 'A';
                while (n < nMaxCol && rtl::isAsciiAlpha(*p))
                    n = ((n + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A';
                if (n > nMaxCol || n < 0 || (*p && *p != '$' && !rtl::isAsciiDigit( *p ) &&
                        (!pErrRef || !lcl_isString( p, *pErrRef))))
                    nBits = ScRefFlags::ZERO;
                else
                    nCol = sal::static_int_cast<SCCOL>( n );
            }
            else
                nBits = ScRefFlags::ZERO;

            if( nBits == ScRefFlags::ZERO )
                p = q;
        }
        nRes |= nBits;
    }

    q = p;
    if (*p)
    {
        nBits = ScRefFlags::ROW_VALID;
        if (*p == '$')
        {
            nBits |= ScRefFlags::ROW_ABS;
            p++;
        }

        if (pErrRef && lcl_isString( p, *pErrRef))
        {
            // #REF! particle of an invalidated reference.
            p += pErrRef->getLength();
            // Clearing the ROW_VALID bit here is not possible because of the
            // check at the end whether only a valid column was detected in
            // which case all bits are cleared because it could be any other
            // name. Instead, set to an absolute invalid row value. This will
            // display a $#REF! instead of #REF! if the error value was
            // relative, but live with it.
            nBits |= ScRefFlags::ROW_ABS;
            nRow = -1;
        }
        else
        {
            if( !rtl::isAsciiDigit( *p ) )
            {
                nBits = ScRefFlags::ZERO;
                nRow = -1;
            }
            else
            {
                sal_Int64 n = rtl_ustr_toInt32( p, 10 ) - 1;
                while (rtl::isAsciiDigit( *p ))
                    p++;
                const SCROW nMaxRow = rDoc.MaxRow();
                if( n < 0 || n > nMaxRow )
                    nBits = ScRefFlags::ZERO;
                nRow = sal::static_int_cast<SCROW>(n);
            }
            if( nBits == ScRefFlags::ZERO )
                p = q;
        }
        nRes |= nBits;
    }

    rAddr.Set( nCol, nRow, nTab );

    if (!*p && bExtDoc)
    {
        ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();

        // Need document name if inherited.
        if (bExtDocInherited)
        {
            // The FileId was created using the original file name, so
            // obtain that. Otherwise lcl_ScRange_External_TabSpan() would
            // retrieve a FileId for the real name and bail out if that
            // differed from pExtInfo->mnFileId, as is the case when
            // loading documents that refer external files relative to the
            // current own document but were saved from a different path
            // than loaded.
            const OUString* pFileName = pRefMgr->getExternalFileName( pExtInfo->mnFileId, true);
            if (pFileName)
                aDocName = *pFileName;
            else
                nRes = ScRefFlags::ZERO;
        }
        pRefMgr->convertToAbsName(aDocName);

        if ((!pExtInfo || !pExtInfo->mbExternal) && pRefMgr->isOwnDocument(aDocName))
        {
            if (!rDoc.GetTable( aTab, nTab ))
                nRes = ScRefFlags::ZERO;
            else
            {
                rAddr.SetTab( nTab);
                nRes |= ScRefFlags::TAB_VALID;
            }
        }
        else
        {
            if (!pExtInfo)
                nRes = ScRefFlags::ZERO;
            else
            {
                if (!pExtInfo->mbExternal)
                {
                    sal_uInt16 nFileId = pRefMgr->getExternalFileId(aDocName);

                    pExtInfo->mbExternal = true;
                    pExtInfo->maTabName = aTab;
                    pExtInfo->mnFileId = nFileId;

                    if (pRefMgr->getSingleRefToken(nFileId, aTab,
                                ScAddress(nCol, nRow, 0), nullptr,
                                &nTab))
                    {
                        rAddr.SetTab( nTab);
                        nRes |= ScRefFlags::TAB_VALID;
                    }
                    else
                        nRes = ScRefFlags::ZERO;
                }
                else
                {
                    // This is a call for the second part of the reference,
                    // we must have the range to adapt tab span.
                    if (!pRange)
                        nRes = ScRefFlags::ZERO;
                    else
                    {
                        ScRefFlags nFlags = nRes | ScRefFlags::TAB2_VALID;
                        if (!lcl_ScRange_External_TabSpan( *pRange, nFlags,
                                    pExtInfo, aDocName,
                                    pExtInfo->maTabName, aTab, rDoc))
                            nRes &= ~ScRefFlags::TAB_VALID;
                        else
                        {
                            if (nFlags & ScRefFlags::TAB2_VALID)
                            {
                                rAddr.SetTab( pRange->aEnd.Tab());
                                nRes |= ScRefFlags::TAB_VALID;
                            }
                            else
                                nRes &= ~ScRefFlags::TAB_VALID;
                        }
                    }
                }
            }
        }
    }
    else if (bExtDoc && pExtInfo && !bExtDocInherited && !pExtInfo->mbExternal && pSheetEndPos)
    {
        // Pass partial info up to caller, there may be an external name
        // following, and if after *pSheetEndPos it's external sheet-local.
        // For global names aTab is empty and *pSheetEndPos==0.
        pExtInfo->mbExternal = true;
        pExtInfo->maTabName = aTab;
        pExtInfo->mnFileId = rDoc.GetExternalRefManager()->getExternalFileId(aDocName);
    }

    rRawRes |= nRes;

    if ( !(nRes & ScRefFlags::ROW_VALID) && (nRes & ScRefFlags::COL_VALID)
            && !( (nRes & ScRefFlags::TAB_3D) && (nRes & ScRefFlags::TAB_VALID)) )
    {   // no Row, no Tab, but Col => DM (...), B (...) et al
        nRes = ScRefFlags::ZERO;
    }
    if( !*p )
    {
        ScRefFlags nMask = nRes & ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID );
        if( nMask == ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ) )
            nRes |= ScRefFlags::VALID;
    }
    else
        nRes = rRawRes = nBailOutFlags;
    return nRes;
}

static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr,
                                        const ScAddress::Details& rDetails,
                                        ScAddress::ExternalInfo* pExtInfo,
                                        const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
                                        sal_Int32* pSheetEndPos,
                                        const OUString* pErrRef )
{
    if( !*p )
        return ScRefFlags::ZERO;

    switch (rDetails.eConv)
    {
        case formula::FormulaGrammar::CONV_XL_A1:
        case formula::FormulaGrammar::CONV_XL_OOX:
        {
            ScRange rRange(rAddr);
            ScRefFlags nFlags = lcl_ScRange_Parse_XL_A1(
                    rRange, p, rDoc, true, pExtInfo,
                    (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr),
                    pSheetEndPos, pErrRef);
            rAddr = rRange.aStart;
            return nFlags;
        }
        case formula::FormulaGrammar::CONV_XL_R1C1:
        {
            ScRange rRange(rAddr);
            ScRefFlags nFlags = lcl_ScRange_Parse_XL_R1C1( rRange, p, rDoc, rDetails, true, pExtInfo, pSheetEndPos);
            rAddr = rRange.aStart;
            return nFlags;
        }
        default :
        case formula::FormulaGrammar::CONV_OOO:
        {
            ScRefFlags nRawRes = ScRefFlags::ZERO;
            return lcl_ScAddress_Parse_OOo( p, rDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos, pErrRef);
        }
    }
}

bool ConvertSingleRef( const ScDocument& rDoc, const OUString& rRefString,
                       SCTAB nDefTab, ScRefAddress& rRefAddress,
                       const ScAddress::Details& rDetails,
                       ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
{
    bool bRet = false;
    if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
    {
        ScAddress aAddr( 0, 0, nDefTab );
        ScRefFlags nRes = aAddr.Parse( rRefString, rDoc, rDetails, pExtInfo);
        if ( nRes & ScRefFlags::VALID )
        {
            rRefAddress.Set( aAddr,
                    ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO));
            bRet = true;
        }
    }
    return bRet;
}

bool ConvertDoubleRef( const ScDocument& rDoc, const OUString& rRefString, SCTAB nDefTab,
                       ScRefAddress& rStartRefAddress, ScRefAddress& rEndRefAddress,
                       const ScAddress::Details& rDetails,
                       ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
{
    bool bRet = false;
    if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
    {
        ScRange aRange( ScAddress( 0, 0, nDefTab));
        ScRefFlags nRes = aRange.Parse( rRefString, rDoc, rDetails, pExtInfo);
        if ( nRes & ScRefFlags::VALID )
        {
            rStartRefAddress.Set( aRange.aStart,
                    ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO));
            rEndRefAddress.Set( aRange.aEnd,
                    ((nRes & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO),
                    ((nRes & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO));
            bRet = true;
        }
    }
    return bRet;
}

ScRefFlags ScAddress::Parse( const OUString& r, const ScDocument& rDoc,
                             const Details& rDetails,
                             ExternalInfo* pExtInfo,
                             const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
                             sal_Int32* pSheetEndPos,
                             const OUString* pErrRef )
{
    return lcl_ScAddress_Parse( r.getStr(), rDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos, pErrRef);
}

ScRange ScRange::Intersection( const ScRange& rOther ) const
{
    SCCOL nCol1 = std::max(aStart.Col(), rOther.aStart.Col());
    SCCOL nCol2 = std::min(aEnd.Col(), rOther.aEnd.Col());
    SCROW nRow1 = std::max(aStart.Row(), rOther.aStart.Row());
    SCROW nRow2 = std::min(aEnd.Row(), rOther.aEnd.Row());
    SCTAB nTab1 = std::max(aStart.Tab(), rOther.aStart.Tab());
    SCTAB nTab2 = std::min(aEnd.Tab(), rOther.aEnd.Tab());

    if (nCol1 > nCol2 || nRow1 > nRow2 || nTab1 > nTab2)
        return ScRange(ScAddress::INITIALIZE_INVALID);

    return ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
}

void ScRange::ExtendTo( const ScRange& rRange )
{
    OSL_ENSURE( rRange.IsValid(), "ScRange::ExtendTo - cannot extend to invalid range" );
    if( IsValid() )
    {
        aStart.SetCol( std::min( aStart.Col(), rRange.aStart.Col() ) );
        aStart.SetRow( std::min( aStart.Row(), rRange.aStart.Row() ) );
        aStart.SetTab( std::min( aStart.Tab(), rRange.aStart.Tab() ) );
        aEnd.SetCol(   std::max( aEnd.Col(),   rRange.aEnd.Col() ) );
        aEnd.SetRow(   std::max( aEnd.Row(),   rRange.aEnd.Row() ) );
        aEnd.SetTab(   std::max( aEnd.Tab(),   rRange.aEnd.Tab() ) );
    }
    else
        *this = rRange;
}

static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange,
                                         const OUString& r,
                                         const ScDocument& rDoc,
                                         ScAddress::ExternalInfo* pExtInfo,
                                         const OUString* pErrRef )
{
    ScRefFlags nRes1 = ScRefFlags::ZERO, nRes2 = ScRefFlags::ZERO;
    sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':');
    if (nPos != -1)
    {
        OUStringBuffer aTmp(r);
        aTmp[nPos] = 0;
        const sal_Unicode* p = aTmp.getStr();
        ScRefFlags nRawRes1 = ScRefFlags::ZERO;
        nRes1 = lcl_ScAddress_Parse_OOo( p, rDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr, pErrRef);
        if ((nRes1 != ScRefFlags::ZERO) ||
                ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
                 (nRawRes1 & ScRefFlags::TAB_VALID)))
        {
            rRange.aEnd = rRange.aStart;  // sheet must be initialized identical to first sheet
            ScRefFlags nRawRes2 = ScRefFlags::ZERO;
            nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, rDoc, rRange.aEnd, nRawRes2,
                    pExtInfo, &rRange, nullptr, pErrRef);
            if (!((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) &&
                    // If not fully valid addresses, check if both have a valid
                    // column or row, and both have valid (or omitted) sheet references.
                    (nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
                    (nRawRes1 & ScRefFlags::TAB_VALID) &&
                    (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
                    (nRawRes2 & ScRefFlags::TAB_VALID) &&
                    // Both must be column XOR row references, A:A or 1:1 but not A:1 or 1:A
                    ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) ==
                     (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID))))
            {
                nRes1 = nRawRes1 | ScRefFlags::VALID;
                nRes2 = nRawRes2 | ScRefFlags::VALID;
                if (nRawRes1 & ScRefFlags::COL_VALID)
                {
                    rRange.aStart.SetRow(0);
                    rRange.aEnd.SetRow(rDoc.MaxRow());
                    nRes1 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS;
                    nRes2 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS;
                }
                else
                {
                    rRange.aStart.SetCol(0);
                    rRange.aEnd.SetCol( rDoc.MaxCol() );
                    nRes1 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS;
                    nRes2 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS;
                }
            }
            else if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID))
            {
                // Flag entire column/row references so they can be displayed
                // as such. If the sticky reference parts are not both
                // absolute or relative, assume that the user thought about
                // something we should not touch.
                if (rRange.aStart.Row() == 0 && rRange.aEnd.Row() == rDoc.MaxRow() &&
                        ((nRes1 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO) &&
                        ((nRes2 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO))
                {
                    nRes1 |= ScRefFlags::ROW_ABS;
                    nRes2 |= ScRefFlags::ROW_ABS;
                }
                else if (rRange.aStart.Col() == 0 && rRange.aEnd.Col() == rDoc.MaxCol() &&
                        ((nRes1 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO) && ((nRes2 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO))
                {
                    nRes1 |= ScRefFlags::COL_ABS;
                    nRes2 |= ScRefFlags::COL_ABS;
                }
            }
            if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID))
            {
                // PutInOrder / Justify
                ScRefFlags nMask, nBits1, nBits2;
                SCCOL nTempCol;
                if ( rRange.aEnd.Col() < (nTempCol = rRange.aStart.Col()) )
                {
                    rRange.aStart.SetCol(rRange.aEnd.Col()); rRange.aEnd.SetCol(nTempCol);
                    nMask = (ScRefFlags::COL_VALID | ScRefFlags::COL_ABS);
                    nBits1 = nRes1 & nMask;
                    nBits2 = nRes2 & nMask;
                    nRes1 = (nRes1 & ~nMask) | nBits2;
                    nRes2 = (nRes2 & ~nMask) | nBits1;
                }
                SCROW nTempRow;
                if ( rRange.aEnd.Row() < (nTempRow = rRange.aStart.Row()) )
                {
                    rRange.aStart.SetRow(rRange.aEnd.Row()); rRange.aEnd.SetRow(nTempRow);
                    nMask = (ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS);
                    nBits1 = nRes1 & nMask;
                    nBits2 = nRes2 & nMask;
                    nRes1 = (nRes1 & ~nMask) | nBits2;
                    nRes2 = (nRes2 & ~nMask) | nBits1;
                }
                SCTAB nTempTab;
                if ( rRange.aEnd.Tab() < (nTempTab = rRange.aStart.Tab()) )
                {
                    rRange.aStart.SetTab(rRange.aEnd.Tab()); rRange.aEnd.SetTab(nTempTab);
                    nMask = (ScRefFlags::TAB_VALID | ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D);
                    nBits1 = nRes1 & nMask;
                    nBits2 = nRes2 & nMask;
                    nRes1 = (nRes1 & ~nMask) | nBits2;
                    nRes2 = (nRes2 & ~nMask) | nBits1;
                }
                if ( ((nRes1 & ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D ))
                            == ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D ))
                  && !(nRes2 & ScRefFlags::TAB_3D) )
                    nRes2 |= ScRefFlags::TAB_ABS;
            }
            else
            {
                // Don't leave around valid half references.
                nRes1 = nRes2 = ScRefFlags::ZERO;
            }
        }
    }
    applyStartToEndFlags(nRes1, nRes2 & ScRefFlags::BITS);
    nRes1 |= nRes2 & ScRefFlags::VALID;
    return nRes1;
}

ScRefFlags ScRange::Parse( const OUString& rString, const ScDocument& rDoc,
                           const ScAddress::Details& rDetails,
                           ScAddress::ExternalInfo* pExtInfo,
                           const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
                           const OUString* pErrRef )
{
    if (rString.isEmpty())
        return ScRefFlags::ZERO;

    switch (rDetails.eConv)
    {
        case formula::FormulaGrammar::CONV_XL_A1:
        case formula::FormulaGrammar::CONV_XL_OOX:
        {
            return lcl_ScRange_Parse_XL_A1( *this, rString.getStr(), rDoc, false, pExtInfo,
                    (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr),
                    nullptr, pErrRef );
        }

        case formula::FormulaGrammar::CONV_XL_R1C1:
        {
            return lcl_ScRange_Parse_XL_R1C1( *this, rString.getStr(), rDoc, rDetails, false, pExtInfo, nullptr );
        }

        default:
        case formula::FormulaGrammar::CONV_OOO:
        {
            return lcl_ScRange_Parse_OOo( *this, rString, rDoc, pExtInfo, pErrRef );
        }
    }
}

// Accept a full range, or an address
ScRefFlags ScRange::ParseAny( const OUString& rString, const ScDocument& rDoc,
                              const ScAddress::Details& rDetails )
{
    ScRefFlags nRet = Parse( rString, rDoc, rDetails );
    const ScRefFlags nValid = ScRefFlags::VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID;

    if ( (nRet & nValid) != nValid )
    {
        ScAddress aAdr(aStart);//initialize with currentPos as fallback for table number
        nRet = aAdr.Parse( rString, rDoc, rDetails );
        if ( nRet & ScRefFlags::VALID )
            aStart = aEnd = aAdr;
    }
    return nRet;
}

// Parse only full row references
ScRefFlags ScRange::ParseCols( const ScDocument& rDoc,
                               const OUString& rStr,
                               const ScAddress::Details& rDetails )
{
    if (rStr.isEmpty())
        return ScRefFlags::ZERO;

    const sal_Unicode* p = rStr.getStr();
    ScRefFlags nRes = ScRefFlags::ZERO;
    ScRefFlags ignored = ScRefFlags::ZERO;

    switch (rDetails.eConv)
    {
    default :
    case formula::FormulaGrammar::CONV_OOO: // No full col refs in OOO yet, assume XL notation
    case formula::FormulaGrammar::CONV_XL_A1:
    case formula::FormulaGrammar::CONV_XL_OOX:
        if (nullptr != (p = lcl_a1_get_col( rDoc, p, &aStart, &ignored, nullptr) ) )
        {
            if( p[0] == ':')
            {
                if( nullptr != (p = lcl_a1_get_col( rDoc, p+1, &aEnd, &ignored, nullptr)))
                {
                    nRes = ScRefFlags::COL_VALID;
                }
            }
            else
            {
                aEnd = aStart;
                nRes = ScRefFlags::COL_VALID;
            }
        }
        break;

    case formula::FormulaGrammar::CONV_XL_R1C1:
        if ((p[0] == 'C' || p[0] == 'c') &&
            nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored )))
        {
            if( p[0] == ':')
            {
                if( (p[1] == 'C' || p[1] == 'c') &&
                    nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored )))
                {
                    nRes = ScRefFlags::COL_VALID;
                }
            }
            else
            {
                aEnd = aStart;
                nRes = ScRefFlags::COL_VALID;
            }
        }
        break;
    }

    return (p != nullptr && *p == '\0') ? nRes : ScRefFlags::ZERO;
}

// Parse only full row references
void ScRange::ParseRows( const ScDocument& rDoc,
                               const OUString& rStr,
                               const ScAddress::Details& rDetails )
{
    if (rStr.isEmpty())
        return;

    const sal_Unicode* p = rStr.getStr();
    ScRefFlags ignored = ScRefFlags::ZERO;

    switch (rDetails.eConv)
    {
    default :
    case formula::FormulaGrammar::CONV_OOO: // No full row refs in OOO yet, assume XL notation
    case formula::FormulaGrammar::CONV_XL_A1:
    case formula::FormulaGrammar::CONV_XL_OOX:
        if (nullptr != (p = lcl_a1_get_row( rDoc, p, &aStart, &ignored, nullptr) ) )
        {
            if( p[0] == ':')
            {
                lcl_a1_get_row( rDoc, p+1, &aEnd, &ignored, nullptr);
            }
            else
            {
                aEnd = aStart;
            }
        }
        break;

    case formula::FormulaGrammar::CONV_XL_R1C1:
        if ((p[0] == 'R' || p[0] == 'r') &&
            nullptr != (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored )))
        {
            if( p[0] == ':')
            {
                if( p[1] == 'R' || p[1] == 'r' )
                {
                    lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored );
                }
            }
            else
            {
                aEnd = aStart;
            }
        }
        break;
    }
}

template<typename T > static void lcl_ScColToAlpha( T& rBuf, SCCOL nCol )
{
    if (nCol < 26*26)
    {
        if (nCol < 26)
            rBuf.append( static_cast<char>( 'A' + nCol ));
        else
        {
            rBuf.append( static_cast<char>( 'A' + nCol / 26 - 1 ));
            rBuf.append( static_cast<char>( 'A' + nCol % 26 ));
        }
    }
    else
    {
        sal_Int32 nInsert = rBuf.getLength();
        while (nCol >= 26)
        {
            SCCOL nC = nCol % 26;
            rBuf.insert(nInsert, static_cast<char> ( 'A' + nC ));
            nCol = sal::static_int_cast<SCCOL>( nCol - nC );
            nCol = nCol / 26 - 1;
        }
        rBuf.insert(nInsert, static_cast<char> ( 'A' + nCol ));
    }
}

void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol)
{
    lcl_ScColToAlpha(rBuf, nCol);
}

template <typename T > static void lcl_a1_append_c ( T &rString, int nCol, bool bIsAbs )
{
    if( bIsAbs )
        rString.append("$");
    lcl_ScColToAlpha( rString, sal::static_int_cast<SCCOL>(nCol) );
}

template <typename T > static void lcl_a1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs )
{
    if ( bIsAbs )
        rString.append("$");
    rString.append( nRow + 1 );
}

template <typename T > static void lcl_r1c1_append_c ( T &rString, sal_Int32 nCol, bool bIsAbs,
                                       const ScAddress::Details& rDetails )
{
    rString.append("C");
    if (bIsAbs)
    {
        rString.append( nCol + 1 );
    }
    else
    {
        nCol -= rDetails.nCol;
        if (nCol != 0) {
            rString.append("[").append(nCol).append("]");
        }
    }
}

template <typename T > static void lcl_r1c1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs,
                                       const ScAddress::Details& rDetails )
{
    rString.append("R");
    if (bIsAbs)
    {
        rString.append( nRow + 1 );
    }
    else
    {
        nRow -= rDetails.nRow;
        if (nRow != 0) {
            rString.append("[").append(nRow).append("]");
        }
    }
}

static OUString getFileNameFromDoc( const ScDocument* pDoc )
{
    // TODO : er points at ScGlobal::GetAbsDocName()
    // as a better template.  Look into it
    OUString sFileName;
    SfxObjectShell* pShell;

    if( nullptr != pDoc &&
        nullptr != (pShell = pDoc->GetDocumentShell() ) )
    {
        uno::Reference< frame::XModel > xModel = pShell->GetModel();
        if( xModel.is() )
        {
            if( !xModel->getURL().isEmpty() )
            {
                INetURLObject aURL( xModel->getURL() );
                sFileName = aURL.GetLastName();
            }
            else
                sFileName = pShell->GetTitle();
        }
    }
    return sFileName;
}


static void lcl_string_append(OUStringBuffer &rString, std::u16string_view sString)
{
    rString.append(sString);
}

static void lcl_string_append(OStringBuffer &rString, std::u16string_view sString)
{
    rString.append(OUStringToOString( sString, RTL_TEXTENCODING_UTF8  ));
}

template<typename T > static void lcl_Format( T& r, SCTAB nTab, SCROW nRow, SCCOL nCol, ScRefFlags nFlags,
                                  const ScDocument* pDoc,
                                  const ScAddress::Details& rDetails)
{
    if( nFlags & ScRefFlags::VALID )
        nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID;
    if( pDoc && (nFlags & ScRefFlags::TAB_VALID ) )
    {
        if ( nTab < 0 || nTab >= pDoc->GetTableCount() )
        {
            lcl_string_append(r, ScCompiler::GetNativeSymbol(ocErrRef));
            return;
        }
        if( nFlags & ScRefFlags::TAB_3D )
        {
            OUString aTabName, aDocName;
            pDoc->GetName(nTab, aTabName);
            assert( !aTabName.isEmpty() && "empty sheet name");
            // External Reference, same as in ScCompiler::MakeTabStr()
            if( aTabName[0] == '\'' )
            {   // "'Doc'#Tab"
                sal_Int32 nPos = ScCompiler::GetDocTabPos( aTabName);
                if (nPos != -1)
                {
                    aDocName = aTabName.copy( 0, nPos + 1 );
                    aTabName = aTabName.copy( nPos + 1 );
                }
            }
            else if( nFlags & ScRefFlags::FORCE_DOC )
            {
                // VBA has an 'external' flag that forces the addition of the
                // tab name _and_ the doc name.  The VBA code would be
                // needlessly complicated if it constructed an actual external
                // reference so we add this somewhat cheesy kludge to force the
                // addition of the document name even for non-external references
                aDocName = getFileNameFromDoc( pDoc );
            }
            ScCompiler::CheckTabQuotes( aTabName, rDetails.eConv);

            switch( rDetails.eConv )
            {
            default :
            case formula::FormulaGrammar::CONV_OOO:
                lcl_string_append(r, aDocName);
                if( nFlags & ScRefFlags::TAB_ABS )
                    r.append("$");
                lcl_string_append(r, aTabName);
                r.append(".");
                break;

            case formula::FormulaGrammar::CONV_XL_OOX:
                if (!aTabName.isEmpty() && aTabName[0] == '\'')
                {
                    if (!aDocName.isEmpty())
                    {
                        lcl_string_append(r.append("'["), aDocName);
                        r.append("]");
                        lcl_string_append(r, aTabName.subView(1));
                    }
                    else
                    {
                        lcl_string_append(r, aTabName);
                    }
                    r.append("!");
                    break;
                }
                [[fallthrough]];
            case formula::FormulaGrammar::CONV_XL_A1:
            case formula::FormulaGrammar::CONV_XL_R1C1:
                if (!aDocName.isEmpty())
                {
                    lcl_string_append(r.append("["), aDocName);
                    r.append("]");
                }
                lcl_string_append(r, aTabName);
                r.append("!");
                break;
            }
        }
    }
    switch( rDetails.eConv )
    {
    default :
    case formula::FormulaGrammar::CONV_OOO:
    case formula::FormulaGrammar::CONV_XL_A1:
    case formula::FormulaGrammar::CONV_XL_OOX:
        if( nFlags & ScRefFlags::COL_VALID )
            lcl_a1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
        if( nFlags & ScRefFlags::ROW_VALID )
            lcl_a1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
        break;

    case formula::FormulaGrammar::CONV_XL_R1C1:
        if( nFlags & ScRefFlags::ROW_VALID )
            lcl_r1c1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
        if( nFlags & ScRefFlags::COL_VALID )
            lcl_r1c1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
        break;
    }
}

void ScAddress::Format( OStringBuffer& r, ScRefFlags nFlags,
                                  const ScDocument* pDoc,
                                  const Details& rDetails) const
{
    lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails);
}

OUString ScAddress::Format(ScRefFlags nFlags, const ScDocument* pDoc,
                           const Details& rDetails) const
{
    OUStringBuffer r;
    lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails);
    return r.makeStringAndClear();
}

static void lcl_Split_DocTab( const ScDocument& rDoc,  SCTAB nTab,
                              const ScAddress::Details& rDetails,
                              ScRefFlags nFlags,
                              OUString& rTabName, OUString& rDocName )
{
    rDoc.GetName(nTab, rTabName);
    rDocName.clear();
    // External reference, same as in ScCompiler::MakeTabStr()
    if (!rTabName.isEmpty() && rTabName[0] == '\'')
    {   // "'Doc'#Tab"
        sal_Int32 nPos = ScCompiler::GetDocTabPos( rTabName);
        if (nPos != -1)
        {
            rDocName = rTabName.copy( 0, nPos + 1 );
            rTabName = rTabName.copy( nPos + 1 );
        }
    }
    else if( nFlags & ScRefFlags::FORCE_DOC )
    {
        // VBA has an 'external' flag that forces the addition of the
        // tab name _and_ the doc name.  The VBA code would be
        // needlessly complicated if it constructed an actual external
        // reference so we add this somewhat cheesy kludge to force the
        // addition of the document name even for non-external references
        rDocName = getFileNameFromDoc(&rDoc);
    }
    ScCompiler::CheckTabQuotes( rTabName, rDetails.eConv);
}

static void lcl_ScRange_Format_XL_Header( OUStringBuffer& rString, const ScRange& rRange,
                                          ScRefFlags nFlags, const ScDocument& rDoc,
                                          const ScAddress::Details& rDetails )
{
    if( !(nFlags & ScRefFlags::TAB_3D) )
        return;

    sal_Int32 nQuotePos = rString.getLength();
    OUString aTabName, aDocName;
    lcl_Split_DocTab( rDoc, rRange.aStart.Tab(), rDetails, nFlags, aTabName, aDocName );
    switch (rDetails.eConv)
    {
        case formula::FormulaGrammar::CONV_XL_OOX:
            if (!aTabName.isEmpty() && aTabName[0] == '\'')
            {
                if (!aDocName.isEmpty())
                {
                    rString.append("'[" + aDocName + "]" + aTabName.subView(1));
                }
                else
                {
                    rString.append(aTabName);
                }
                break;
            }
            [[fallthrough]];
        default:
            if (!aDocName.isEmpty())
            {
                rString.append("[" + aDocName + "]");
                nQuotePos = rString.getLength();
            }
            rString.append(aTabName);
        break;
    }
    if( nFlags & ScRefFlags::TAB2_3D )
    {
        lcl_Split_DocTab( rDoc, rRange.aEnd.Tab(), rDetails, nFlags, aTabName, aDocName );
        ScCompiler::FormExcelSheetRange( rString, nQuotePos, aTabName);
    }
    rString.append("!");
}

// helpers used in ScRange::Format
static bool lcl_ColAbsFlagDiffer(const ScRefFlags nFlags)
{
    return static_cast<bool>(nFlags & ScRefFlags::COL_ABS) != static_cast<bool>(nFlags & ScRefFlags::COL2_ABS);
}
static bool lcl_RowAbsFlagDiffer(const ScRefFlags nFlags)
{
    return static_cast<bool>(nFlags & ScRefFlags::ROW_ABS) != static_cast<bool>(nFlags & ScRefFlags::ROW2_ABS);
}

OUString ScRange::Format( const ScDocument& rDoc, ScRefFlags nFlags,
                          const ScAddress::Details& rDetails, bool bFullAddressNotation ) const
{
    if( !( nFlags & ScRefFlags::VALID ) )
    {
        return ScCompiler::GetNativeSymbol(ocErrRef);
    }

    OUStringBuffer r;
    switch( rDetails.eConv ) {
    default :
    case formula::FormulaGrammar::CONV_OOO: {
        bool bOneTab = (aStart.Tab() == aEnd.Tab());
        if ( !bOneTab )
            nFlags |= ScRefFlags::TAB_3D;
        r = aStart.Format(nFlags, &rDoc, rDetails);
        if( aStart != aEnd ||
            lcl_ColAbsFlagDiffer( nFlags ) ||
            lcl_RowAbsFlagDiffer( nFlags ))
        {
            const ScDocument* pDoc = &rDoc;
            // move flags of end reference to start reference, mask with BITS to exclude FORCE_DOC flag
            nFlags = ScRefFlags::VALID | (ScRefFlags(o3tl::to_underlying(nFlags) >> 4) & ScRefFlags::BITS);
            if ( bOneTab )
                pDoc = nullptr;
            else
                nFlags |= ScRefFlags::TAB_3D;
            r.append(":" + aEnd.Format(nFlags, pDoc, rDetails));
        }
        break;
    }

    case formula::FormulaGrammar::CONV_XL_A1:
    case formula::FormulaGrammar::CONV_XL_OOX: {
        SCCOL nMaxCol = rDoc.MaxCol();
        SCROW nMaxRow = rDoc.MaxRow();

        lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails );
        if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation )
        {
            // Full col refs always require 2 rows (2:2)
            lcl_a1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
            r.append(":");
            lcl_a1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO );
        }
        else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation )
        {
            // Full row refs always require 2 cols (A:A)
            lcl_a1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
            r.append(":");
            lcl_a1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO );
        }
        else
        {
            lcl_a1_append_c ( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
            lcl_a1_append_r ( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
            if( aStart.Col() != aEnd.Col() ||
                lcl_ColAbsFlagDiffer( nFlags ) ||
                aStart.Row() != aEnd.Row() ||
                lcl_RowAbsFlagDiffer( nFlags ) ) {
                r.append(":");
                lcl_a1_append_c ( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO );
                lcl_a1_append_r ( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO );
            }
        }
        break;
    }

    case formula::FormulaGrammar::CONV_XL_R1C1: {
        SCCOL nMaxCol = rDoc.MaxCol();
        SCROW nMaxRow = rDoc.MaxRow();

        lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails );
        if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation )
        {
            lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
            if( aStart.Row() != aEnd.Row() ||
                lcl_RowAbsFlagDiffer( nFlags ) ) {
                r.append(":");
                lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails );
            }
        }
        else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation )
        {
            lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
            if( aStart.Col() != aEnd.Col() ||
                lcl_ColAbsFlagDiffer( nFlags )) {
                r.append(":");
                lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails );
            }
        }
        else
        {
            lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
            lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
            if( aStart.Col() != aEnd.Col() ||
                lcl_ColAbsFlagDiffer( nFlags ) ||
                aStart.Row() != aEnd.Row() ||
                lcl_RowAbsFlagDiffer( nFlags ) ) {
                r.append(":");
                lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails );
                lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails );
            }
        }
        break;
    }
    }
    return r.makeStringAndClear();
}

bool ScAddress::Move( SCCOL dx, SCROW dy, SCTAB dz, ScAddress& rErrorPos, const ScDocument& rDoc )
{
    SCTAB nMaxTab = rDoc.GetTableCount();
    SCCOL nMaxCol = rDoc.MaxCol();
    SCROW nMaxRow = rDoc.MaxRow();
    dx = Col() + dx;
    dy = Row() + dy;
    dz = Tab() + dz;
    bool bValid = true;
    rErrorPos.SetCol(dx);
    if( dx < 0 )
    {
        dx = 0;
        bValid = false;
    }
    else if( dx > nMaxCol )
    {
        dx = nMaxCol;
        bValid =false;
    }
    rErrorPos.SetRow(dy);
    if( dy < 0 )
    {
        dy = 0;
        bValid = false;
    }
    else if( dy > nMaxRow )
    {
        dy = nMaxRow;
        bValid =false;
    }
    rErrorPos.SetTab(dz);
    if( dz < 0 )
    {
        dz = 0;
        bValid = false;
    }
    else if( dz > nMaxTab )
    {
        // Always set MAXTAB+1 so further checks without ScDocument detect invalid.
        rErrorPos.SetTab(MAXTAB+1);
        dz = nMaxTab;
        bValid =false;
    }
    Set( dx, dy, dz );
    return bValid;
}

bool ScRange::Move( SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange, const ScDocument& rDoc )
{
    SCCOL nMaxCol = rDoc.MaxCol();
    SCROW nMaxRow = rDoc.MaxRow();
    if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow)
        dy = 0;     // Entire column not to be moved.
    if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol)
        dx = 0;     // Entire row not to be moved.
    bool b = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc );
    b &= aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc );
    return b;
}

bool ScRange::MoveSticky( const ScDocument& rDoc, SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange )
{
    const SCCOL nMaxCol = rDoc.MaxCol();
    const SCROW nMaxRow = rDoc.MaxRow();
    bool bColRange = (aStart.Col() < aEnd.Col());
    bool bRowRange = (aStart.Row() < aEnd.Row());
    if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow)
        dy = 0;     // Entire column not to be moved.
    if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol)
        dx = 0;     // Entire row not to be moved.
    bool b1 = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc );
    if (dx && bColRange && aEnd.Col() == nMaxCol)
        dx = 0;     // End column sticky.
    if (dy && bRowRange && aEnd.Row() == nMaxRow)
        dy = 0;     // End row sticky.
    SCTAB nOldTab = aEnd.Tab();
    bool b2 = aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc );
    if (!b2)
    {
        // End column or row of a range may have become sticky.
        bColRange = (!dx || (bColRange && aEnd.Col() == nMaxCol));
        if (dx && bColRange)
            rErrorRange.aEnd.SetCol(nMaxCol);
        bRowRange = (!dy || (bRowRange && aEnd.Row() == nMaxRow));
        if (dy && bRowRange)
            rErrorRange.aEnd.SetRow(nMaxRow);
        b2 = bColRange && bRowRange && (aEnd.Tab() - nOldTab == dz);
    }
    return b1 && b2;
}

void ScRange::IncColIfNotLessThan(const ScDocument& rDoc, SCCOL nStartCol, SCCOL nOffset)
{
    SCCOL offset;
    if (aStart.Col() > nStartCol)
    {
        offset = nOffset;
        if (nStartCol + nOffset > aStart.Col())
            offset = aStart.Col() - nStartCol;
        else if (nStartCol - nOffset > aStart.Col())
            offset = -1 * (aStart.Col() - nStartCol);

        aStart.IncCol(offset);
        if (aStart.Col() < 0)
            aStart.SetCol(0);
        else if(aStart.Col() > rDoc.MaxCol())
            aStart.SetCol(rDoc.MaxCol());
    }
    if (aEnd.Col() > nStartCol)
    {
        offset = nOffset;
        if (nStartCol + nOffset > aEnd.Col())
            offset = aEnd.Col() - nStartCol;
        else if (nStartCol - nOffset > aEnd.Col())
            offset = -1 * (aEnd.Col() - nStartCol);

        aEnd.IncCol(offset);
        if (aEnd.Col() < 0)
            aEnd.SetCol(0);
        else if(aEnd.Col() > rDoc.MaxCol())
            aEnd.SetCol(rDoc.MaxCol());
    }
}

void ScRange::IncRowIfNotLessThan(const ScDocument& rDoc, SCROW nStartRow, SCROW nOffset)
{
    SCROW offset;
    if (aStart.Row() > nStartRow)
    {
        offset = nOffset;
        if (nStartRow + nOffset > aStart.Row())
            offset = aStart.Row() - nStartRow;
        else if (nStartRow - nOffset > aStart.Row())
            offset = -1 * (aStart.Row() - nStartRow);

        aStart.IncRow(offset);
        if (aStart.Row() < 0)
            aStart.SetRow(0);
        else if(aStart.Row() > rDoc.MaxRow())
            aStart.SetRow(rDoc.MaxRow());
    }
    if (aEnd.Row() > nStartRow)
    {
        offset = nOffset;
        if (nStartRow + nOffset > aEnd.Row())
            offset = aEnd.Row() - nStartRow;
        else if (nStartRow - nOffset > aEnd.Row())
            offset = -1 * (aEnd.Row() - nStartRow);

        aEnd.IncRow(offset);
        if (aEnd.Row() < 0)
            aEnd.SetRow(0);
        else if(aEnd.Row() > rDoc.MaxRow())
            aEnd.SetRow(rDoc.MaxRow());
    }
}

bool ScRange::IsEndColSticky( const ScDocument& rDoc ) const
{
    // Only in an actual column range, i.e. not if both columns are MAXCOL.
    return aEnd.Col() == rDoc.MaxCol() && aStart.Col() < aEnd.Col();
}

bool ScRange::IsEndRowSticky( const ScDocument& rDoc ) const
{
    // Only in an actual row range, i.e. not if both rows are MAXROW.
    return aEnd.Row() == rDoc.MaxRow() && aStart.Row() < aEnd.Row();
}

void ScRange::IncEndColSticky( const ScDocument& rDoc, SCCOL nDelta )
{
    SCCOL nCol = aEnd.Col();
    if (aStart.Col() >= nCol)
    {
        // Less than two columns => not sticky.
        aEnd.IncCol( nDelta);
        return;
    }

    const SCCOL nMaxCol = rDoc.MaxCol();
    if (nCol == nMaxCol)
        // already sticky
        return;

    if (nCol < nMaxCol)
        aEnd.SetCol( ::std::min( static_cast<SCCOL>(nCol + nDelta), nMaxCol));
    else
        aEnd.IncCol( nDelta);   // was greater than nMaxCol, caller should know...
}

void ScRange::IncEndRowSticky( const ScDocument& rDoc, SCROW nDelta )
{
    SCROW nRow = aEnd.Row();
    if (aStart.Row() >= nRow)
    {
        // Less than two rows => not sticky.
        aEnd.IncRow( nDelta);
        return;
    }

    if (nRow == rDoc.MaxRow())
        // already sticky
        return;

    if (nRow < rDoc.MaxRow())
        aEnd.SetRow( ::std::min( static_cast<SCROW>(nRow + nDelta), rDoc.MaxRow()));
    else
        aEnd.IncRow( nDelta);   // was greater than rDoc.MaxRow(), caller should know...
}

OUString ScAddress::GetColRowString() const
{
    OUStringBuffer aString;

    switch( detailsOOOa1.eConv )
    {
    default :
    case formula::FormulaGrammar::CONV_OOO:
    case formula::FormulaGrammar::CONV_XL_A1:
    case formula::FormulaGrammar::CONV_XL_OOX:
        lcl_ScColToAlpha( aString, nCol);
        aString.append(nRow+1);
        break;

    case formula::FormulaGrammar::CONV_XL_R1C1:
        lcl_r1c1_append_r ( aString, nRow, false/*bAbsolute*/, detailsOOOa1 );
        lcl_r1c1_append_c ( aString, nCol, false/*bAbsolute*/, detailsOOOa1 );
        break;
    }

    return aString.makeStringAndClear();
}

OUString ScRefAddress::GetRefString( const ScDocument& rDoc, SCTAB nActTab,
                                     const ScAddress::Details& rDetails ) const
{
    if ( Tab()+1 > rDoc.GetTableCount() )
        return ScCompiler::GetNativeSymbol(ocErrRef);

    ScRefFlags nFlags = ScRefFlags::VALID;
    if ( nActTab != Tab() )
    {
        nFlags |= ScRefFlags::TAB_3D;
        if ( !bRelTab )
            nFlags |= ScRefFlags::TAB_ABS;
    }
    if ( !bRelCol )
        nFlags |= ScRefFlags::COL_ABS;
    if ( !bRelRow )
        nFlags |= ScRefFlags::ROW_ABS;

    return aAdr.Format(nFlags, &rDoc, rDetails);
}

bool AlphaToCol(const ScDocument& rDoc, SCCOL& rCol, std::u16string_view rStr)
{
    SCCOL nResult = 0;
    sal_Int32 nStop = rStr.size();
    sal_Int32 nPos = 0;
    sal_Unicode c;
    const SCCOL nMaxCol = rDoc.MaxCol();
    while (nResult <= nMaxCol && nPos < nStop && (c = rStr[nPos]) != 0 &&
            rtl::isAsciiAlpha(c))
    {
        if (nPos > 0)
            nResult = (nResult + 1) * 26;
        nResult += ScGlobal::ToUpperAlpha(c) - 'A';
        ++nPos;
    }
    bool bOk = (rDoc.ValidCol(nResult) && nPos > 0);
    if (bOk)
        rCol = nResult;
    return bOk;
}

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