/* -*- 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 <externalrefmgr.hxx>
#include <document.hxx>
#include <token.hxx>
#include <tokenarray.hxx>
#include <address.hxx>
#include <tablink.hxx>
#include <docsh.hxx>
#include <scextopt.hxx>
#include <rangenam.hxx>
#include <formulacell.hxx>
#include <utility>
#include <viewdata.hxx>
#include <tabvwsh.hxx>
#include <sc.hrc>
#include <globstr.hrc>
#include <scresid.hxx>
#include <cellvalue.hxx>
#include <defaultsoptions.hxx>
#include <scmod.hxx>
 
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <osl/file.hxx>
#include <sfx2/app.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/event.hxx>
#include <sfx2/fcontnr.hxx>
#include <sfx2/objsh.hxx>
#include <svl/itemset.hxx>
#include <svl/numformat.hxx>
#include <svl/stritem.hxx>
#include <svl/urihelper.hxx>
#include <svl/sharedstringpool.hxx>
#include <sfx2/linkmgr.hxx>
#include <tools/hostfilter.hxx>
#include <tools/urlobj.hxx>
#include <unotools/charclass.hxx>
#include <comphelper/configuration.hxx>
#include <unotools/ucbhelper.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <stringutil.hxx>
#include <scmatrix.hxx>
#include <columnspanset.hxx>
#include <column.hxx>
#include <com/sun/star/document/MacroExecMode.hpp>
#include <com/sun/star/document/UpdateDocMode.hpp>
#include <sal/log.hxx>
 
#include <memory>
#include <algorithm>
 
using ::std::unique_ptr;
using ::com::sun::star::uno::Any;
using ::std::vector;
using ::std::find_if;
using ::std::for_each;
using ::std::distance;
using ::std::pair;
using namespace formula;
 
#define SRCDOC_LIFE_SPAN     30000      // 5 minutes (in 100th of a sec)
#define SRCDOC_SCAN_INTERVAL 1000*30    // every 30 seconds (in msec)
 
namespace {
 
class TabNameSearchPredicate
{
public:
    explicit TabNameSearchPredicate(const OUString& rSearchName) :
        maSearchName(ScGlobal::getCharClass().uppercase(rSearchName))
    {
    }
 
    bool operator()(const ScExternalRefCache::TableName& rTabNameSet) const
    {
        // Ok, I'm doing case insensitive search here.
        return rTabNameSet.maUpperName == maSearchName;
    }
 
private:
    OUString maSearchName;
};
 
class FindSrcFileByName
{
public:
    explicit FindSrcFileByName(const OUString& rMatchName) :
        mrMatchName(rMatchName)
    {
    }
 
    bool operator()(const ScExternalRefManager::SrcFileData& rSrcData) const
    {
        return rSrcData.maFileName == mrMatchName;
    }
 
private:
    const OUString& mrMatchName;
};
 
class NotifyLinkListener
{
public:
    NotifyLinkListener(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType) :
        mnFileId(nFileId), meType(eType) {}
 
    void operator() (ScExternalRefManager::LinkListener* p) const
    {
        p->notify(mnFileId, meType);
    }
private:
    sal_uInt16 mnFileId;
    ScExternalRefManager::LinkUpdateType meType;
};
 
struct UpdateFormulaCell
{
    void operator() (ScFormulaCell* pCell) const
    {
        // Check to make sure the cell really contains svExternal*.
        // External names, external cell and range references all have a
        // token of svExternal*. Additionally check for INDIRECT() that can be
        // called with any constructed URI string.
        ScTokenArray* pCode = pCell->GetCode();
        if (!pCode->HasExternalRef() && !pCode->HasOpCode(ocIndirect))
            return;
 
        if (pCode->GetCodeError() != FormulaError::NONE)
        {
            // Clear the error code, or a cell with error won't get re-compiled.
            pCode->SetCodeError(FormulaError::NONE);
            pCell->SetCompile(true);
            pCell->CompileTokenArray();
        }
 
        pCell->SetDirty();
    }
};
 
class RemoveFormulaCell
{
public:
    explicit RemoveFormulaCell(ScFormulaCell* p) : mpCell(p) {}
    void operator() (pair<const sal_uInt16, ScExternalRefManager::RefCellSet>& r) const
    {
        r.second.erase(mpCell);
    }
private:
    ScFormulaCell* mpCell;
};
 
class ConvertFormulaToStatic
{
public:
    explicit ConvertFormulaToStatic(ScDocument* pDoc) : mpDoc(pDoc) {}
    void operator() (ScFormulaCell* pCell) const
    {
        ScAddress aPos = pCell->aPos;
 
        // We don't check for empty cells because empty external cells are
        // treated as having a value of 0.
 
        if (pCell->IsValue())
        {
            // Turn this into value cell.
            mpDoc->SetValue(aPos, pCell->GetValue());
        }
        else
        {
            // string cell otherwise.
            ScSetStringParam aParam;
            aParam.setTextInput();
            mpDoc->SetString(aPos, pCell->GetString().getString(), &aParam);
        }
    }
private:
    ScDocument* mpDoc;
};
 
/**
 * Check whether a named range contains an external reference to a
 * particular document.
 */
bool hasRefsToSrcDoc(ScRangeData& rData, sal_uInt16 nFileId)
{
    ScTokenArray* pArray = rData.GetCode();
    if (!pArray)
        return false;
 
    formula::FormulaTokenArrayPlainIterator aIter(*pArray);
    formula::FormulaToken* p = aIter.GetNextReference();
    for (; p; p = aIter.GetNextReference())
    {
        if (!p->IsExternalRef())
            continue;
 
        if (p->GetIndex() == nFileId)
            return true;
    }
    return false;
}
 
class EraseRangeByIterator
{
    ScRangeName& mrRanges;
public:
    explicit EraseRangeByIterator(ScRangeName& rRanges) : mrRanges(rRanges) {}
    void operator() (const ScRangeName::const_iterator& itr)
    {
        mrRanges.erase(itr);
    }
};
 
/**
 * Remove all named ranges that contain references to specified source
 * document.
 */
void removeRangeNamesBySrcDoc(ScRangeName& rRanges, sal_uInt16 nFileId)
{
    ScRangeName::const_iterator itr = rRanges.begin(), itrEnd = rRanges.end();
    vector<ScRangeName::const_iterator> v;
    for (; itr != itrEnd; ++itr)
    {
        if (hasRefsToSrcDoc(*itr->second, nFileId))
            v.push_back(itr);
    }
    for_each(v.begin(), v.end(), EraseRangeByIterator(rRanges));
}
 
}
 
ScExternalRefCache::Table::Table()
    : mbReferenced( true )
      // Prevent accidental data loss due to lack of knowledge.
{
}
 
ScExternalRefCache::Table::~Table()
{
}
 
void ScExternalRefCache::Table::clear()
{
    maRows.clear();
    maCachedRanges.RemoveAll();
    mbReferenced = true;
}
 
void ScExternalRefCache::Table::setReferenced( bool bReferenced )
{
    mbReferenced = bReferenced;
}
 
bool ScExternalRefCache::Table::isReferenced() const
{
    return mbReferenced;
}
 
void ScExternalRefCache::Table::setCell(SCCOL nCol, SCROW nRow, TokenRef const & pToken, sal_uLong nFmtIndex, bool bSetCacheRange)
{
    using ::std::pair;
    RowsDataType::iterator itrRow = maRows.find(nRow);
    if (itrRow == maRows.end())
    {
        // This row does not exist yet.
        pair<RowsDataType::iterator, bool> res = maRows.emplace(
            nRow, RowDataType());
 
        if (!res.second)
            return;
 
        itrRow = res.first;
    }
 
    // Insert this token into the specified column location.  I don't need to
    // check for existing data.  Just overwrite it.
    RowDataType& rRow = itrRow->second;
    ScExternalRefCache::Cell aCell;
    aCell.mxToken = pToken;
    aCell.mnFmtIndex = nFmtIndex;
    rRow.emplace(nCol, aCell);
    if (bSetCacheRange)
        setCachedCell(nCol, nRow);
}
 
ScExternalRefCache::TokenRef ScExternalRefCache::Table::getCell(SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex) const
{
    RowsDataType::const_iterator itrTable = maRows.find(nRow);
    if (itrTable == maRows.end())
    {
        // this table doesn't have the specified row.
        return getEmptyOrNullToken(nCol, nRow);
    }
 
    const RowDataType& rRowData = itrTable->second;
    RowDataType::const_iterator itrRow = rRowData.find(nCol);
    if (itrRow == rRowData.end())
    {
        // this row doesn't have the specified column.
        return getEmptyOrNullToken(nCol, nRow);
    }
 
    const Cell& rCell = itrRow->second;
    if (pnFmtIndex)
        *pnFmtIndex = rCell.mnFmtIndex;
 
    return rCell.mxToken;
}
 
bool ScExternalRefCache::Table::hasRow( SCROW nRow ) const
{
    RowsDataType::const_iterator itrRow = maRows.find(nRow);
    return itrRow != maRows.end();
}
 
template< typename P >
void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, P predicate) const
{
    vector<SCROW> aRows;
    aRows.reserve(maRows.size());
    for (const auto& rEntry : maRows)
        if (predicate(rEntry))
            aRows.push_back(rEntry.first);
 
    // hash map is not ordered, so we need to explicitly sort it.
    ::std::sort(aRows.begin(), aRows.end());
    rRows.swap(aRows);
}
 
void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, SCROW nLow, SCROW nHigh) const
{
    getAllRows(rRows,
        [nLow, nHigh](std::pair<SCROW, RowDataType> rEntry) { return (nLow <= rEntry.first && rEntry.first <= nHigh); });
}
 
void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows) const
{
    getAllRows(rRows, [](std::pair<SCROW, RowDataType>) { return true; } );
}
 
::std::pair< SCROW, SCROW > ScExternalRefCache::Table::getRowRange() const
{
    ::std::pair< SCROW, SCROW > aRange( 0, 0 );
    if( !maRows.empty() )
    {
        // iterate over entire container (hash map is not sorted by key)
        auto itMinMax = std::minmax_element(maRows.begin(), maRows.end(),
            [](const RowsDataType::value_type& a, const RowsDataType::value_type& b) { return a.first < b.first; });
        aRange.first = itMinMax.first->first;
        aRange.second = itMinMax.second->first + 1;
    }
    return aRange;
}
 
template< typename P >
void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, P predicate) const
{
    RowsDataType::const_iterator itrRow = maRows.find(nRow);
    if (itrRow == maRows.end())
        // this table doesn't have the specified row.
        return;
 
    const RowDataType& rRowData = itrRow->second;
    vector<SCCOL> aCols;
    aCols.reserve(rRowData.size());
    for (const auto& rCol : rRowData)
        if (predicate(rCol))
            aCols.push_back(rCol.first);
 
    // hash map is not ordered, so we need to explicitly sort it.
    ::std::sort(aCols.begin(), aCols.end());
    rCols.swap(aCols);
}
 
void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, SCCOL nLow, SCCOL nHigh) const
{
    getAllCols(nRow, rCols,
        [nLow, nHigh](std::pair<SCCOL, Cell> rCol) { return nLow <= rCol.first && rCol.first <= nHigh; } );
}
 
void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols) const
{
    getAllCols(nRow, rCols, [](std::pair<SCCOL, Cell>) { return true; } );
}
 
::std::pair< SCCOL, SCCOL > ScExternalRefCache::Table::getColRange( SCROW nRow ) const
{
    ::std::pair< SCCOL, SCCOL > aRange( 0, 0 );
 
    RowsDataType::const_iterator itrRow = maRows.find( nRow );
    if (itrRow == maRows.end())
        // this table doesn't have the specified row.
        return aRange;
 
    const RowDataType& rRowData = itrRow->second;
    if( !rRowData.empty() )
    {
        // iterate over entire container (hash map is not sorted by key)
        auto itMinMax = std::minmax_element(rRowData.begin(), rRowData.end(),
            [](const RowDataType::value_type& a, const RowDataType::value_type& b) { return a.first < b.first; });
        aRange.first = itMinMax.first->first;
        aRange.second = itMinMax.second->first + 1;
    }
    return aRange;
}
 
void ScExternalRefCache::Table::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
{
    for (const auto& rRow : maRows)
    {
        const RowDataType& rRowData = rRow.second;
        for (const auto& rCol : rRowData)
        {
            const Cell& rCell = rCol.second;
            rNumFmts.push_back(rCell.mnFmtIndex);
        }
    }
}
 
