/* -*- 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 .
*/
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; usingnamespace formula;
#define SRCDOC_LIFE_SPAN 30000 // 5 minutes (in 100th of a sec) #define SRCDOC_SCAN_INTERVAL 1000*30 // every 30 seconds (in msec)
struct UpdateFormulaCell
{ voidoperator() (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();
}
// 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) returnfalse;
formula::FormulaTokenArrayPlainIterator aIter(*pArray);
formula::FormulaToken* p = aIter.GetNextReference(); for (; p; p = aIter.GetNextReference())
{ if (!p->IsExternalRef()) continue;
if (p->GetIndex() == nFileId) returntrue;
} returnfalse;
}
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;
::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 (constauto& 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);
}
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;
// 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();
if (!pTab->isRangeCached(nDataCol1, nDataRow1, nDataCol2, nDataRow2))
{ // specified range is not entirely within cached ranges. return TokenArrayRef();
}
// 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);
} elseif ((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 (constauto& 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 (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();
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>();
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;
}
namespace {
OUString getFirstSheetName()
{ // Get Custom prefix. const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions(); // Form sheet name identical to the first generated sheet name when // creating an internal document, e.g. 'Sheet1'. return rOpt.GetInitTabPrefix() + "1";
}
}
// 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 (constauto& 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; elseif (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, rBaseName))
pDoc->maSingleTableNameAlias = aSheetName;
}
// 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();
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();
}
}
}
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();
}
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; elseif (ScGlobal::GetTransliteration().isEqual( rTabName, aBaseName))
pDoc->maSingleTableNameAlias = aSheetName;
}
} else
{
rDoc.maSingleTableNameAlias.clear();
}
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();
}
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;
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;
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;
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));
// 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());
} elseif ((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());
}
}
void ScExternalRefManager::markUsedByLinkListeners()
{ bool bAllMarked = false; for (constauto& [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 (constauto& rEntry : maRefCells)
{ for (ScFormulaCell* pCell : rEntry.second)
{ bool bUsed = pCell->MarkUsedExternalReferences(); if (bUsed) // Return true when at least one cell references external docs. return;
}
}
}
/** * 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();
}
}
// 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());
// 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;
}
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();
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();
}
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;
}
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;
// 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.
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;
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));
}
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();
bool ScExternalRefManager::isFileLoadable(const OUString& rFile) const
{ if (rFile.isEmpty()) returnfalse;
if (isOwnDocument(rFile)) returnfalse;
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)) returnfalse;
return utl::UCBContentHelper::Exists(rFile);
} else returntrue; // 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;
// 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));
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;
// 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);
// Update all cells containing names from this source document.
refreshAllRefCells(nFileId);
notifyAllLinkListeners(nFileId, LINK_MODIFIED);
returntrue;
}
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);
}
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)
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;
}
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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.