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