bool ScExternalRefCache::Table::isRangeCached(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const
{
    return maCachedRanges.Contains(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
}
 
void ScExternalRefCache::Table::setCachedCell(SCCOL nCol, SCROW nRow)
{
    setCachedCellRange(nCol, nRow, nCol, nRow);
}
 
void ScExternalRefCache::Table::setCachedCellRange(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
{
    ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
    maCachedRanges.Join(aRange);
}
 
void ScExternalRefCache::Table::setWholeTableCached()
{
    setCachedCellRange(0, 0, MAXCOL, MAXROW);
}
 
bool ScExternalRefCache::Table::isInCachedRanges(SCCOL nCol, SCROW nRow) const
{
    return maCachedRanges.Contains(ScRange(nCol, nRow, 0, nCol, nRow, 0));
}
 
ScExternalRefCache::TokenRef ScExternalRefCache::Table::getEmptyOrNullToken(
    SCCOL nCol, SCROW nRow) const
{
    if (isInCachedRanges(nCol, nRow))
    {
        TokenRef p(new ScEmptyCellToken(false, false));
        return p;
    }
    return TokenRef();
}
 
ScExternalRefCache::TableName::TableName(OUString aUpper, OUString aReal) :
    maUpperName(std::move(aUpper)), maRealName(std::move(aReal))
{
}
 
ScExternalRefCache::CellFormat::CellFormat() :
    mbIsSet(false), mnType(SvNumFormatType::ALL), mnIndex(0)
{
}
 
ScExternalRefCache::ScExternalRefCache(const ScDocument& rDoc)
    : mrDoc(rDoc)
{
}
 
ScExternalRefCache::~ScExternalRefCache() {}
 
const OUString* ScExternalRefCache::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
    if (itrDoc == maDocs.end())
    {
        // specified document is not cached.
        return nullptr;
    }
 
    const DocItem& rDoc = itrDoc->second;
    TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
    if (itrTabId == rDoc.maTableNameIndex.end())
    {
        // the specified table is not in cache.
        return nullptr;
    }
 
    return &rDoc.maTableNames[itrTabId->second].maRealName;
}
 
const OUString* ScExternalRefCache::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
    if (itrDoc == maDocs.end())
    {
        // specified document is not cached.
        return nullptr;
    }
 
    const DocItem& rDoc = itrDoc->second;
    NamePairMap::const_iterator itr = rDoc.maRealRangeNameMap.find(
        ScGlobal::getCharClass().uppercase(rRangeName));
    if (itr == rDoc.maRealRangeNameMap.end())
        // range name not found.
        return nullptr;
 
    return &itr->second;
}
 
ScExternalRefCache::TokenRef ScExternalRefCache::getCellData(
    sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex)
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
    if (itrDoc == maDocs.end())
    {
        // specified document is not cached.
        return TokenRef();
    }
 
    const DocItem& rDoc = itrDoc->second;
    TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
    if (itrTabId == rDoc.maTableNameIndex.end())
    {
        // the specified table is not in cache.
        return TokenRef();
    }
 
    const TableTypeRef& pTableData = rDoc.maTables[itrTabId->second];
    if (!pTableData)
    {
        // the table data is not instantiated yet.
        return TokenRef();
    }
 
    return pTableData->getCell(nCol, nRow, pnFmtIndex);
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefCache::getCellRangeData(
    sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange)
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocDataType::iterator itrDoc = maDocs.find(nFileId);
    if (itrDoc == maDocs.end())
        // specified document is not cached.
        return TokenArrayRef();
 
    DocItem& rDoc = itrDoc->second;
 
    TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
    if (itrTabId == rDoc.maTableNameIndex.end())
        // the specified table is not in cache.
        return TokenArrayRef();
 
    const ScAddress& s = rRange.aStart;
    const ScAddress& e = rRange.aEnd;
 
    const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
    const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
    const SCROW nRow1 = s.Row(), nRow2 = e.Row();
 
    // Make sure I have all the tables cached.
    size_t nTabFirstId = itrTabId->second;
    size_t nTabLastId  = nTabFirstId + nTab2 - nTab1;
    if (nTabLastId >= rDoc.maTables.size())
        // not all tables are cached.
        return TokenArrayRef();
 
    ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
 
    RangeArrayMap::const_iterator itrRange = rDoc.maRangeArrays.find( aCacheRange);
    if (itrRange != rDoc.maRangeArrays.end())
        // Cache hit!
        return itrRange->second;
 
    std::unique_ptr<ScRange> pNewRange;
    TokenArrayRef pArray;
    bool bFirstTab = true;
    for (size_t nTab = nTabFirstId; nTab <= nTabLastId; ++nTab)
    {
        TableTypeRef pTab = rDoc.maTables[nTab];
        if (!pTab)
            return TokenArrayRef();
 
        SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
        SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
 
        if (!pTab->isRangeCached(nDataCol1, nDataRow1, nDataCol2, nDataRow2))
        {
            // specified range is not entirely within cached ranges.
            return TokenArrayRef();
        }
 
        SCSIZE nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
        SCSIZE nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
        ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
 
        // Needed in shrink and fill.
        vector<SCROW> aRows;
        pTab->getAllRows(aRows, nDataRow1, nDataRow2);
        bool bFill = true;
 
        // Check if size could be allocated and if not skip the fill, there's
        // one error element instead. But retry first with the actual data area
        // if that is smaller than the original range, which works for most
        // functions just not some that operate/compare with the original size
        // and expect empty values in non-data areas.
        // Restrict this though to ranges of entire columns or rows, other
        // ranges might be on purpose. (Other special cases to handle?)
        /* TODO: sparse matrix could help */
        SCSIZE nMatCols, nMatRows;
        xMat->GetDimensions( nMatCols, nMatRows);
        if (nMatCols != nMatrixColumns || nMatRows != nMatrixRows)
        {
            bFill = false;
            if (aRows.empty())
            {
                // There's no data at all. Set the one matrix element to empty
                // for column-repeated and row-repeated access.
                xMat->PutEmpty(0,0);
            }
            else if ((nCol1 == 0 && nCol2 == MAXCOL) || (nRow1 == 0 && nRow2 == MAXROW))
            {
                nDataRow1 = aRows.front();
                nDataRow2 = aRows.back();
                SCCOL nMinCol = std::numeric_limits<SCCOL>::max();
                SCCOL nMaxCol = std::numeric_limits<SCCOL>::min();
                for (const auto& rRow : aRows)
                {
                    vector<SCCOL> aCols;
                    pTab->getAllCols(rRow, aCols, nDataCol1, nDataCol2);
                    if (!aCols.empty())
                    {
                        nMinCol = std::min( nMinCol, aCols.front());
                        nMaxCol = std::max( nMaxCol, aCols.back());
                    }
                }
 
                if (nMinCol <= nMaxCol && ((o3tl::make_unsigned(nMaxCol-nMinCol+1) < nMatrixColumns) ||
                            (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows)))
                {
                    nMatrixColumns = static_cast<SCSIZE>(nMaxCol-nMinCol+1);
                    nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
                    xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
                    xMat->GetDimensions( nMatCols, nMatRows);
                    if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
                    {
                        nDataCol1 = nMinCol;
                        nDataCol2 = nMaxCol;
                        bFill = true;
                    }
                }
            }
        }
 
        if (bFill)
        {
            // Only fill non-empty cells, for better performance.
            for (SCROW nRow : aRows)
            {
                vector<SCCOL> aCols;
                pTab->getAllCols(nRow, aCols, nDataCol1, nDataCol2);
                for (SCCOL nCol : aCols)
                {
                    TokenRef pToken = pTab->getCell(nCol, nRow);
                    if (!pToken)
                        // This should never happen!
                        return TokenArrayRef();
 
                    SCSIZE nC = nCol - nDataCol1, nR = nRow - nDataRow1;
                    switch (pToken->GetType())
                    {
                        case svDouble:
                            xMat->PutDouble(pToken->GetDouble(), nC, nR);
                            break;
                        case svString:
                            xMat->PutString(pToken->GetString(), nC, nR);
                            break;
                        default:
                            ;
                    }
                }
            }
 
            if (!bFirstTab)
                pArray->AddOpCode(ocSep);
 
            ScMatrixToken aToken(std::move(xMat));
            if (!pArray)
                pArray = std::make_shared<ScTokenArray>(mrDoc);
            pArray->AddToken(aToken);
 
            bFirstTab = false;
 
            if (!pNewRange)
                pNewRange.reset(new ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
            else
                pNewRange->ExtendTo(ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
        }
    }
 
    rDoc.maRangeArrays.emplace(aCacheRange, pArray);
    if (pNewRange && *pNewRange != aCacheRange)
        rDoc.maRangeArrays.emplace(*pNewRange, pArray);
 
    return pArray;
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefCache::getRangeNameTokens(sal_uInt16 nFileId, const OUString& rName)
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocItem* pDoc = getDocItem(aGuard, nFileId);
    if (!pDoc)
        return TokenArrayRef();
 
    RangeNameMap& rMap = pDoc->maRangeNames;
    RangeNameMap::const_iterator itr = rMap.find(
        ScGlobal::getCharClass().uppercase(rName));
    if (itr == rMap.end())
        return TokenArrayRef();
 
    return itr->second;
}
 
void ScExternalRefCache::setRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const TokenArrayRef& pArray)
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocItem* pDoc = getDocItem(aGuard, nFileId);
    if (!pDoc)
        return;
 
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    RangeNameMap& rMap = pDoc->maRangeNames;
    rMap.emplace(aUpperName, pArray);
    pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
}
 
bool ScExternalRefCache::isValidRangeName(sal_uInt16 nFileId, const OUString& rName) const
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocItem* pDoc = getDocItem(aGuard, nFileId);
    if (!pDoc)
        return false;
 
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    const RangeNameMap& rMap = pDoc->maRangeNames;
    return rMap.count(aUpperName) > 0;
}
 
void ScExternalRefCache::setRangeName(sal_uInt16 nFileId, const OUString& rName)
{
    std::unique_lock aGuard(maMtxDocs);
 
    DocItem* pDoc = getDocItem(aGuard, nFileId);
    if (!pDoc)
        return;
 
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
}
 
void ScExternalRefCache::setCellData(sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow,
                                     TokenRef const & pToken, sal_uLong nFmtIndex)
{
    if (!isDocInitialized(nFileId))
        return;
 
    using ::std::pair;
    DocItem* pDocItem = getDocItem(nFileId);
    if (!pDocItem)
        return;
 
    DocItem& rDoc = *pDocItem;
 
    // See if the table by this name already exists.
    TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rTabName);
    if (itrTabName == rDoc.maTableNameIndex.end())
        // Table not found.  Maybe the table name or the file id is wrong ???
        return;
 
    TableTypeRef& pTableData = rDoc.maTables[itrTabName->second];
    if (!pTableData)
        pTableData = std::make_shared<Table>();
 
    pTableData->setCell(nCol, nRow, pToken, nFmtIndex);
    pTableData->setCachedCell(nCol, nRow);
}
 
void ScExternalRefCache::setCellRangeData(sal_uInt16 nFileId, const ScRange& rRange, const vector<SingleRangeData>& rData,
                                          const TokenArrayRef& pArray)
{
    using ::std::pair;
    if (rData.empty() || !isDocInitialized(nFileId))
        // nothing to cache
        return;
 
    // First, get the document item for the given file ID.
    DocItem* pDocItem = getDocItem(nFileId);
    if (!pDocItem)
        return;
 
    DocItem& rDoc = *pDocItem;
 
    // Now, find the table position of the first table to cache.
    const OUString& rFirstTabName = rData.front().maTableName;
    TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rFirstTabName);
    if (itrTabName == rDoc.maTableNameIndex.end())
    {
        // table index not found.
        return;
    }
 
    size_t nTabFirstId = itrTabName->second;
    SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
    SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
    size_t i = nTabFirstId;
    for (const auto& rItem : rData)
    {
        TableTypeRef& pTabData = rDoc.maTables[i];
        if (!pTabData)
            pTabData = std::make_shared<Table>();
 
        const ScMatrixRef& pMat = rItem.mpRangeData;
        SCSIZE nMatCols, nMatRows;
        pMat->GetDimensions( nMatCols, nMatRows);
        if (nMatCols > o3tl::make_unsigned(nCol2 - nCol1) && nMatRows > o3tl::make_unsigned(nRow2 - nRow1))
        {
            ScMatrix::DoubleOpFunction aDoubleFunc = [=](size_t row, size_t col, double val) -> void
            {
                pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val), 0, false);
            };
            ScMatrix::BoolOpFunction aBoolFunc = [=](size_t row, size_t col, bool val) -> void
            {
                pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val ? 1.0 : 0.0), 0, false);
            };
            ScMatrix::StringOpFunction aStringFunc = [=](size_t row, size_t col, svl::SharedString val) -> void
            {
                pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaStringToken(std::move(val)), 0, false);
            };
            ScMatrix::EmptyOpFunction aEmptyFunc = [](size_t /*row*/, size_t /*col*/) -> void
            {
                // Nothing. Empty cell.
            };
            pMat->ExecuteOperation(std::pair<size_t, size_t>(0, 0),
                    std::pair<size_t, size_t>(nRow2-nRow1, nCol2-nCol1),
                    std::move(aDoubleFunc), std::move(aBoolFunc), std::move(aStringFunc), std::move(aEmptyFunc));
            // Mark the whole range 'cached'.
            pTabData->setCachedCellRange(nCol1, nRow1, nCol2, nRow2);
        }
        else
        {
            // This may happen due to a matrix not been allocated earlier, in
            // which case it should have exactly one error element.
            SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix size mismatch");
            if (nMatCols != 1 || nMatRows != 1)
                SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - not a one element matrix");
            else
            {
                FormulaError nErr = GetDoubleErrorValue( pMat->GetDouble(0,0));
                SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix error value is " << static_cast<int>(nErr) <<
                        (nErr == FormulaError::MatrixSize ? ", ok" : ", not ok"));
            }
        }
        ++i;
    }
 
    size_t nTabLastId = nTabFirstId + rRange.aEnd.Tab() - rRange.aStart.Tab();
    ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
 
    rDoc.maRangeArrays.emplace(aCacheRange, pArray);
}
 
bool ScExternalRefCache::isDocInitialized(sal_uInt16 nFileId)
{
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc)
        return false;
 
    return pDoc->mbInitFromSource;
}
 
static bool lcl_getStrictTableDataIndex(const ScExternalRefCache::TableNameIndexMap& rMap, const OUString& rName, size_t& rIndex)
{
    ScExternalRefCache::TableNameIndexMap::const_iterator itr = rMap.find(rName);
    if (itr == rMap.end())
        return false;
 
    rIndex = itr->second;
    return true;
}
 
bool ScExternalRefCache::DocItem::getTableDataIndex( const OUString& rTabName, size_t& rIndex ) const
{
    ScExternalRefCache::TableNameIndexMap::const_iterator itr = findTableNameIndex(rTabName);
    if (itr == maTableNameIndex.end())
        return false;
 
    rIndex = itr->second;
    return true;
}
 
namespace {
OUString getFirstSheetName()
{
    // Get Custom prefix.
    const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
    // Form sheet name identical to the first generated sheet name when
    // creating an internal document, e.g. 'Sheet1'.
    return rOpt.GetInitTabPrefix() + "1";
}
}
 
void ScExternalRefCache::initializeDoc(sal_uInt16 nFileId, const vector<OUString>& rTabNames,
        const OUString& rBaseName)
{
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc)
        return;
 
    size_t n = rTabNames.size();
 
    // table name list - the list must include all table names in the source
    // document and only to be populated when loading the source document, not
    // when loading cached data from, say, Excel XCT/CRN records.
    vector<TableName> aNewTabNames;
    aNewTabNames.reserve(n);
    for (const auto& rTabName : rTabNames)
    {
        TableName aNameItem(ScGlobal::getCharClass().uppercase(rTabName), rTabName);
        aNewTabNames.push_back(aNameItem);
    }
    pDoc->maTableNames.swap(aNewTabNames);
 
    // data tables - preserve any existing data that may have been set during
    // file import.
    vector<TableTypeRef> aNewTables(n);
    for (size_t i = 0; i < n; ++i)
    {
        size_t nIndex;
        if (lcl_getStrictTableDataIndex(pDoc->maTableNameIndex, pDoc->maTableNames[i].maUpperName, nIndex))
        {
            aNewTables[i] = pDoc->maTables[nIndex];
        }
    }
    pDoc->maTables.swap(aNewTables);
 
    // name index map
    TableNameIndexMap aNewNameIndex;
    for (size_t i = 0; i < n; ++i)
        aNewNameIndex.emplace(pDoc->maTableNames[i].maUpperName, i);
    pDoc->maTableNameIndex.swap(aNewNameIndex);
 
    // Setup name for Sheet1 vs base name to be able to load documents
    // that store the base name as table name, or vice versa.
    pDoc->maSingleTableNameAlias.clear();
    if (!rBaseName.isEmpty() && pDoc->maTableNames.size() == 1)
    {
        OUString aSheetName = getFirstSheetName();
        // If the one and only table name matches exactly, carry on the base
        // file name for further alias use. If instead the table name matches
        // the base name, carry on the sheet name as alias.
        if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, aSheetName))
            pDoc->maSingleTableNameAlias = rBaseName;
        else if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, rBaseName))
            pDoc->maSingleTableNameAlias = aSheetName;
    }
 
    pDoc->mbInitFromSource = true;
}
 
ScExternalRefCache::TableNameIndexMap::const_iterator ScExternalRefCache::DocItem::findTableNameIndex(
        const OUString& rTabName ) const
{
    const OUString aTabNameUpper = ScGlobal::getCharClass().uppercase( rTabName);
    TableNameIndexMap::const_iterator itrTabName = maTableNameIndex.find( aTabNameUpper);
    if (itrTabName != maTableNameIndex.end())
        return itrTabName;
 
    // Since some time for external references to CSV files the base name is
    // used as sheet name instead of Sheet1, check if we can resolve that.
    // Also helps users that got accustomed to one or the other way.
    if (maSingleTableNameAlias.isEmpty() || maTableNameIndex.size() != 1)
        return itrTabName;
 
    // maSingleTableNameAlias has been set up only if the original file loaded
    // had exactly one sheet and internal sheet name was Sheet1 or localized or
    // customized equivalent, or base name.
    if (aTabNameUpper == ScGlobal::getCharClass().uppercase( maSingleTableNameAlias))
        return maTableNameIndex.begin();
 
    return itrTabName;
}
 
bool ScExternalRefCache::DocItem::getSingleTableNameAlternative( OUString& rTabName ) const
{
    if (maSingleTableNameAlias.isEmpty() || maTableNames.size() != 1)
        return false;
    if (ScGlobal::GetTransliteration().isEqual( rTabName, maTableNames[0].maRealName))
    {
        rTabName = maSingleTableNameAlias;
        return true;
    }
    if (ScGlobal::GetTransliteration().isEqual( rTabName, maSingleTableNameAlias))
    {
        rTabName = maTableNames[0].maRealName;
        return true;
    }
    return false;
}
 
bool ScExternalRefCache::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
        sal_uInt16 nFileId ) const
{
    bool bFound = rSrcDoc.GetTable( rTabName, rTab);
    if (!bFound)
    {
        // Check the one table alias alternative.
        const DocItem* pDoc = getDocItem( nFileId );
        if (pDoc)
        {
            OUString aTabName( rTabName);
            if (pDoc->getSingleTableNameAlternative( aTabName))
                bFound = rSrcDoc.GetTable( aTabName, rTab);
        }
    }
    return bFound;
}
 
OUString ScExternalRefCache::getTableName(sal_uInt16 nFileId, size_t nCacheId) const
{
    if( DocItem* pDoc = getDocItem( nFileId ) )
        if( nCacheId < pDoc->maTableNames.size() )
            return pDoc->maTableNames[ nCacheId ].maRealName;
    return OUString();
}
 
void ScExternalRefCache::getAllTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
{
    rTabNames.clear();
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc)
        return;
 
    size_t n = pDoc->maTableNames.size();
    rTabNames.reserve(n);
    for (const auto& rTableName : pDoc->maTableNames)
        rTabNames.push_back(rTableName.maRealName);
}
 
SCTAB ScExternalRefCache::getTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
{
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc)
        return -1;
 
    vector<TableName>::const_iterator itrBeg = pDoc->maTableNames.begin();
    vector<TableName>::const_iterator itrEnd = pDoc->maTableNames.end();
 
    vector<TableName>::const_iterator itrStartTab = ::std::find_if( itrBeg, itrEnd,
            TabNameSearchPredicate( rStartTabName));
    if (itrStartTab == itrEnd)
        return -1;
 
    vector<TableName>::const_iterator itrEndTab = ::std::find_if( itrBeg, itrEnd,
            TabNameSearchPredicate( rEndTabName));
    if (itrEndTab == itrEnd)
        return 0;
 
    size_t nStartDist = ::std::distance( itrBeg, itrStartTab);
    size_t nEndDist = ::std::distance( itrBeg, itrEndTab);
    return nStartDist <= nEndDist ? static_cast<SCTAB>(nEndDist - nStartDist + 1) : -static_cast<SCTAB>(nStartDist - nEndDist + 1);
}
 
void ScExternalRefCache::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
{
    std::unique_lock aGuard(maMtxDocs);
 
    using ::std::sort;
    using ::std::unique;
 
    vector<sal_uInt32> aNumFmts;
    for (const auto& rEntry : maDocs)
    {
        const vector<TableTypeRef>& rTables = rEntry.second.maTables;
        for (const TableTypeRef& pTab : rTables)
        {
            if (!pTab)
                continue;
 
            pTab->getAllNumberFormats(aNumFmts);
        }
    }
 
    // remove duplicates.
    sort(aNumFmts.begin(), aNumFmts.end());
    aNumFmts.erase(unique(aNumFmts.begin(), aNumFmts.end()), aNumFmts.end());
    rNumFmts.swap(aNumFmts);
}
 
bool ScExternalRefCache::setCacheDocReferenced( sal_uInt16 nFileId )
{
    DocItem* pDocItem = getDocItem(nFileId);
    if (!pDocItem)
        return areAllCacheTablesReferenced();
 
    for (auto& rxTab : pDocItem->maTables)
    {
        if (rxTab)
            rxTab->setReferenced(true);
    }
    addCacheDocToReferenced( nFileId);
    return areAllCacheTablesReferenced();
}
 
bool ScExternalRefCache::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
{
    DocItem* pDoc = getDocItem(nFileId);
    if (pDoc)
    {
        size_t nIndex = 0;
        if (pDoc->getTableDataIndex( rTabName, nIndex))
        {
            size_t nStop = ::std::min( nIndex + nSheets, pDoc->maTables.size());
            for (size_t i = nIndex; i < nStop; ++i)
            {
                TableTypeRef pTab = pDoc->maTables[i];
                if (pTab)
                {
                    if (!pTab->isReferenced())
                    {
                        pTab->setReferenced(true);
                        addCacheTableToReferenced( nFileId, i);
                    }
                }
            }
        }
    }
    return areAllCacheTablesReferenced();
}
 
void ScExternalRefCache::setAllCacheTableReferencedStati( bool bReferenced )
{
    std::unique_lock aGuard(maMtxDocs);
 
    if (bReferenced)
    {
        maReferenced.reset(0);
        for (auto& rEntry : maDocs)
        {
            ScExternalRefCache::DocItem& rDocItem = rEntry.second;
            for (auto& rxTab : rDocItem.maTables)
            {
                if (rxTab)
                    rxTab->setReferenced(true);
            }
        }
    }
    else
    {
        size_t nDocs = 0;
        auto itrMax = std::max_element(maDocs.begin(), maDocs.end(),
            [](const DocDataType::value_type& a, const DocDataType::value_type& b) { return a.first < b.first; });
        if (itrMax != maDocs.end())
            nDocs = itrMax->first + 1;
        maReferenced.reset( nDocs);
 
        for (auto& [nFileId, rDocItem] : maDocs)
        {
            size_t nTables = rDocItem.maTables.size();
            ReferencedStatus::DocReferenced & rDocReferenced = maReferenced.maDocs[nFileId];
            // All referenced => non-existing tables evaluate as completed.
            rDocReferenced.maTables.resize( nTables, true);
            for (size_t i=0; i < nTables; ++i)
            {
                TableTypeRef & xTab = rDocItem.maTables[i];
                if (xTab)
                {
                    xTab->setReferenced(false);
                    rDocReferenced.maTables[i] = false;
                    rDocReferenced.mbAllTablesReferenced = false;
                    // An addCacheTableToReferenced() actually may have
                    // resulted in mbAllReferenced been set. Clear it.
                    maReferenced.mbAllReferenced = false;
                }
            }
        }
    }
}
 
void ScExternalRefCache::addCacheTableToReferenced( sal_uInt16 nFileId, size_t nIndex )
{
    if (nFileId >= maReferenced.maDocs.size())
        return;
 
    ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
    size_t nTables = rTables.size();
    if (nIndex >= nTables)
        return;
 
    if (!rTables[nIndex])
    {
        rTables[nIndex] = true;
        size_t i = 0;
        while (i < nTables && rTables[i])
            ++i;
        if (i == nTables)
        {
            maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
            maReferenced.checkAllDocs();
        }
    }
}
 
void ScExternalRefCache::addCacheDocToReferenced( sal_uInt16 nFileId )
{
    if (nFileId >= maReferenced.maDocs.size())
        return;
 
    if (!maReferenced.maDocs[nFileId].mbAllTablesReferenced)
    {
        ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
        size_t nSize = rTables.size();
        for (size_t i=0; i < nSize; ++i)
            rTables[i] = true;
        maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
        maReferenced.checkAllDocs();
    }
}
 
void ScExternalRefCache::getAllCachedDataSpans( const ScDocument& rSrcDoc, sal_uInt16 nFileId, sc::ColumnSpanSet& rSet ) const
{
    const DocItem* pDocItem = getDocItem(nFileId);
    if (!pDocItem)
        // This document is not cached.
        return;
 
    const std::vector<TableTypeRef>& rTables = pDocItem->maTables;
    for (size_t nTab = 0, nTabCount = rTables.size(); nTab < nTabCount; ++nTab)
    {
        TableTypeRef pTab = rTables[nTab];
        if (!pTab)
            continue;
 
        std::vector<SCROW> aRows;
        pTab->getAllRows(aRows);
        for (SCROW nRow : aRows)
        {
            std::vector<SCCOL> aCols;
            pTab->getAllCols(nRow, aCols);
            for (SCCOL nCol : aCols)
            {
                rSet.set(rSrcDoc, nTab, nCol, nRow, true);
            }
        }
    }
}
 
ScExternalRefCache::ReferencedStatus::ReferencedStatus() :
    mbAllReferenced(false)
{
    reset(0);
}
 
void ScExternalRefCache::ReferencedStatus::reset( size_t nDocs )
{
    if (nDocs)
    {
        mbAllReferenced = false;
        DocReferencedVec aRefs( nDocs);
        maDocs.swap( aRefs);
    }
    else
    {
        mbAllReferenced = true;
        DocReferencedVec aRefs;
        maDocs.swap( aRefs);
    }
}
 
void ScExternalRefCache::ReferencedStatus::checkAllDocs()
{
    if (std::all_of(maDocs.begin(), maDocs.end(), [](const DocReferenced& rDoc) { return rDoc.mbAllTablesReferenced; }))
        mbAllReferenced = true;
}
 
ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
{
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc || nTabIndex >= pDoc->maTables.size())
        return TableTypeRef();
 
    return pDoc->maTables[nTabIndex];
}
 
ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, const OUString& rTabName,
        bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
{
    // In API, the index is transported as cached sheet ID of type sal_Int32 in
    // sheet::SingleReference.Sheet or sheet::ComplexReference.Reference1.Sheet
    // in a sheet::FormulaToken, choose a sensible value for N/A. Effectively
    // being 0xffffffff
    const size_t nNotAvailable = static_cast<size_t>( static_cast<sal_Int32>( -1));
 
    DocItem* pDoc = getDocItem(nFileId);
    if (!pDoc)
    {
        if (pnIndex) *pnIndex = nNotAvailable;
        return TableTypeRef();
    }
 
    DocItem& rDoc = *pDoc;
 
    size_t nIndex;
    if (rDoc.getTableDataIndex(rTabName, nIndex))
    {
        // specified table found.
        if( pnIndex ) *pnIndex = nIndex;
        if (bCreateNew && !rDoc.maTables[nIndex])
            rDoc.maTables[nIndex] = std::make_shared<Table>();
 
        return rDoc.maTables[nIndex];
    }
 
    if (!bCreateNew)
    {
        if (pnIndex) *pnIndex = nNotAvailable;
        return TableTypeRef();
    }
 
    // If this is the first table to be created propagate the base name or
    // Sheet1 as an alias. For subsequent tables remove it again.
    if (rDoc.maTableNames.empty())
    {
        if (pExtUrl)
        {
            const OUString aBaseName( INetURLObject( *pExtUrl).GetBase());
            const OUString aSheetName( getFirstSheetName());
            if (ScGlobal::GetTransliteration().isEqual( rTabName, aSheetName))
                pDoc->maSingleTableNameAlias = aBaseName;
            else if (ScGlobal::GetTransliteration().isEqual( rTabName, aBaseName))
                pDoc->maSingleTableNameAlias = aSheetName;
        }
    }
    else
    {
        rDoc.maSingleTableNameAlias.clear();
    }
 
    // Specified table doesn't exist yet.  Create one.
    OUString aTabNameUpper = ScGlobal::getCharClass().uppercase(rTabName);
    nIndex = rDoc.maTables.size();
    if( pnIndex ) *pnIndex = nIndex;
    TableTypeRef pTab = std::make_shared<Table>();
    rDoc.maTables.push_back(pTab);
    rDoc.maTableNames.emplace_back(aTabNameUpper, rTabName);
    rDoc.maTableNameIndex.emplace(aTabNameUpper, nIndex);
    return pTab;
}
 
void ScExternalRefCache::clearCache(sal_uInt16 nFileId)
{
    std::unique_lock aGuard(maMtxDocs);
    maDocs.erase(nFileId);
}
 
void ScExternalRefCache::clearCacheTables(sal_uInt16 nFileId)
{
    std::unique_lock aGuard(maMtxDocs);
    DocItem* pDocItem = getDocItem(aGuard, nFileId);
    if (!pDocItem)
        // This document is not cached at all.
        return;
 
    // Clear all cache table content, but keep the tables.
    std::vector<TableTypeRef>& rTabs = pDocItem->maTables;
    for (TableTypeRef & pTab : rTabs)
    {
        if (!pTab)
            continue;
 
        pTab->clear();
    }
 
    // Clear the external range name caches.
    pDocItem->maRangeNames.clear();
    pDocItem->maRangeArrays.clear();
    pDocItem->maRealRangeNameMap.clear();
}
 
ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(sal_uInt16 nFileId) const
{
    std::unique_lock aGuard(maMtxDocs);
    return getDocItem(aGuard, nFileId);
}
 
ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(std::unique_lock<std::mutex>& /*rGuard*/, sal_uInt16 nFileId) const
{
 
    using ::std::pair;
    DocDataType::iterator itrDoc = maDocs.find(nFileId);
    if (itrDoc == maDocs.end())
    {
        // specified document is not cached.
        pair<DocDataType::iterator, bool> res = maDocs.emplace(
                nFileId, DocItem());
 
        if (!res.second)
            // insertion failed.
            return nullptr;
 
        itrDoc = res.first;
    }
 
    return &itrDoc->second;
}
 
ScExternalRefLink::ScExternalRefLink(ScDocument& rDoc, sal_uInt16 nFileId) :
    ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE),
    mnFileId(nFileId),
    mrDoc(rDoc),
    mbDoRefresh(true)
{
}
 
ScExternalRefLink::~ScExternalRefLink()
{
}
 
void ScExternalRefLink::Closed()
{
    ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
    pMgr->breakLink(mnFileId);
}
 
::sfx2::SvBaseLink::UpdateResult ScExternalRefLink::DataChanged(const OUString& /*rMimeType*/, const Any& /*rValue*/)
{
    if (!mbDoRefresh)
        return SUCCESS;
 
    OUString aFile, aFilter;
    sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, nullptr, &aFilter);
    ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
 
    if (!pMgr->isFileLoadable(aFile))
        return ERROR_GENERAL;
 
    const OUString* pCurFile = pMgr->getExternalFileName(mnFileId);
    if (!pCurFile)
        return ERROR_GENERAL;
 
    if (*pCurFile == aFile)
    {
        // Refresh the current source document.
        if (!pMgr->refreshSrcDocument(mnFileId))
            return ERROR_GENERAL;
    }
    else
    {
        // The source document has changed.
        ScViewData* pViewData = ScDocShell::GetViewData();
        if (!pViewData)
            return ERROR_GENERAL;
 
        ScDocShell* pDocShell = pViewData->GetDocShell();
        ScDocShellModificator aMod(*pDocShell);
        pMgr->switchSrcFile(mnFileId, aFile, aFilter);
        aMod.SetDocumentModified();
    }
 
    return SUCCESS;
}
 
void ScExternalRefLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /*rEndEditHdl*/)
{
    SvBaseLink::Edit(pParent, Link<SvBaseLink&,void>());
}
 
void ScExternalRefLink::SetDoRefresh(bool b)
{
    mbDoRefresh = b;
}
 
static FormulaToken* convertToToken( ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRefCellValue& rCell )
{
    if (rCell.hasEmptyValue())
    {
        bool bInherited = (rCell.getType() == CELLTYPE_FORMULA);
        return new ScEmptyCellToken(bInherited, false);
    }
 
    switch (rCell.getType())
    {
        case CELLTYPE_EDIT:
        case CELLTYPE_STRING:
        {
            OUString aStr = rCell.getString(&rSrcDoc);
            svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern(aStr);
            return new formula::FormulaStringToken(std::move(aSS));
        }
        case CELLTYPE_VALUE:
            return new formula::FormulaDoubleToken(rCell.getDouble());
        case CELLTYPE_FORMULA:
        {
            ScFormulaCell* pFCell = rCell.getFormula();
            FormulaError nError = pFCell->GetErrCode();
            if (nError != FormulaError::NONE)
                return new FormulaErrorToken( nError);
            else if (pFCell->IsValue())
            {
                double fVal = pFCell->GetValue();
                return new formula::FormulaDoubleToken(fVal);
            }
            else
            {
                svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern( pFCell->GetString().getString());
                return new formula::FormulaStringToken(std::move(aSS));
            }
        }
        default:
            OSL_FAIL("attempted to convert an unknown cell type.");
    }
 
    return nullptr;
}
 
static std::unique_ptr<ScTokenArray> convertToTokenArray(
    ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRange& rRange, vector<ScExternalRefCache::SingleRangeData>& rCacheData )
{
    ScAddress& s = rRange.aStart;
    ScAddress& e = rRange.aEnd;
 
    const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
    const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
    const SCROW nRow1 = s.Row(), nRow2 = e.Row();
 
    if (nTab2 != nTab1)
        // For now, we don't support multi-sheet ranges intentionally because
        // we don't have a way to express them in a single token.  In the
        // future we can introduce a new stack variable type svMatrixList with
        // a new token type that can store a 3D matrix value and convert a 3D
        // range to it.
        return nullptr;
 
    std::unique_ptr<ScRange> pUsedRange;
 
    unique_ptr<ScTokenArray> pArray(new ScTokenArray(rSrcDoc));
    bool bFirstTab = true;
    vector<ScExternalRefCache::SingleRangeData>::iterator
        itrCache = rCacheData.begin(), itrCacheEnd = rCacheData.end();
 
    for (SCTAB nTab = nTab1; nTab <= nTab2 && itrCache != itrCacheEnd; ++nTab, ++itrCache)
    {
        // Only loop within the data area.
        SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
        SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
        bool bShrunk;
        if (!rSrcDoc.ShrinkToUsedDataArea( bShrunk, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, false))
            // no data within specified range.
            continue;
 
        if (pUsedRange)
            // Make sure the used area only grows, not shrinks.
            pUsedRange->ExtendTo(ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
        else
            pUsedRange.reset(new ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
 
        SCSIZE nMatrixColumns = static_cast<SCSIZE>(nCol2-nCol1+1);
        SCSIZE nMatrixRows = static_cast<SCSIZE>(nRow2-nRow1+1);
        ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
 
        // Check if size could be allocated and if not skip the fill, there's
        // one error element instead. But retry first with the actual data area
        // if that is smaller than the original range, which works for most
        // functions just not some that operate/compare with the original size
        // and expect empty values in non-data areas.
        // Restrict this though to ranges of entire columns or rows, other
        // ranges might be on purpose. (Other special cases to handle?)
        /* TODO: sparse matrix could help */
        SCSIZE nMatCols, nMatRows;
        xMat->GetDimensions( nMatCols, nMatRows);
        if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
        {
            rSrcDoc.FillMatrix(*xMat, nTab, nCol1, nRow1, nCol2, nRow2, &rHostDoc.GetSharedStringPool());
        }
        else if ((nCol1 == 0 && nCol2 == rSrcDoc.MaxCol()) || (nRow1 == 0 && nRow2 == rSrcDoc.MaxRow()))
        {
            if ((o3tl::make_unsigned(nDataCol2-nDataCol1+1) < nMatrixColumns) ||
                (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows))
            {
                nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
                nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
                xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
                xMat->GetDimensions( nMatCols, nMatRows);
                if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
                    rSrcDoc.FillMatrix(*xMat, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, &rHostDoc.GetSharedStringPool());
            }
        }
 
        if (!bFirstTab)
            pArray->AddOpCode(ocSep);
 
        ScMatrixToken aToken(xMat);
        pArray->AddToken(aToken);
 
        itrCache->mpRangeData = std::move(xMat);
 
        bFirstTab = false;
    }
 
    if (!pUsedRange)
        return nullptr;
 
    s.SetCol(pUsedRange->aStart.Col());
    s.SetRow(pUsedRange->aStart.Row());
    e.SetCol(pUsedRange->aEnd.Col());
    e.SetRow(pUsedRange->aEnd.Row());
 
    return pArray;
}
 
static std::unique_ptr<ScTokenArray> lcl_fillEmptyMatrix(const ScDocument& rDoc, const ScRange& rRange)
{
    SCSIZE nC = static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1);
    SCSIZE nR = static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1);
    ScMatrixRef xMat = new ScMatrix(nC, nR);
 
    ScMatrixToken aToken(std::move(xMat));
    unique_ptr<ScTokenArray> pArray(new ScTokenArray(rDoc));
    pArray->AddToken(aToken);
    return pArray;
}
 
namespace {
bool isLinkUpdateAllowedInDoc(const ScDocument& rDoc)
{
    ScDocShell* pDocShell = rDoc.GetDocumentShell();
    if (!pDocShell)
        return rDoc.IsFunctionAccess();
 
    return pDocShell->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate();
}
}
 
ScExternalRefManager::ScExternalRefManager(ScDocument& rDoc) :
    mrDoc(rDoc),
    maRefCache(rDoc),
    mbInReferenceMarking(false),
    mbUserInteractionEnabled(true),
    mbDocTimerEnabled(true),
    maSrcDocTimer( "sc::ScExternalRefManager maSrcDocTimer" )
{
    maSrcDocTimer.SetInvokeHandler( LINK(this, ScExternalRefManager, TimeOutHdl) );
    maSrcDocTimer.SetTimeout(SRCDOC_SCAN_INTERVAL);
}
 
ScExternalRefManager::~ScExternalRefManager()
{
    clear();
}
 
OUString ScExternalRefManager::getCacheTableName(sal_uInt16 nFileId, size_t nTabIndex) const
{
    return maRefCache.getTableName(nFileId, nTabIndex);
}
 
ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
{
    return maRefCache.getCacheTable(nFileId, nTabIndex);
}
 
ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(
    sal_uInt16 nFileId, const OUString& rTabName, bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
{
    return maRefCache.getCacheTable(nFileId, rTabName, bCreateNew, pnIndex, pExtUrl);
}
 
ScExternalRefManager::LinkListener::LinkListener()
{
}
 
ScExternalRefManager::LinkListener::~LinkListener()
{
}
 
ScExternalRefManager::ApiGuard::ApiGuard(const ScDocument& rDoc) :
    mpMgr(rDoc.GetExternalRefManager()),
    mbOldInteractionEnabled(mpMgr->mbUserInteractionEnabled)
{
    // We don't want user interaction handled in the API.
    mpMgr->mbUserInteractionEnabled = false;
}
 
ScExternalRefManager::ApiGuard::~ApiGuard()
{
    // Restore old value.
    mpMgr->mbUserInteractionEnabled = mbOldInteractionEnabled;
}
 
void ScExternalRefManager::getAllCachedTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
{
    maRefCache.getAllTableNames(nFileId, rTabNames);
}
 
SCTAB ScExternalRefManager::getCachedTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
{
    return maRefCache.getTabSpan( nFileId, rStartTabName, rEndTabName);
}
 
void ScExternalRefManager::getAllCachedNumberFormats(vector<sal_uInt32>& rNumFmts) const
{
    maRefCache.getAllNumberFormats(rNumFmts);
}
 
sal_uInt16 ScExternalRefManager::getExternalFileCount() const
{
    return static_cast< sal_uInt16 >( maSrcFiles.size() );
}
 
void ScExternalRefManager::markUsedByLinkListeners()
{
    bool bAllMarked = false;
    for (const auto& [rFileId, rLinkListeners] : maLinkListeners)
    {
        if (!rLinkListeners.empty())
            bAllMarked = maRefCache.setCacheDocReferenced(rFileId);
 
        if (bAllMarked)
            break;
        /* TODO: LinkListeners should remember the table they're listening to.
         * As is, listening to one table will mark all tables of the document
         * being referenced. */
    }
}
 
void ScExternalRefManager::markUsedExternalRefCells()
{
    for (const auto& rEntry : maRefCells)
    {
        for (ScFormulaCell* pCell : rEntry.second)
        {
            bool bUsed = pCell->MarkUsedExternalReferences();
            if (bUsed)
                // Return true when at least one cell references external docs.
                return;
        }
    }
}
 
bool ScExternalRefManager::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
{
    return maRefCache.setCacheTableReferenced( nFileId, rTabName, nSheets);
}
 
void ScExternalRefManager::setAllCacheTableReferencedStati( bool bReferenced )
{
    mbInReferenceMarking = !bReferenced;
    maRefCache.setAllCacheTableReferencedStati( bReferenced );
}
 
void ScExternalRefManager::storeRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const ScTokenArray& rArray)
{
    ScExternalRefCache::TokenArrayRef pNewArray;
    if (!rArray.HasExternalRef())
    {
        // Parse all tokens in this external range data, and replace each absolute
        // reference token with an external reference token, and cache them.
        pNewArray = std::make_shared<ScTokenArray>(mrDoc);
        FormulaTokenArrayPlainIterator aIter(rArray);
        for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next())
        {
            bool bTokenAdded = false;
            switch (pToken->GetType())
            {
                case svSingleRef:
                {
                    const ScSingleRefData& rRef = *pToken->GetSingleRef();
                    OUString aTabName;
                    if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0)
                        aTabName = maRefCache.getTableName(nFileId, nCacheId);
                    ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString(aTabName),   // string not interned
                        *pToken->GetSingleRef());
                    pNewArray->AddToken(aNewToken);
                    bTokenAdded = true;
                }
                break;
                case svDoubleRef:
                {
                    const ScSingleRefData& rRef = *pToken->GetSingleRef();
                    OUString aTabName;
                    if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0)
                        aTabName = maRefCache.getTableName(nFileId, nCacheId);
                    ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString(aTabName),   // string not interned
                        *pToken->GetDoubleRef());
                    pNewArray->AddToken(aNewToken);
                    bTokenAdded = true;
                }
                break;
                default:
                    ;   // nothing
            }
 
            if (!bTokenAdded)
                pNewArray->AddToken(*pToken);
        }
    }
    else
        pNewArray = rArray.Clone();
 
    maRefCache.setRangeNameTokens(nFileId, rName, pNewArray);
}
 
namespace {
 
/**
 * Put a single cell data into internal cache table.
 *
 * @param pFmt optional cell format index that may need to be stored with
 *             the cell value.
 */
void putCellDataIntoCache(
    ScExternalRefCache& rRefCache, const ScExternalRefCache::TokenRef& pToken,
    sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
    const ScExternalRefCache::CellFormat* pFmt)
{
    // Now, insert the token into cache table but don't cache empty cells.
    if (pToken->GetType() != formula::svEmptyCell)
    {
        sal_uLong nFmtIndex = (pFmt && pFmt->mbIsSet) ? pFmt->mnIndex : 0;
        rRefCache.setCellData(nFileId, rTabName, rCell.Col(), rCell.Row(), pToken, nFmtIndex);
    }
}
 
/**
 * Put the data into our internal cache table.
 *
 * @param rRefCache cache table set.
 * @param pArray single range data to be returned.
 * @param nFileId external file ID
 * @param rTabName name of the table where the data should be cached.
 * @param rCacheData range data to be cached.
 * @param rCacheRange original cache range, including the empty region if
 *                    any.
 * @param rDataRange reduced cache range that includes only the non-empty
 *                   data area.
 */
void putRangeDataIntoCache(
    ScExternalRefCache& rRefCache, ScExternalRefCache::TokenArrayRef& pArray,
    sal_uInt16 nFileId, const OUString& rTabName,
    const vector<ScExternalRefCache::SingleRangeData>& rCacheData,
    const ScRange& rCacheRange, const ScRange& rDataRange)
{
    if (pArray)
        // Cache these values.
        rRefCache.setCellRangeData(nFileId, rDataRange, rCacheData, pArray);
    else
    {
        // Array is empty.  Fill it with an empty matrix of the required size.
        pArray = lcl_fillEmptyMatrix(rRefCache.getDoc(), rCacheRange);
 
        // Make sure to set this range 'cached', to prevent unnecessarily
        // accessing the src document time and time again.
        ScExternalRefCache::TableTypeRef pCacheTab =
            rRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
        if (pCacheTab)
            pCacheTab->setCachedCellRange(
                rCacheRange.aStart.Col(), rCacheRange.aStart.Row(), rCacheRange.aEnd.Col(), rCacheRange.aEnd.Row());
    }
}
 
/**
 * When accessing an external document for the first time, we need to
 * populate the cache with all its sheet names (whether they are referenced
 * or not) in the correct order.  Many client codes that use external
 * references make this assumption.
 *
 * @param rRefCache cache table set.
 * @param pSrcDoc source document instance.
 * @param nFileId external file ID associated with the source document.
 */
void initDocInCache(ScExternalRefCache& rRefCache, const ScDocument* pSrcDoc, sal_uInt16 nFileId)
{
    if (!pSrcDoc)
        return;
 
    if (rRefCache.isDocInitialized(nFileId))
        // Already initialized.  No need to do this twice.
        return;
 
    SCTAB nTabCount = pSrcDoc->GetTableCount();
    if (!nTabCount)
        return;
 
    // Populate the cache with all table names in the source document.
    vector<OUString> aTabNames;
    aTabNames.reserve(nTabCount);
    for (SCTAB i = 0; i < nTabCount; ++i)
    {
        OUString aName;
        pSrcDoc->GetName(i, aName);
        aTabNames.push_back(aName);
    }
 
    // Obtain the base name, don't bother if there are more than one sheets.
    OUString aBaseName;
    if (nTabCount == 1)
    {
        const ScDocShell* pShell = pSrcDoc->GetDocumentShell();
        if (pShell && pShell->GetMedium())
        {
            OUString aName = pShell->GetMedium()->GetName();
            aBaseName = INetURLObject( aName).GetBase();
        }
    }
 
    rRefCache.initializeDoc(nFileId, aTabNames, aBaseName);
}
 
}
 
bool ScExternalRefManager::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
        sal_uInt16 nFileId ) const
{
    return maRefCache.getSrcDocTable( rSrcDoc, rTabName, rTab, nFileId);
}
 
ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefToken(
    sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
    const ScAddress* pCurPos, SCTAB* pTab, ScExternalRefCache::CellFormat* pFmt)
{
    if (pCurPos)
        insertRefCell(nFileId, *pCurPos);
 
    maybeLinkExternalFile(nFileId);
 
    if (pTab)
        *pTab = -1;
 
    if (pFmt)
        pFmt->mbIsSet = false;
 
    ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
    if (pSrcDoc)
    {
        // source document already loaded in memory.  Re-use this instance.
        SCTAB nTab;
        if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
        {
            // specified table name doesn't exist in the source document.
            ScExternalRefCache::TokenRef pToken(new FormulaErrorToken(FormulaError::NoRef));
            return pToken;
        }
 
        if (pTab)
            *pTab = nTab;
 
        ScExternalRefCache::TokenRef pToken =
            getSingleRefTokenFromSrcDoc(
                nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
 
        putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
        return pToken;
    }
 
    // Check if the given table name and the cell position is cached.
    sal_uInt32 nFmtIndex = 0;
    ScExternalRefCache::TokenRef pToken = maRefCache.getCellData(
        nFileId, rTabName, rCell.Col(), rCell.Row(), &nFmtIndex);
    if (pToken)
    {
        // Cache hit !
        fillCellFormat(nFmtIndex, pFmt);
        return pToken;
    }
 
    // reference not cached.  read from the source document.
    pSrcDoc = getSrcDocument(nFileId);
    if (!pSrcDoc)
    {
        // Source document not reachable.
        if (!isLinkUpdateAllowedInDoc(mrDoc))
        {
            // Indicate with specific error.
            pToken.reset(new FormulaErrorToken(FormulaError::LinkFormulaNeedingCheck));
        }
        else
        {
            // Throw a reference error.
            pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
        }
        return pToken;
    }
 
    SCTAB nTab;
    if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
    {
        // specified table name doesn't exist in the source document.
        pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
        return pToken;
    }
 
    if (pTab)
        *pTab = nTab;
 
    SCCOL nDataCol1 = 0, nDataCol2 = pSrcDoc->MaxCol();
    SCROW nDataRow1 = 0, nDataRow2 = pSrcDoc->MaxRow();
    bool bData = pSrcDoc->ShrinkToDataArea(nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2);
    if (!bData || rCell.Col() < nDataCol1 || nDataCol2 < rCell.Col() || rCell.Row() < nDataRow1 || nDataRow2 < rCell.Row())
    {
        // requested cell is outside the data area.  Don't even bother caching
        // this data, but add it to the cached range to prevent accessing the
        // source document time and time again.
        ScExternalRefCache::TableTypeRef pCacheTab =
            maRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
        if (pCacheTab)
            pCacheTab->setCachedCell(rCell.Col(), rCell.Row());
 
        pToken.reset(new ScEmptyCellToken(false, false));
        return pToken;
    }
 
    pToken = getSingleRefTokenFromSrcDoc(
        nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
 
    putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
    return pToken;
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokens(
    sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange, const ScAddress* pCurPos)
{
    if (pCurPos)
        insertRefCell(nFileId, *pCurPos);
 
    maybeLinkExternalFile(nFileId);
 
    ScRange aDataRange(rRange);
    ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
    if (pSrcDoc)
    {
        // Document already loaded in memory.
        vector<ScExternalRefCache::SingleRangeData> aCacheData;
        ScExternalRefCache::TokenArrayRef pArray =
            getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
 
        // Put the data into cache.
        putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
        return pArray;
    }
 
    // Check if the given table name and the cell position is cached.
    ScExternalRefCache::TokenArrayRef pArray =
        maRefCache.getCellRangeData(nFileId, rTabName, rRange);
    if (pArray)
        // Cache hit !
        return pArray;
 
    pSrcDoc = getSrcDocument(nFileId);
    if (!pSrcDoc)
    {
        // Source document is not reachable.  Throw a reference error.
        pArray = std::make_shared<ScTokenArray>(maRefCache.getDoc());
        pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
        return pArray;
    }
 
    vector<ScExternalRefCache::SingleRangeData> aCacheData;
    pArray = getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
 
    // Put the data into cache.
    putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
    return pArray;
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokens(
    sal_uInt16 nFileId, const OUString& rName, const ScAddress* pCurPos)
{
    if (pCurPos)
        insertRefCell(nFileId, *pCurPos);
 
    maybeLinkExternalFile(nFileId);
 
    OUString aName = rName; // make a copy to have the casing corrected.
    ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
    if (pSrcDoc)
    {
        // Document already loaded in memory.
        ScExternalRefCache::TokenArrayRef pArray =
            getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
 
        if (pArray)
            // Cache this range name array.
            maRefCache.setRangeNameTokens(nFileId, aName, pArray);
 
        return pArray;
    }
 
    ScExternalRefCache::TokenArrayRef pArray = maRefCache.getRangeNameTokens(nFileId, rName);
    if (pArray)
        // This range name is cached.
        return pArray;
 
    pSrcDoc = getSrcDocument(nFileId);
    if (!pSrcDoc)
        // failed to load document from disk.
        return ScExternalRefCache::TokenArrayRef();
 
    pArray = getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
 
    if (pArray)
        // Cache this range name array.
        maRefCache.setRangeNameTokens(nFileId, aName, pArray);
 
    return pArray;
}
 
namespace {
 
bool hasRangeName(const ScDocument& rDoc, const OUString& rName)
{
    ScRangeName* pExtNames = rDoc.GetRangeName();
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
    return pRangeData != nullptr;
}
 
}
 
bool ScExternalRefManager::isValidRangeName(sal_uInt16 nFileId, const OUString& rName)
{
    maybeLinkExternalFile(nFileId);
    ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
    if (pSrcDoc)
    {
        // Only check the presence of the name.
        if (hasRangeName(*pSrcDoc, rName))
        {
            maRefCache.setRangeName(nFileId, rName);
            return true;
        }
        return false;
    }
 
    if (maRefCache.isValidRangeName(nFileId, rName))
        // Range name is cached.
        return true;
 
    pSrcDoc = getSrcDocument(nFileId);
    if (!pSrcDoc)
        // failed to load document from disk.
        return false;
 
    if (hasRangeName(*pSrcDoc, rName))
    {
        maRefCache.setRangeName(nFileId, rName);
        return true;
    }
 
    return false;
}
 
void ScExternalRefManager::refreshAllRefCells(sal_uInt16 nFileId)
{
    RefCellMap::iterator itrFile = maRefCells.find(nFileId);
    if (itrFile == maRefCells.end())
        return;
 
    RefCellSet& rRefCells = itrFile->second;
    for_each(rRefCells.begin(), rRefCells.end(), UpdateFormulaCell());
 
    ScViewData* pViewData = ScDocShell::GetViewData();
    if (!pViewData)
        return;
 
    ScTabViewShell* pVShell = pViewData->GetViewShell();
    if (!pVShell)
        return;
 
    // Repainting the grid also repaints the texts, but is there a better way
    // to refresh texts?
    pVShell->Invalidate(FID_REPAINT);
    pVShell->PaintGrid();
}
 
namespace {
 
void insertRefCellByIterator(
    const ScExternalRefManager::RefCellMap::iterator& itr, ScFormulaCell* pCell)
{
    if (pCell)
    {
        itr->second.insert(pCell);
        pCell->SetIsExtRef();
    }
}
 
}
 
void ScExternalRefManager::insertRefCell(sal_uInt16 nFileId, const ScAddress& rCell)
{
    RefCellMap::iterator itr = maRefCells.find(nFileId);
    if (itr == maRefCells.end())
    {
        RefCellSet aRefCells;
        pair<RefCellMap::iterator, bool> r = maRefCells.emplace(
            nFileId, aRefCells);
        if (!r.second)
            // insertion failed.
            return;
 
        itr = r.first;
    }
 
    insertRefCellByIterator(itr, mrDoc.GetFormulaCell(rCell));
}
 
void ScExternalRefManager::insertRefCellFromTemplate( ScFormulaCell* pTemplateCell, ScFormulaCell* pCell )
{
    if (!pTemplateCell || !pCell)
        return;
 
    for (RefCellMap::iterator itr = maRefCells.begin(); itr != maRefCells.end(); ++itr)
    {
        if (itr->second.find(pTemplateCell) != itr->second.end())
            insertRefCellByIterator(itr, pCell);
    }
}
 
bool ScExternalRefManager::hasCellExternalReference(const ScAddress& rCell)
{
    ScFormulaCell* pCell = mrDoc.GetFormulaCell(rCell);
 
    if (pCell)
        return std::any_of(maRefCells.begin(), maRefCells.end(),
            [&pCell](const RefCellMap::value_type& rEntry) { return rEntry.second.find(pCell) != rEntry.second.end(); });
 
    return false;
}
 
void ScExternalRefManager::enableDocTimer( bool bEnable )
{
    if (mbDocTimerEnabled == bEnable)
        return;
 
    mbDocTimerEnabled = bEnable;
    if (mbDocTimerEnabled)
    {
        if (!maDocShells.empty())
        {
            for (auto& rEntry : maDocShells)
                rEntry.second.maLastAccess = tools::Time(tools::Time::SYSTEM);
 
            maSrcDocTimer.Start();
        }
    }
    else
        maSrcDocTimer.Stop();
}
 
void ScExternalRefManager::fillCellFormat(sal_uLong nFmtIndex, ScExternalRefCache::CellFormat* pFmt) const
{
    if (!pFmt)
        return;
 
    SvNumFormatType nFmtType = mrDoc.GetFormatTable()->GetType(nFmtIndex);
    if (nFmtType != SvNumFormatType::UNDEFINED)
    {
        pFmt->mbIsSet = true;
        pFmt->mnIndex = nFmtIndex;
        pFmt->mnType = nFmtType;
    }
}
 
ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefTokenFromSrcDoc(
    sal_uInt16 nFileId, ScDocument& rSrcDoc, const ScAddress& rPos,
    ScExternalRefCache::CellFormat* pFmt)
{
    // Get the cell from src doc, and convert it into a token.
    ScRefCellValue aCell(rSrcDoc, rPos);
    ScExternalRefCache::TokenRef pToken(convertToToken(mrDoc, rSrcDoc, aCell));
 
    if (!pToken)
    {
        // Generate an error for unresolvable cells.
        pToken.reset( new FormulaErrorToken( FormulaError::NoValue));
    }
 
    // Get number format information.
    sal_uInt32 nFmtIndex = rSrcDoc.GetNumberFormat(rPos.Col(), rPos.Row(), rPos.Tab());
    nFmtIndex = getMappedNumberFormat(nFileId, nFmtIndex, rSrcDoc);
    fillCellFormat(nFmtIndex, pFmt);
    return pToken;
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokensFromSrcDoc(
    const ScDocument& rSrcDoc, const OUString& rTabName, ScRange& rRange,
    vector<ScExternalRefCache::SingleRangeData>& rCacheData)
{
    ScExternalRefCache::TokenArrayRef pArray;
    SCTAB nTab1;
 
    if (!rSrcDoc.GetTable(rTabName, nTab1))
    {
        // specified table name doesn't exist in the source document.
        pArray = std::make_shared<ScTokenArray>(rSrcDoc);
        pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
        return pArray;
    }
 
    ScRange aRange(rRange);
    aRange.PutInOrder();
    SCTAB nTabSpan = aRange.aEnd.Tab() - aRange.aStart.Tab();
 
    vector<ScExternalRefCache::SingleRangeData> aCacheData;
    aCacheData.reserve(nTabSpan+1);
    aCacheData.emplace_back();
    aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(rTabName);
 
    for (SCTAB i = 1; i < nTabSpan + 1; ++i)
    {
        OUString aTabName;
        if (!rSrcDoc.GetName(nTab1 + 1, aTabName))
            // source document doesn't have any table by the specified name.
            break;
 
        aCacheData.emplace_back();
        aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(aTabName);
    }
 
    aRange.aStart.SetTab(nTab1);
    aRange.aEnd.SetTab(nTab1 + nTabSpan);
 
    pArray = convertToTokenArray(mrDoc, rSrcDoc, aRange, aCacheData);
    rRange = aRange;
    rCacheData.swap(aCacheData);
    return pArray;
}
 
ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokensFromSrcDoc(
    sal_uInt16 nFileId, const ScDocument& rSrcDoc, OUString& rName)
{
    ScRangeName* pExtNames = rSrcDoc.GetRangeName();
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
    if (!pRangeData)
        return ScExternalRefCache::TokenArrayRef();
 
    // Parse all tokens in this external range data, and replace each absolute
    // reference token with an external reference token, and cache them.  Also
    // register the source document with the link manager if it's a new
    // source.
 
    ScExternalRefCache::TokenArrayRef pNew = std::make_shared<ScTokenArray>(rSrcDoc);
 
    ScTokenArray aCode(*pRangeData->GetCode());
    FormulaTokenArrayPlainIterator aIter(aCode);
    for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next())
    {
        bool bTokenAdded = false;
        switch (pToken->GetType())
        {
            case svSingleRef:
            {
                const ScSingleRefData& rRef = *pToken->GetSingleRef();
                OUString aTabName;
                rSrcDoc.GetName(rRef.Tab(), aTabName);
                ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString( aTabName),   // string not interned
                        *pToken->GetSingleRef());
                pNew->AddToken(aNewToken);
                bTokenAdded = true;
            }
            break;
            case svDoubleRef:
            {
                const ScSingleRefData& rRef = *pToken->GetSingleRef();
                OUString aTabName;
                rSrcDoc.GetName(rRef.Tab(), aTabName);
                ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString( aTabName),   // string not interned
                        *pToken->GetDoubleRef());
                pNew->AddToken(aNewToken);
                bTokenAdded = true;
            }
            break;
            default:
                ;   // nothing
        }
 
        if (!bTokenAdded)
            pNew->AddToken(*pToken);
    }
 
    rName = pRangeData->GetName(); // Get the correctly-cased name.
    return pNew;
}
 
ScDocument* ScExternalRefManager::getInMemorySrcDocument(sal_uInt16 nFileId)
{
    const OUString* pFileName = getExternalFileName(nFileId);
    if (!pFileName)
        return nullptr;
 
    // Do not load document until it was allowed.
    if (!isLinkUpdateAllowedInDoc(mrDoc))
        return nullptr;
 
    ScDocument* pSrcDoc = nullptr;
    ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
    while (pShell)
    {
        SfxMedium* pMedium = pShell->GetMedium();
        if (pMedium && !pMedium->GetName().isEmpty())
        {
            // TODO: We should make the case sensitivity platform dependent.
            if (pFileName->equalsIgnoreAsciiCase(pMedium->GetName()))
            {
                // Found !
                pSrcDoc = &pShell->GetDocument();
                break;
            }
        }
        else
        {
            // handle unsaved documents here
            OUString aName = pShell->GetName();
            if (pFileName->equalsIgnoreAsciiCase(aName))
            {
                // Found !
                SrcShell aSrcDoc;
                aSrcDoc.maShell = pShell;
                maUnsavedDocShells.emplace(nFileId, aSrcDoc);
                StartListening(*pShell);
                pSrcDoc = &pShell->GetDocument();
                break;
            }
        }
        pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
    }
 
    initDocInCache(maRefCache, pSrcDoc, nFileId);
    return pSrcDoc;
}
 
ScDocument* ScExternalRefManager::getSrcDocument(sal_uInt16 nFileId)
{
    if (!mrDoc.IsExecuteLinkEnabled())
        return nullptr;
 
    DocShellMap::iterator itrEnd = maDocShells.end();
    DocShellMap::iterator itr = maDocShells.find(nFileId);
 
    if (itr != itrEnd)
    {
        // document already loaded.
 
        SfxObjectShell* p = itr->second.maShell.get();
        itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
        return &static_cast<ScDocShell*>(p)->GetDocument();
    }
 
    itrEnd = maUnsavedDocShells.end();
    itr = maUnsavedDocShells.find(nFileId);
    if (itr != itrEnd)
    {
        //document is unsaved document
 
        SfxObjectShell* p = itr->second.maShell.get();
        itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
        return &static_cast<ScDocShell*>(p)->GetDocument();
    }
 
    const OUString* pFile = getExternalFileName(nFileId);
    if (!pFile)
        // no file name associated with this ID.
        return nullptr;
 
    SrcShell aSrcDoc;
    try
    {
        OUString aFilter;
        aSrcDoc.maShell = loadSrcDocument(nFileId, aFilter);
    }
    catch (const css::uno::Exception&)
    {
    }
    if (!aSrcDoc.maShell.is())
    {
        // source document could not be loaded.
        return nullptr;
    }
 
    return &cacheNewDocShell(nFileId, aSrcDoc);
}
 
SfxObjectShellRef ScExternalRefManager::loadSrcDocument(sal_uInt16 nFileId, OUString& rFilter)
{
    // Do not load document until it was allowed.
    if (!isLinkUpdateAllowedInDoc(mrDoc))
        return nullptr;
 
    const SrcFileData* pFileData = getExternalFileData(nFileId);
    if (!pFileData)
        return nullptr;
 
    // Always load the document by using the path created from the relative
    // path.  If the referenced document is not there, simply exit.  The
    // original file name should be used only when the relative path is not
    // given.
    OUString aFile = pFileData->maFileName;
    maybeCreateRealFileName(nFileId);
    if (!pFileData->maRealFileName.isEmpty())
        aFile = pFileData->maRealFileName;
 
    if (!isFileLoadable(aFile))
        return nullptr;
 
    INetURLObject aURLObject(aFile);
    const OUString sHost = aURLObject.GetHost();
    if (HostFilter::isForbidden(sHost))
    {
        SAL_WARN( "sc.ui", "ScExternalRefManager::loadSrcDocument: blocked access to external file: \"" << aFile << "\"");
        return nullptr;
    }
 
    OUString aOptions = pFileData->maFilterOptions;
    if ( !pFileData->maFilterName.isEmpty() )
        rFilter = pFileData->maFilterName;      // don't overwrite stored filter with guessed filter
    else
        ScDocumentLoader::GetFilterName(aFile, rFilter, aOptions, true, false);
    std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName(rFilter);
 
    if (pFileData->maRelativeName.isEmpty())
    {
        // Generate a relative file path.
        INetURLObject aBaseURL(getOwnDocumentName());
        aBaseURL.insertName(u"content.xml");
 
        OUString aStr = URIHelper::simpleNormalizedMakeRelative(
            aBaseURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), aFile);
 
        setRelativeFileName(nFileId, aStr);
    }
 
    std::unique_ptr<SfxItemSet> pSet(new SfxAllItemSet(SfxGetpApp()->GetPool()));
    if (!aOptions.isEmpty())
        pSet->Put(SfxStringItem(SID_FILE_FILTEROPTIONS, aOptions));
 
    // make medium hidden to prevent assertion from progress bar
    pSet->Put( SfxBoolItem(SID_HIDDEN, true) );
 
    // If the current document is allowed to execute macros then the referenced
    // document may execute macros according to the security configuration.
    // Similar for UpdateDocMode to update links, just that if we reach here
    // the user already allowed updates and intermediate documents are expected
    // to update as well. When loading the document ScDocShell::Load() will
    // check through ScDocShell::GetLinkUpdateModeState() if its location is
    // trusted.
    ScDocShell* pShell = mrDoc.GetDocumentShell();
    if (pShell)
    {
        SfxMedium* pMedium = pShell->GetMedium();
        if (pMedium)
        {
            const SfxUInt16Item* pItem = pMedium->GetItemSet().GetItemIfSet( SID_MACROEXECMODE, false );
            if (pItem &&
                    pItem->GetValue() != css::document::MacroExecMode::NEVER_EXECUTE)
                pSet->Put( SfxUInt16Item( SID_MACROEXECMODE, css::document::MacroExecMode::USE_CONFIG));
        }
 
        pSet->Put( SfxUInt16Item( SID_UPDATEDOCMODE, css::document::UpdateDocMode::FULL_UPDATE));
    }
 
    unique_ptr<SfxMedium> pMedium(new SfxMedium(aFile, StreamMode::STD_READ,
                                                std::move(pFilter), std::move(pSet)));
    if (pMedium->GetErrorIgnoreWarning() != ERRCODE_NONE)
        return nullptr;
 
    // To load encrypted documents with password, user interaction needs to be enabled.
    pMedium->UseInteractionHandler(mbUserInteractionEnabled);
 
    rtl::Reference<ScDocShell> pNewShell = new ScDocShell(SfxModelFlags::EXTERNAL_LINK);
 
    // increment the recursive link count of the source document.
    ScExtDocOptions* pExtOpt = mrDoc.GetExtDocOptions();
    sal_uInt32 nLinkCount = pExtOpt ? pExtOpt->GetDocSettings().mnLinkCnt : 0;
    ScDocument& rSrcDoc = pNewShell->GetDocument();
    rSrcDoc.EnableExecuteLink(false); // to prevent circular access of external references.
    rSrcDoc.EnableUndo(false);
    rSrcDoc.LockAdjustHeight();
    rSrcDoc.EnableUserInteraction(false);
 
    ScExtDocOptions* pExtOptNew = rSrcDoc.GetExtDocOptions();
    if (!pExtOptNew)
    {
        rSrcDoc.SetExtDocOptions(std::make_unique<ScExtDocOptions>());
        pExtOptNew = rSrcDoc.GetExtDocOptions();
    }
    pExtOptNew->GetDocSettings().mnLinkCnt = nLinkCount + 1;
 
    if (!pNewShell->DoLoad(pMedium.release()))
    {
        pNewShell->DoClose();
        pNewShell.clear();
        return pNewShell;
    }
 
    // with UseInteractionHandler, options may be set by dialog during DoLoad
    OUString aNew = ScDocumentLoader::GetOptions(*pNewShell->GetMedium());
    if (!aNew.isEmpty() && aNew != aOptions)
        aOptions = aNew;
    setFilterData(nFileId, rFilter, aOptions);    // update the filter data, including the new options
 
    return pNewShell;
}
 
ScDocument& ScExternalRefManager::cacheNewDocShell( sal_uInt16 nFileId, SrcShell& rSrcShell )
{
    if (mbDocTimerEnabled && maDocShells.empty())
        // If this is the first source document insertion, start up the timer.
        maSrcDocTimer.Start();
 
    maDocShells.emplace(nFileId, rSrcShell);
    SfxObjectShell& rShell = *rSrcShell.maShell;
    ScDocument& rSrcDoc = static_cast<ScDocShell&>(rShell).GetDocument();
    initDocInCache(maRefCache, &rSrcDoc, nFileId);
    return rSrcDoc;
}
 
bool ScExternalRefManager::isFileLoadable(const OUString& rFile) const
{
    if (rFile.isEmpty())
        return false;
 
    if (isOwnDocument(rFile))
        return false;
    OUString aPhysical;
    if (osl::FileBase::getSystemPathFromFileURL(rFile, aPhysical)
        == osl::FileBase::E_None)
    {
        // #i114504# try IsFolder/Exists only for file URLs
 
        if (utl::UCBContentHelper::IsFolder(rFile))
            return false;
 
        return utl::UCBContentHelper::Exists(rFile);
    }
    else
        return true;    // for http and others, Exists doesn't work, but the URL can still be opened
}
 
void ScExternalRefManager::maybeLinkExternalFile( sal_uInt16 nFileId, bool bDeferFilterDetection )
{
    if (maLinkedDocs.count(nFileId))
        // file already linked, or the link has been broken.
        return;
 
    // Source document not linked yet.  Link it now.
    const OUString* pFileName = getExternalFileName(nFileId);
    if (!pFileName)
        return;
 
    OUString aFilter, aOptions;
    const SrcFileData* pFileData = getExternalFileData(nFileId);
    if (pFileData)
    {
        aFilter = pFileData->maFilterName;
        aOptions = pFileData->maFilterOptions;
    }
 
    // Filter detection may access external links; defer it until we are allowed.
    if (!bDeferFilterDetection)
        bDeferFilterDetection = !isLinkUpdateAllowedInDoc(mrDoc);
 
    // If a filter was already set (for example, loading the cached table),
    // don't call GetFilterName which has to access the source file.
    // If filter detection is deferred, the next successful loadSrcDocument()
    // will update SrcFileData filter name.
    if (aFilter.isEmpty() && !bDeferFilterDetection)
        ScDocumentLoader::GetFilterName(*pFileName, aFilter, aOptions, true, false);
    sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager();
    if (!pLinkMgr)
    {
        SAL_WARN( "sc.ui", "ScExternalRefManager::maybeLinkExternalFile: pLinkMgr==NULL");
        return;
    }
    ScExternalRefLink* pLink = new ScExternalRefLink(mrDoc, nFileId);
    OSL_ENSURE(pFileName, "ScExternalRefManager::maybeLinkExternalFile: file name pointer is NULL");
    pLinkMgr->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, *pFileName,
            (aFilter.isEmpty() && bDeferFilterDetection ? nullptr : &aFilter));
 
    pLink->SetDoRefresh(false);
    pLink->Update();
    pLink->SetDoRefresh(true);
 
    maLinkedDocs.emplace(nFileId, true);
}
 
void ScExternalRefManager::addFilesToLinkManager()
{
    if (maSrcFiles.empty())
        return;
 
    SAL_WARN_IF( maSrcFiles.size() >= SAL_MAX_UINT16,
            "sc.ui", "ScExternalRefManager::addFilesToLinkManager: files overflow");
    const sal_uInt16 nSize = static_cast<sal_uInt16>( std::min<size_t>( maSrcFiles.size(), SAL_MAX_UINT16));
    for (sal_uInt16 nFileId = 0; nFileId < nSize; ++nFileId)
        maybeLinkExternalFile( nFileId, true);
}
 
void ScExternalRefManager::SrcFileData::maybeCreateRealFileName(std::u16string_view rOwnDocName)
{
    if (maRelativeName.isEmpty())
        // No relative path given.  Nothing to do.
        return;
 
    if (!maRealFileName.isEmpty())
        // Real file name already created.  Nothing to do.
        return;
 
    // Formulate the absolute file path from the relative path.
    const OUString& rRelPath = maRelativeName;
    INetURLObject aBaseURL(rOwnDocName);
    aBaseURL.insertName(u"content.xml");
    bool bWasAbs = false;
    maRealFileName = aBaseURL.smartRel2Abs(rRelPath, bWasAbs).GetMainURL(INetURLObject::DecodeMechanism::NONE);
}
 
void ScExternalRefManager::maybeCreateRealFileName(sal_uInt16 nFileId)
{
    if (nFileId >= maSrcFiles.size())
        return;
 
    maSrcFiles[nFileId].maybeCreateRealFileName(getOwnDocumentName());
}
 
OUString ScExternalRefManager::getOwnDocumentName() const
{
    if (comphelper::IsFuzzing())
        return u"file:///tmp/document"_ustr;
 
    ScDocShell* pShell = mrDoc.GetDocumentShell();
    if (!pShell)
        // This should not happen!
        return OUString();
 
    SfxMedium* pMed = pShell->GetMedium();
    if (!pMed)
        return OUString();
 
    return pMed->GetName();
}
 
bool ScExternalRefManager::isOwnDocument(std::u16string_view rFile) const
{
    return getOwnDocumentName() == rFile;
}
 
void ScExternalRefManager::convertToAbsName(OUString& rFile) const
{
    // unsaved documents have no AbsName
    ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
    while (pShell)
    {
        if (rFile == pShell->GetName())
            return;
 
        pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
    }
 
    ScDocShell* pDocShell = mrDoc.GetDocumentShell();
    rFile = ScGlobal::GetAbsDocName(rFile, pDocShell);
}
 
sal_uInt16 ScExternalRefManager::getExternalFileId(const OUString& rFile)
{
    vector<SrcFileData>::const_iterator itrBeg = maSrcFiles.begin(), itrEnd = maSrcFiles.end();
    vector<SrcFileData>::const_iterator itr = find_if(itrBeg, itrEnd, FindSrcFileByName(rFile));
    if (itr != itrEnd)
    {
        size_t nId = distance(itrBeg, itr);
        return static_cast<sal_uInt16>(nId);
    }
 
    SrcFileData aData;
    aData.maFileName = rFile;
    maSrcFiles.push_back(aData);
    return static_cast<sal_uInt16>(maSrcFiles.size() - 1);
}
 
const OUString* ScExternalRefManager::getExternalFileName(sal_uInt16 nFileId, bool bForceOriginal)
{
    if (nFileId >= maSrcFiles.size())
        return nullptr;
 
    if (bForceOriginal)
        return &maSrcFiles[nFileId].maFileName;
 
    maybeCreateRealFileName(nFileId);
 
    if (!maSrcFiles[nFileId].maRealFileName.isEmpty())
        return &maSrcFiles[nFileId].maRealFileName;
 
    return &maSrcFiles[nFileId].maFileName;
}
 
sal_uInt16 ScExternalRefManager::convertFileIdToUsedFileId(sal_uInt16 nFileId)
{
    if (!mbSkipUnusedFileIds)
        return nFileId;
    else
        return maConvertFileIdToUsedFileId[nFileId];
}
 
void ScExternalRefManager::setSkipUnusedFileIds(std::vector<sal_uInt16>& rExternFileIds)
{
    mbSkipUnusedFileIds = true;
    maConvertFileIdToUsedFileId.resize(maSrcFiles.size());
    std::fill(maConvertFileIdToUsedFileId.begin(), maConvertFileIdToUsedFileId.end(), 0);
    int nUsedCount = 0;
    for (auto nEntry : rExternFileIds)
    {
        maConvertFileIdToUsedFileId[nEntry] = nUsedCount++;
    }
}
 
void ScExternalRefManager::disableSkipUnusedFileIds()
{
    mbSkipUnusedFileIds = false;
}
 
std::vector<OUString> ScExternalRefManager::getAllCachedExternalFileNames() const
{
    std::vector<OUString> aNames;
    aNames.reserve(maSrcFiles.size());
    for (const SrcFileData& rData : maSrcFiles)
    {
        aNames.push_back(rData.maFileName);
    }
 
    return aNames;
}
 
bool ScExternalRefManager::hasExternalFile(sal_uInt16 nFileId) const
{
    return nFileId < maSrcFiles.size();
}
 
bool ScExternalRefManager::hasExternalFile(const OUString& rFile) const
{
    return ::std::any_of(maSrcFiles.begin(), maSrcFiles.end(), FindSrcFileByName(rFile));
}
 
const ScExternalRefManager::SrcFileData* ScExternalRefManager::getExternalFileData(sal_uInt16 nFileId) const
{
    if (nFileId >= maSrcFiles.size())
        return nullptr;
 
    return &maSrcFiles[nFileId];
}
 
const OUString* ScExternalRefManager::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
{
    return maRefCache.getRealTableName(nFileId, rTabName);
}
 
const OUString* ScExternalRefManager::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
{
    return maRefCache.getRealRangeName(nFileId, rRangeName);
}
 
template<typename MapContainer>
static void lcl_removeByFileId(sal_uInt16 nFileId, MapContainer& rMap)
{
    typename MapContainer::iterator itr = rMap.find(nFileId);
    if (itr != rMap.end())
    {
        // Close this document shell.
        itr->second.maShell->DoClose();
        rMap.erase(itr);
    }
}
 
void ScExternalRefManager::clearCache(sal_uInt16 nFileId)
{
    maRefCache.clearCache(nFileId);
}
 
namespace {
 
class RefCacheFiller : public sc::ColumnSpanSet::ColumnAction
{
    svl::SharedStringPool& mrStrPool;
 
    ScExternalRefCache& mrRefCache;
    ScExternalRefCache::TableTypeRef mpRefTab;
    sal_uInt16 mnFileId;
    ScColumn* mpCurCol;
    sc::ColumnBlockConstPosition maBlockPos;
 
public:
    RefCacheFiller( svl::SharedStringPool& rStrPool, ScExternalRefCache& rRefCache, sal_uInt16 nFileId ) :
        mrStrPool(rStrPool), mrRefCache(rRefCache), mnFileId(nFileId), mpCurCol(nullptr) {}
 
    virtual void startColumn( ScColumn* pCol ) override
    {
        mpCurCol = pCol;
        if (!mpCurCol)
            return;
 
        mpCurCol->InitBlockPosition(maBlockPos);
        mpRefTab = mrRefCache.getCacheTable(mnFileId, mpCurCol->GetTab());
    }
 
    virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
    {
        if (!mpCurCol || !bVal)
            return;
 
        if (!mpRefTab)
            return;
 
        for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
        {
            ScExternalRefCache::TokenRef pTok;
            ScRefCellValue aCell = mpCurCol->GetCellValue(maBlockPos, nRow);
            switch (aCell.getType())
            {
                case CELLTYPE_STRING:
                case CELLTYPE_EDIT:
                {
                    OUString aStr = aCell.getString(&mpCurCol->GetDoc());
                    svl::SharedString aSS = mrStrPool.intern(aStr);
                    pTok.reset(new formula::FormulaStringToken(std::move(aSS)));
                }
                break;
                case CELLTYPE_VALUE:
                    pTok.reset(new formula::FormulaDoubleToken(aCell.getDouble()));
                break;
                case CELLTYPE_FORMULA:
                {
                    sc::FormulaResultValue aRes = aCell.getFormula()->GetResult();
                    switch (aRes.meType)
                    {
                        case sc::FormulaResultValue::Value:
                            pTok.reset(new formula::FormulaDoubleToken(aRes.mfValue));
                        break;
                        case sc::FormulaResultValue::String:
                        {
                            // Re-intern the string to the host document pool.
                            svl::SharedString aInterned = mrStrPool.intern(aRes.maString.getString());
                            pTok.reset(new formula::FormulaStringToken(std::move(aInterned)));
                        }
                        break;
                        case sc::FormulaResultValue::Error:
                        case sc::FormulaResultValue::Invalid:
                        default:
                            pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
                    }
                }
                break;
                default:
                    pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
            }
 
            if (pTok)
            {
                // Cache this cell.
                mpRefTab->setCell(mpCurCol->GetCol(), nRow, pTok, mpCurCol->GetNumberFormat(mpCurCol->GetDoc().GetNonThreadedContext(), nRow));
                mpRefTab->setCachedCell(mpCurCol->GetCol(), nRow);
            }
        }
    };
};
 
}
 
bool ScExternalRefManager::refreshSrcDocument(sal_uInt16 nFileId)
{
    SfxObjectShellRef xDocShell;
    try
    {
        OUString aFilter;
        xDocShell = loadSrcDocument(nFileId, aFilter);
    }
    catch ( const css::uno::Exception& ) {}
 
    if (!xDocShell.is())
        // Failed to load the document.  Bail out.
        return false;
 
    ScDocShell& rDocSh = static_cast<ScDocShell&>(*xDocShell);
    ScDocument& rSrcDoc = rDocSh.GetDocument();
 
    sc::ColumnSpanSet aCachedArea;
    maRefCache.getAllCachedDataSpans(rSrcDoc, nFileId, aCachedArea);
 
    // Clear the existing cache, and refill it.  Make sure we keep the
    // existing cache table instances here.
    maRefCache.clearCacheTables(nFileId);
    RefCacheFiller aAction(mrDoc.GetSharedStringPool(), maRefCache, nFileId);
    aCachedArea.executeColumnAction(rSrcDoc, aAction);
 
    DocShellMap::iterator it = maDocShells.find(nFileId);
    if (it != maDocShells.end())
    {
        it->second.maShell->DoClose();
        it->second.maShell = std::move(xDocShell);
        it->second.maLastAccess = tools::Time(tools::Time::SYSTEM);
    }
    else
    {
        SrcShell aSrcDoc;
        aSrcDoc.maShell = std::move(xDocShell);
        aSrcDoc.maLastAccess = tools::Time(tools::Time::SYSTEM);
        cacheNewDocShell(nFileId, aSrcDoc);
    }
 
    // Update all cells containing names from this source document.
    refreshAllRefCells(nFileId);
 
    notifyAllLinkListeners(nFileId, LINK_MODIFIED);
 
    return true;
}
 
void ScExternalRefManager::breakLink(sal_uInt16 nFileId)
{
    // Turn all formula cells referencing this external document into static
    // cells.
    RefCellMap::iterator itrRefs = maRefCells.find(nFileId);
    if (itrRefs != maRefCells.end())
    {
        // Make a copy because removing the formula cells below will modify
        // the original container.
        RefCellSet aSet = itrRefs->second;
        for_each(aSet.begin(), aSet.end(), ConvertFormulaToStatic(&mrDoc));
        maRefCells.erase(nFileId);
    }
 
    // Remove all named ranges that reference this document.
 
    // Global named ranges.
    ScRangeName* pRanges = mrDoc.GetRangeName();
    if (pRanges)
        removeRangeNamesBySrcDoc(*pRanges, nFileId);
 
    // Sheet-local named ranges.
    for (SCTAB i = 0, n = mrDoc.GetTableCount(); i < n; ++i)
    {
        pRanges = mrDoc.GetRangeName(i);
        if (pRanges)
            removeRangeNamesBySrcDoc(*pRanges, nFileId);
    }
 
    clearCache(nFileId);
    lcl_removeByFileId(nFileId, maDocShells);
 
    if (maDocShells.empty())
        maSrcDocTimer.Stop();
 
    LinkedDocMap::iterator itr = maLinkedDocs.find(nFileId);
    if (itr != maLinkedDocs.end())
        itr->second = false;
 
    notifyAllLinkListeners(nFileId, LINK_BROKEN);
}
 
void ScExternalRefManager::switchSrcFile(sal_uInt16 nFileId, const OUString& rNewFile, const OUString& rNewFilter)
{
    maSrcFiles[nFileId].maFileName = rNewFile;
    maSrcFiles[nFileId].maRelativeName.clear();
    maSrcFiles[nFileId].maRealFileName.clear();
    if (maSrcFiles[nFileId].maFilterName != rNewFilter)
    {
        // Filter type has changed.
        maSrcFiles[nFileId].maFilterName = rNewFilter;
        maSrcFiles[nFileId].maFilterOptions.clear();
    }
    refreshSrcDocument(nFileId);
}
 
void ScExternalRefManager::setRelativeFileName(sal_uInt16 nFileId, const OUString& rRelUrl)
{
    if (nFileId >= maSrcFiles.size())
        return;
    maSrcFiles[nFileId].maRelativeName = rRelUrl;
}
 
void ScExternalRefManager::setFilterData(sal_uInt16 nFileId, const OUString& rFilterName, const OUString& rOptions)
{
    if (nFileId >= maSrcFiles.size())
        return;
    maSrcFiles[nFileId].maFilterName = rFilterName;
    maSrcFiles[nFileId].maFilterOptions = rOptions;
}
 
void ScExternalRefManager::clear()
{
    for (auto& rEntry : maLinkListeners)
    {
        for (auto& it : rEntry.second)
        {
            it->notify(0, OH_NO_WE_ARE_GOING_TO_DIE);
        }
    }
 
    for (auto& rEntry : maDocShells)
        rEntry.second.maShell->DoClose();
 
    maDocShells.clear();
    maSrcDocTimer.Stop();
}
 
bool ScExternalRefManager::hasExternalData() const
{
    return !maSrcFiles.empty();
}
 
void ScExternalRefManager::resetSrcFileData(const OUString& rBaseFileUrl)
{
    for (auto& rSrcFile : maSrcFiles)
    {
        // Re-generate relative file name from the absolute file name.
        OUString aAbsName = rSrcFile.maRealFileName;
        if (aAbsName.isEmpty())
            aAbsName = rSrcFile.maFileName;
 
        rSrcFile.maRelativeName = URIHelper::simpleNormalizedMakeRelative(
            rBaseFileUrl, aAbsName);
    }
}
 
void ScExternalRefManager::updateAbsAfterLoad()
{
    OUString aOwn( getOwnDocumentName() );
    for (auto& rSrcFile : maSrcFiles)
    {
        // update maFileName to the real file name,
        // to be called when the original name is no longer needed (after CompileXML)
 
        rSrcFile.maybeCreateRealFileName( aOwn );
        OUString aReal = rSrcFile.maRealFileName;
        if (!aReal.isEmpty())
            rSrcFile.maFileName = aReal;
    }
}
 
void ScExternalRefManager::removeRefCell(ScFormulaCell* pCell)
{
    for_each(maRefCells.begin(), maRefCells.end(), RemoveFormulaCell(pCell));
}
 
void ScExternalRefManager::addLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
{
    LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
    if (itr == maLinkListeners.end())
    {
        pair<LinkListenerMap::iterator, bool> r = maLinkListeners.emplace(
            nFileId, LinkListeners());
        if (!r.second)
        {
            OSL_FAIL("insertion of new link listener list failed");
            return;
        }
 
        itr = r.first;
    }
 
    LinkListeners& rList = itr->second;
    rList.insert(pListener);
}
 
void ScExternalRefManager::removeLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
{
    LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
    if (itr == maLinkListeners.end())
        // no listeners for a specified file.
        return;
 
    LinkListeners& rList = itr->second;
    rList.erase(pListener);
 
    if (rList.empty())
        // No more listeners for this file.  Remove its entry.
        maLinkListeners.erase(itr);
}
 
void ScExternalRefManager::removeLinkListener(LinkListener* pListener)
{
    for (auto& rEntry : maLinkListeners)
        rEntry.second.erase(pListener);
}
 
void ScExternalRefManager::notifyAllLinkListeners(sal_uInt16 nFileId, LinkUpdateType eType)
{
    LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
    if (itr == maLinkListeners.end())
        // no listeners for a specified file.
        return;
 
    LinkListeners& rList = itr->second;
    for_each(rList.begin(), rList.end(), NotifyLinkListener(nFileId, eType));
}
 
void ScExternalRefManager::purgeStaleSrcDocument(sal_Int32 nTimeOut)
{
    // To avoid potentially freezing Calc, we close one stale document at a time.
    DocShellMap::iterator itr = std::find_if(maDocShells.begin(), maDocShells.end(),
        [nTimeOut](const DocShellMap::value_type& rEntry) {
            // in 100th of a second.
            sal_Int32 nSinceLastAccess = (tools::Time( tools::Time::SYSTEM ) - rEntry.second.maLastAccess).GetTime();
            return nSinceLastAccess >= nTimeOut;
        });
    if (itr != maDocShells.end())
    {
        // Timed out.  Let's close this.
        itr->second.maShell->DoClose();
        maDocShells.erase(itr);
    }
 
    if (maDocShells.empty())
        maSrcDocTimer.Stop();
}
 
sal_uInt32 ScExternalRefManager::getMappedNumberFormat(sal_uInt16 nFileId, sal_uInt32 nNumFmt, const ScDocument& rSrcDoc)
{
    NumFmtMap::iterator itr = maNumFormatMap.find(nFileId);
    if (itr == maNumFormatMap.end())
    {
        // Number formatter map is not initialized for this external document.
        pair<NumFmtMap::iterator, bool> r = maNumFormatMap.emplace(
            nFileId, SvNumberFormatterMergeMap());
 
        if (!r.second)
            // insertion failed.
            return nNumFmt;
 
        itr = r.first;
        mrDoc.GetFormatTable()->MergeFormatter(*rSrcDoc.GetFormatTable());
        SvNumberFormatterMergeMap aMap = mrDoc.GetFormatTable()->ConvertMergeTableToMap();
        itr->second.swap(aMap);
    }
    const SvNumberFormatterMergeMap& rMap = itr->second;
    SvNumberFormatterMergeMap::const_iterator itrNumFmt = rMap.find(nNumFmt);
    if (itrNumFmt != rMap.end())
        // mapped value found.
        return itrNumFmt->second;
 
    return nNumFmt;
}
 
void ScExternalRefManager::transformUnsavedRefToSavedRef( SfxObjectShell* pShell )
{
    DocShellMap::iterator itr = maUnsavedDocShells.begin();
    while( itr != maUnsavedDocShells.end() )
    {
        if ( itr->second.maShell.get() == pShell )
        {
            // found that the shell is marked as unsaved
            OUString aFileURL = pShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
            switchSrcFile(itr->first, aFileURL, OUString());
            EndListening(*pShell);
            itr = maUnsavedDocShells.erase(itr);
        }
        else
            ++itr;
    }
}
 
void ScExternalRefManager::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    if (rHint.GetId() != SfxHintId::ThisIsAnSfxEventHint)
        return;
 
    switch (static_cast<const SfxEventHint&>(rHint).GetEventId())
    {
        case SfxEventHintId::PrepareCloseDoc:
            {
                std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
                                                           VclMessageType::Warning, VclButtonsType::Ok,
                                                           ScResId(STR_CLOSE_WITH_UNSAVED_REFS)));
                xWarn->run();
            }
            break;
        case SfxEventHintId::SaveDocDone:
        case SfxEventHintId::SaveAsDocDone:
            {
                rtl::Reference<SfxObjectShell> pObjShell = static_cast<const SfxEventHint&>( rHint ).GetObjShell();
                transformUnsavedRefToSavedRef(pObjShell.get());
            }
            break;
        default:
            break;
    }
}
 
IMPL_LINK(ScExternalRefManager, TimeOutHdl, Timer*, pTimer, void)
{
    if (pTimer == &maSrcDocTimer)
        purgeStaleSrcDocument(SRCDOC_LIFE_SPAN);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.