nss: upgrade to release 3.73
[LibreOffice.git] / sc / source / ui / docshell / externalrefmgr.cxx
blob9bb744b07049aa840b290f151db0afd9f4e429a3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <externalrefmgr.hxx>
21 #include <document.hxx>
22 #include <token.hxx>
23 #include <tokenarray.hxx>
24 #include <address.hxx>
25 #include <tablink.hxx>
26 #include <docsh.hxx>
27 #include <scextopt.hxx>
28 #include <rangenam.hxx>
29 #include <formulacell.hxx>
30 #include <viewdata.hxx>
31 #include <tabvwsh.hxx>
32 #include <sc.hrc>
33 #include <globstr.hrc>
34 #include <scresid.hxx>
35 #include <cellvalue.hxx>
36 #include <defaultsoptions.hxx>
37 #include <scmod.hxx>
39 #include <o3tl/safeint.hxx>
40 #include <osl/file.hxx>
41 #include <sfx2/app.hxx>
42 #include <sfx2/docfile.hxx>
43 #include <sfx2/event.hxx>
44 #include <sfx2/fcontnr.hxx>
45 #include <sfx2/objsh.hxx>
46 #include <svl/itemset.hxx>
47 #include <svl/stritem.hxx>
48 #include <svl/urihelper.hxx>
49 #include <svl/sharedstringpool.hxx>
50 #include <sfx2/linkmgr.hxx>
51 #include <tools/urlobj.hxx>
52 #include <unotools/charclass.hxx>
53 #include <unotools/configmgr.hxx>
54 #include <unotools/ucbhelper.hxx>
55 #include <vcl/svapp.hxx>
56 #include <vcl/weld.hxx>
57 #include <stringutil.hxx>
58 #include <scmatrix.hxx>
59 #include <columnspanset.hxx>
60 #include <column.hxx>
61 #include <com/sun/star/document/MacroExecMode.hpp>
62 #include <com/sun/star/document/UpdateDocMode.hpp>
63 #include <sal/log.hxx>
65 #include <memory>
66 #include <algorithm>
68 using ::std::unique_ptr;
69 using ::com::sun::star::uno::Any;
70 using ::std::vector;
71 using ::std::find_if;
72 using ::std::for_each;
73 using ::std::distance;
74 using ::std::pair;
75 using namespace formula;
77 #define SRCDOC_LIFE_SPAN 30000 // 5 minutes (in 100th of a sec)
78 #define SRCDOC_SCAN_INTERVAL 1000*30 // every 30 seconds (in msec)
80 namespace {
82 class TabNameSearchPredicate
84 public:
85 explicit TabNameSearchPredicate(const OUString& rSearchName) :
86 maSearchName(ScGlobal::getCharClassPtr()->uppercase(rSearchName))
90 bool operator()(const ScExternalRefCache::TableName& rTabNameSet) const
92 // Ok, I'm doing case insensitive search here.
93 return rTabNameSet.maUpperName == maSearchName;
96 private:
97 OUString maSearchName;
100 class FindSrcFileByName
102 public:
103 explicit FindSrcFileByName(const OUString& rMatchName) :
104 mrMatchName(rMatchName)
108 bool operator()(const ScExternalRefManager::SrcFileData& rSrcData) const
110 return rSrcData.maFileName == mrMatchName;
113 private:
114 const OUString& mrMatchName;
117 class NotifyLinkListener
119 public:
120 NotifyLinkListener(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType) :
121 mnFileId(nFileId), meType(eType) {}
123 void operator() (ScExternalRefManager::LinkListener* p) const
125 p->notify(mnFileId, meType);
127 private:
128 sal_uInt16 mnFileId;
129 ScExternalRefManager::LinkUpdateType meType;
132 struct UpdateFormulaCell
134 void operator() (ScFormulaCell* pCell) const
136 // Check to make sure the cell really contains svExternal*.
137 // External names, external cell and range references all have a
138 // token of svExternal*. Additionally check for INDIRECT() that can be
139 // called with any constructed URI string.
140 ScTokenArray* pCode = pCell->GetCode();
141 if (!pCode->HasExternalRef() && !pCode->HasOpCode(ocIndirect))
142 return;
144 if (pCode->GetCodeError() != FormulaError::NONE)
146 // Clear the error code, or a cell with error won't get re-compiled.
147 pCode->SetCodeError(FormulaError::NONE);
148 pCell->SetCompile(true);
149 pCell->CompileTokenArray();
152 pCell->SetDirty();
156 class RemoveFormulaCell
158 public:
159 explicit RemoveFormulaCell(ScFormulaCell* p) : mpCell(p) {}
160 void operator() (pair<const sal_uInt16, ScExternalRefManager::RefCellSet>& r) const
162 r.second.erase(mpCell);
164 private:
165 ScFormulaCell* mpCell;
168 class ConvertFormulaToStatic
170 public:
171 explicit ConvertFormulaToStatic(ScDocument* pDoc) : mpDoc(pDoc) {}
172 void operator() (ScFormulaCell* pCell) const
174 ScAddress aPos = pCell->aPos;
176 // We don't check for empty cells because empty external cells are
177 // treated as having a value of 0.
179 if (pCell->IsValue())
181 // Turn this into value cell.
182 mpDoc->SetValue(aPos, pCell->GetValue());
184 else
186 // string cell otherwise.
187 ScSetStringParam aParam;
188 aParam.setTextInput();
189 mpDoc->SetString(aPos, pCell->GetString().getString(), &aParam);
192 private:
193 ScDocument* mpDoc;
197 * Check whether a named range contains an external reference to a
198 * particular document.
200 bool hasRefsToSrcDoc(ScRangeData& rData, sal_uInt16 nFileId)
202 ScTokenArray* pArray = rData.GetCode();
203 if (!pArray)
204 return false;
206 formula::FormulaTokenArrayPlainIterator aIter(*pArray);
207 formula::FormulaToken* p = aIter.GetNextReference();
208 for (; p; p = aIter.GetNextReference())
210 if (!p->IsExternalRef())
211 continue;
213 if (p->GetIndex() == nFileId)
214 return true;
216 return false;
219 class EraseRangeByIterator
221 ScRangeName& mrRanges;
222 public:
223 explicit EraseRangeByIterator(ScRangeName& rRanges) : mrRanges(rRanges) {}
224 void operator() (const ScRangeName::iterator& itr)
226 mrRanges.erase(itr);
231 * Remove all named ranges that contain references to specified source
232 * document.
234 void removeRangeNamesBySrcDoc(ScRangeName& rRanges, sal_uInt16 nFileId)
236 ScRangeName::iterator itr = rRanges.begin(), itrEnd = rRanges.end();
237 vector<ScRangeName::iterator> v;
238 for (; itr != itrEnd; ++itr)
240 if (hasRefsToSrcDoc(*itr->second, nFileId))
241 v.push_back(itr);
243 for_each(v.begin(), v.end(), EraseRangeByIterator(rRanges));
248 ScExternalRefCache::Table::Table()
249 : mbReferenced( true )
250 // Prevent accidental data loss due to lack of knowledge.
254 ScExternalRefCache::Table::~Table()
258 void ScExternalRefCache::Table::clear()
260 maRows.clear();
261 maCachedRanges.RemoveAll();
262 mbReferenced = true;
265 void ScExternalRefCache::Table::setReferenced( bool bReferenced )
267 mbReferenced = bReferenced;
270 bool ScExternalRefCache::Table::isReferenced() const
272 return mbReferenced;
275 void ScExternalRefCache::Table::setCell(SCCOL nCol, SCROW nRow, TokenRef const & pToken, sal_uLong nFmtIndex, bool bSetCacheRange)
277 using ::std::pair;
278 RowsDataType::iterator itrRow = maRows.find(nRow);
279 if (itrRow == maRows.end())
281 // This row does not exist yet.
282 pair<RowsDataType::iterator, bool> res = maRows.emplace(
283 nRow, RowDataType());
285 if (!res.second)
286 return;
288 itrRow = res.first;
291 // Insert this token into the specified column location. I don't need to
292 // check for existing data. Just overwrite it.
293 RowDataType& rRow = itrRow->second;
294 ScExternalRefCache::Cell aCell;
295 aCell.mxToken = pToken;
296 aCell.mnFmtIndex = nFmtIndex;
297 rRow.emplace(nCol, aCell);
298 if (bSetCacheRange)
299 setCachedCell(nCol, nRow);
302 ScExternalRefCache::TokenRef ScExternalRefCache::Table::getCell(SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex) const
304 RowsDataType::const_iterator itrTable = maRows.find(nRow);
305 if (itrTable == maRows.end())
307 // this table doesn't have the specified row.
308 return getEmptyOrNullToken(nCol, nRow);
311 const RowDataType& rRowData = itrTable->second;
312 RowDataType::const_iterator itrRow = rRowData.find(nCol);
313 if (itrRow == rRowData.end())
315 // this row doesn't have the specified column.
316 return getEmptyOrNullToken(nCol, nRow);
319 const Cell& rCell = itrRow->second;
320 if (pnFmtIndex)
321 *pnFmtIndex = rCell.mnFmtIndex;
323 return rCell.mxToken;
326 bool ScExternalRefCache::Table::hasRow( SCROW nRow ) const
328 RowsDataType::const_iterator itrRow = maRows.find(nRow);
329 return itrRow != maRows.end();
332 void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, SCROW nLow, SCROW nHigh) const
334 vector<SCROW> aRows;
335 aRows.reserve(maRows.size());
336 for (const auto& rEntry : maRows)
337 if (nLow <= rEntry.first && rEntry.first <= nHigh)
338 aRows.push_back(rEntry.first);
340 // hash map is not ordered, so we need to explicitly sort it.
341 ::std::sort(aRows.begin(), aRows.end());
342 rRows.swap(aRows);
345 ::std::pair< SCROW, SCROW > ScExternalRefCache::Table::getRowRange() const
347 ::std::pair< SCROW, SCROW > aRange( 0, 0 );
348 if( !maRows.empty() )
350 // iterate over entire container (hash map is not sorted by key)
351 auto itMinMax = std::minmax_element(maRows.begin(), maRows.end(),
352 [](const RowsDataType::value_type& a, const RowsDataType::value_type& b) { return a.first < b.first; });
353 aRange.first = itMinMax.first->first;
354 aRange.second = itMinMax.second->first + 1;
356 return aRange;
359 void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, SCCOL nLow, SCCOL nHigh) const
361 RowsDataType::const_iterator itrRow = maRows.find(nRow);
362 if (itrRow == maRows.end())
363 // this table doesn't have the specified row.
364 return;
366 const RowDataType& rRowData = itrRow->second;
367 vector<SCCOL> aCols;
368 aCols.reserve(rRowData.size());
369 for (const auto& rCol : rRowData)
370 if (nLow <= rCol.first && rCol.first <= nHigh)
371 aCols.push_back(rCol.first);
373 // hash map is not ordered, so we need to explicitly sort it.
374 ::std::sort(aCols.begin(), aCols.end());
375 rCols.swap(aCols);
378 ::std::pair< SCCOL, SCCOL > ScExternalRefCache::Table::getColRange( SCROW nRow ) const
380 ::std::pair< SCCOL, SCCOL > aRange( 0, 0 );
382 RowsDataType::const_iterator itrRow = maRows.find( nRow );
383 if (itrRow == maRows.end())
384 // this table doesn't have the specified row.
385 return aRange;
387 const RowDataType& rRowData = itrRow->second;
388 if( !rRowData.empty() )
390 // iterate over entire container (hash map is not sorted by key)
391 auto itMinMax = std::minmax_element(rRowData.begin(), rRowData.end(),
392 [](const RowDataType::value_type& a, const RowDataType::value_type& b) { return a.first < b.first; });
393 aRange.first = itMinMax.first->first;
394 aRange.second = itMinMax.second->first + 1;
396 return aRange;
399 void ScExternalRefCache::Table::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
401 for (const auto& rRow : maRows)
403 const RowDataType& rRowData = rRow.second;
404 for (const auto& rCol : rRowData)
406 const Cell& rCell = rCol.second;
407 rNumFmts.push_back(rCell.mnFmtIndex);
412 bool ScExternalRefCache::Table::isRangeCached(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const
414 return maCachedRanges.In(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
417 void ScExternalRefCache::Table::setCachedCell(SCCOL nCol, SCROW nRow)
419 setCachedCellRange(nCol, nRow, nCol, nRow);
422 void ScExternalRefCache::Table::setCachedCellRange(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
424 ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
425 maCachedRanges.Join(aRange);
428 void ScExternalRefCache::Table::setWholeTableCached()
430 setCachedCellRange(0, 0, MAXCOL, MAXROW);
433 bool ScExternalRefCache::Table::isInCachedRanges(SCCOL nCol, SCROW nRow) const
435 return maCachedRanges.In(ScRange(nCol, nRow, 0, nCol, nRow, 0));
438 ScExternalRefCache::TokenRef ScExternalRefCache::Table::getEmptyOrNullToken(
439 SCCOL nCol, SCROW nRow) const
441 if (isInCachedRanges(nCol, nRow))
443 TokenRef p(new ScEmptyCellToken(false, false));
444 return p;
446 return TokenRef();
449 ScExternalRefCache::TableName::TableName(const OUString& rUpper, const OUString& rReal) :
450 maUpperName(rUpper), maRealName(rReal)
454 ScExternalRefCache::CellFormat::CellFormat() :
455 mbIsSet(false), mnType(SvNumFormatType::ALL), mnIndex(0)
459 ScExternalRefCache::ScExternalRefCache()
460 : mxFakeDoc(new ScDocument())
463 ScExternalRefCache::~ScExternalRefCache() {}
465 const OUString* ScExternalRefCache::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
467 osl::MutexGuard aGuard(&maMtxDocs);
469 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
470 if (itrDoc == maDocs.end())
472 // specified document is not cached.
473 return nullptr;
476 const DocItem& rDoc = itrDoc->second;
477 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
478 if (itrTabId == rDoc.maTableNameIndex.end())
480 // the specified table is not in cache.
481 return nullptr;
484 return &rDoc.maTableNames[itrTabId->second].maRealName;
487 const OUString* ScExternalRefCache::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
489 osl::MutexGuard aGuard(&maMtxDocs);
491 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
492 if (itrDoc == maDocs.end())
494 // specified document is not cached.
495 return nullptr;
498 const DocItem& rDoc = itrDoc->second;
499 NamePairMap::const_iterator itr = rDoc.maRealRangeNameMap.find(
500 ScGlobal::getCharClassPtr()->uppercase(rRangeName));
501 if (itr == rDoc.maRealRangeNameMap.end())
502 // range name not found.
503 return nullptr;
505 return &itr->second;
508 ScExternalRefCache::TokenRef ScExternalRefCache::getCellData(
509 sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex)
511 osl::MutexGuard aGuard(&maMtxDocs);
513 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
514 if (itrDoc == maDocs.end())
516 // specified document is not cached.
517 return TokenRef();
520 const DocItem& rDoc = itrDoc->second;
521 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
522 if (itrTabId == rDoc.maTableNameIndex.end())
524 // the specified table is not in cache.
525 return TokenRef();
528 const TableTypeRef& pTableData = rDoc.maTables[itrTabId->second];
529 if (!pTableData)
531 // the table data is not instantiated yet.
532 return TokenRef();
535 return pTableData->getCell(nCol, nRow, pnFmtIndex);
538 ScExternalRefCache::TokenArrayRef ScExternalRefCache::getCellRangeData(
539 sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange)
541 osl::MutexGuard aGuard(&maMtxDocs);
543 DocDataType::iterator itrDoc = maDocs.find(nFileId);
544 if (itrDoc == maDocs.end())
545 // specified document is not cached.
546 return TokenArrayRef();
548 DocItem& rDoc = itrDoc->second;
550 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
551 if (itrTabId == rDoc.maTableNameIndex.end())
552 // the specified table is not in cache.
553 return TokenArrayRef();
555 const ScAddress& s = rRange.aStart;
556 const ScAddress& e = rRange.aEnd;
558 const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
559 const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
560 const SCROW nRow1 = s.Row(), nRow2 = e.Row();
562 // Make sure I have all the tables cached.
563 size_t nTabFirstId = itrTabId->second;
564 size_t nTabLastId = nTabFirstId + nTab2 - nTab1;
565 if (nTabLastId >= rDoc.maTables.size())
566 // not all tables are cached.
567 return TokenArrayRef();
569 ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
571 RangeArrayMap::const_iterator itrRange = rDoc.maRangeArrays.find( aCacheRange);
572 if (itrRange != rDoc.maRangeArrays.end())
573 // Cache hit!
574 return itrRange->second;
576 std::unique_ptr<ScRange> pNewRange;
577 TokenArrayRef pArray;
578 bool bFirstTab = true;
579 for (size_t nTab = nTabFirstId; nTab <= nTabLastId; ++nTab)
581 TableTypeRef pTab = rDoc.maTables[nTab];
582 if (!pTab)
583 return TokenArrayRef();
585 SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
586 SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
588 if (!pTab->isRangeCached(nDataCol1, nDataRow1, nDataCol2, nDataRow2))
590 // specified range is not entirely within cached ranges.
591 return TokenArrayRef();
594 SCSIZE nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
595 SCSIZE nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
596 ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
598 // Needed in shrink and fill.
599 vector<SCROW> aRows;
600 pTab->getAllRows(aRows, nDataRow1, nDataRow2);
601 bool bFill = true;
603 // Check if size could be allocated and if not skip the fill, there's
604 // one error element instead. But retry first with the actual data area
605 // if that is smaller than the original range, which works for most
606 // functions just not some that operate/compare with the original size
607 // and expect empty values in non-data areas.
608 // Restrict this though to ranges of entire columns or rows, other
609 // ranges might be on purpose. (Other special cases to handle?)
610 /* TODO: sparse matrix could help */
611 SCSIZE nMatCols, nMatRows;
612 xMat->GetDimensions( nMatCols, nMatRows);
613 if (nMatCols != nMatrixColumns || nMatRows != nMatrixRows)
615 bFill = false;
616 if (aRows.empty())
618 // There's no data at all. Set the one matrix element to empty
619 // for column-repeated and row-repeated access.
620 xMat->PutEmpty(0,0);
622 else if ((nCol1 == 0 && nCol2 == MAXCOL) || (nRow1 == 0 && nRow2 == MAXROW))
624 nDataRow1 = aRows.front();
625 nDataRow2 = aRows.back();
626 SCCOL nMinCol = std::numeric_limits<SCCOL>::max();
627 SCCOL nMaxCol = std::numeric_limits<SCCOL>::min();
628 for (const auto& rRow : aRows)
630 vector<SCCOL> aCols;
631 pTab->getAllCols(rRow, aCols, nDataCol1, nDataCol2);
632 if (!aCols.empty())
634 nMinCol = std::min( nMinCol, aCols.front());
635 nMaxCol = std::max( nMaxCol, aCols.back());
639 if (nMinCol <= nMaxCol && ((o3tl::make_unsigned(nMaxCol-nMinCol+1) < nMatrixColumns) ||
640 (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows)))
642 nMatrixColumns = static_cast<SCSIZE>(nMaxCol-nMinCol+1);
643 nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
644 xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
645 xMat->GetDimensions( nMatCols, nMatRows);
646 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
648 nDataCol1 = nMinCol;
649 nDataCol2 = nMaxCol;
650 bFill = true;
656 if (bFill)
658 // Only fill non-empty cells, for better performance.
659 for (SCROW nRow : aRows)
661 vector<SCCOL> aCols;
662 pTab->getAllCols(nRow, aCols, nDataCol1, nDataCol2);
663 for (SCCOL nCol : aCols)
665 TokenRef pToken = pTab->getCell(nCol, nRow);
666 if (!pToken)
667 // This should never happen!
668 return TokenArrayRef();
670 SCSIZE nC = nCol - nDataCol1, nR = nRow - nDataRow1;
671 switch (pToken->GetType())
673 case svDouble:
674 xMat->PutDouble(pToken->GetDouble(), nC, nR);
675 break;
676 case svString:
677 xMat->PutString(pToken->GetString(), nC, nR);
678 break;
679 default:
685 if (!bFirstTab)
686 pArray->AddOpCode(ocSep);
688 ScMatrixToken aToken(xMat);
689 if (!pArray)
690 pArray = std::make_shared<ScTokenArray>(*mxFakeDoc);
691 pArray->AddToken(aToken);
693 bFirstTab = false;
695 if (!pNewRange)
696 pNewRange.reset(new ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
697 else
698 pNewRange->ExtendTo(ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
702 rDoc.maRangeArrays.emplace(aCacheRange, pArray);
703 if (pNewRange && *pNewRange != aCacheRange)
704 rDoc.maRangeArrays.emplace(*pNewRange, pArray);
706 return pArray;
709 ScExternalRefCache::TokenArrayRef ScExternalRefCache::getRangeNameTokens(sal_uInt16 nFileId, const OUString& rName)
711 osl::MutexGuard aGuard(&maMtxDocs);
713 DocItem* pDoc = getDocItem(nFileId);
714 if (!pDoc)
715 return TokenArrayRef();
717 RangeNameMap& rMap = pDoc->maRangeNames;
718 RangeNameMap::const_iterator itr = rMap.find(
719 ScGlobal::getCharClassPtr()->uppercase(rName));
720 if (itr == rMap.end())
721 return TokenArrayRef();
723 return itr->second;
726 void ScExternalRefCache::setRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, TokenArrayRef pArray)
728 osl::MutexGuard aGuard(&maMtxDocs);
730 DocItem* pDoc = getDocItem(nFileId);
731 if (!pDoc)
732 return;
734 OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(rName);
735 RangeNameMap& rMap = pDoc->maRangeNames;
736 rMap.emplace(aUpperName, pArray);
737 pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
740 bool ScExternalRefCache::isValidRangeName(sal_uInt16 nFileId, const OUString& rName) const
742 osl::MutexGuard aGuard(&maMtxDocs);
744 DocItem* pDoc = getDocItem(nFileId);
745 if (!pDoc)
746 return false;
748 const RangeNameMap& rMap = pDoc->maRangeNames;
749 return rMap.count(rName) > 0;
752 void ScExternalRefCache::setRangeName(sal_uInt16 nFileId, const OUString& rName)
754 osl::MutexGuard aGuard(&maMtxDocs);
756 DocItem* pDoc = getDocItem(nFileId);
757 if (!pDoc)
758 return;
760 OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(rName);
761 pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
764 void ScExternalRefCache::setCellData(sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow,
765 TokenRef const & pToken, sal_uLong nFmtIndex)
767 if (!isDocInitialized(nFileId))
768 return;
770 using ::std::pair;
771 DocItem* pDocItem = getDocItem(nFileId);
772 if (!pDocItem)
773 return;
775 DocItem& rDoc = *pDocItem;
777 // See if the table by this name already exists.
778 TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rTabName);
779 if (itrTabName == rDoc.maTableNameIndex.end())
780 // Table not found. Maybe the table name or the file id is wrong ???
781 return;
783 TableTypeRef& pTableData = rDoc.maTables[itrTabName->second];
784 if (!pTableData)
785 pTableData = std::make_shared<Table>();
787 pTableData->setCell(nCol, nRow, pToken, nFmtIndex);
788 pTableData->setCachedCell(nCol, nRow);
791 void ScExternalRefCache::setCellRangeData(sal_uInt16 nFileId, const ScRange& rRange, const vector<SingleRangeData>& rData,
792 const TokenArrayRef& pArray)
794 using ::std::pair;
795 if (rData.empty() || !isDocInitialized(nFileId))
796 // nothing to cache
797 return;
799 // First, get the document item for the given file ID.
800 DocItem* pDocItem = getDocItem(nFileId);
801 if (!pDocItem)
802 return;
804 DocItem& rDoc = *pDocItem;
806 // Now, find the table position of the first table to cache.
807 const OUString& rFirstTabName = rData.front().maTableName;
808 TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rFirstTabName);
809 if (itrTabName == rDoc.maTableNameIndex.end())
811 // table index not found.
812 return;
815 size_t nTabFirstId = itrTabName->second;
816 SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
817 SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
818 size_t i = nTabFirstId;
819 for (const auto& rItem : rData)
821 TableTypeRef& pTabData = rDoc.maTables[i];
822 if (!pTabData)
823 pTabData = std::make_shared<Table>();
825 const ScMatrixRef& pMat = rItem.mpRangeData;
826 SCSIZE nMatCols, nMatRows;
827 pMat->GetDimensions( nMatCols, nMatRows);
828 if (nMatCols > o3tl::make_unsigned(nCol2 - nCol1) && nMatRows > o3tl::make_unsigned(nRow2 - nRow1))
830 ScMatrix::DoubleOpFunction aDoubleFunc = [=](size_t row, size_t col, double val) -> void
832 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val), 0, false);
834 ScMatrix::BoolOpFunction aBoolFunc = [=](size_t row, size_t col, bool val) -> void
836 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val ? 1.0 : 0.0), 0, false);
838 ScMatrix::StringOpFunction aStringFunc = [=](size_t row, size_t col, svl::SharedString val) -> void
840 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaStringToken(val), 0, false);
842 ScMatrix::EmptyOpFunction aEmptyFunc = [=](size_t /*row*/, size_t /*col*/) -> void
844 // Nothing. Empty cell.
846 pMat->ExecuteOperation(std::pair<size_t, size_t>(0, 0),
847 std::pair<size_t, size_t>(nRow2-nRow1, nCol2-nCol1),
848 aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc);
849 // Mark the whole range 'cached'.
850 pTabData->setCachedCellRange(nCol1, nRow1, nCol2, nRow2);
852 else
854 // This may happen due to a matrix not been allocated earlier, in
855 // which case it should have exactly one error element.
856 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix size mismatch");
857 if (nMatCols != 1 || nMatRows != 1)
858 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - not a one element matrix");
859 else
861 FormulaError nErr = GetDoubleErrorValue( pMat->GetDouble(0,0));
862 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix error value is " << static_cast<int>(nErr) <<
863 (nErr == FormulaError::MatrixSize ? ", ok" : ", not ok"));
866 ++i;
869 size_t nTabLastId = nTabFirstId + rRange.aEnd.Tab() - rRange.aStart.Tab();
870 ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
872 rDoc.maRangeArrays.emplace(aCacheRange, pArray);
875 bool ScExternalRefCache::isDocInitialized(sal_uInt16 nFileId)
877 DocItem* pDoc = getDocItem(nFileId);
878 if (!pDoc)
879 return false;
881 return pDoc->mbInitFromSource;
884 static bool lcl_getStrictTableDataIndex(const ScExternalRefCache::TableNameIndexMap& rMap, const OUString& rName, size_t& rIndex)
886 ScExternalRefCache::TableNameIndexMap::const_iterator itr = rMap.find(rName);
887 if (itr == rMap.end())
888 return false;
890 rIndex = itr->second;
891 return true;
894 bool ScExternalRefCache::DocItem::getTableDataIndex( const OUString& rTabName, size_t& rIndex ) const
896 ScExternalRefCache::TableNameIndexMap::const_iterator itr = findTableNameIndex(rTabName);
897 if (itr == maTableNameIndex.end())
898 return false;
900 rIndex = itr->second;
901 return true;
904 namespace {
905 OUString getFirstSheetName()
907 // Get Custom prefix.
908 const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
909 // Form sheet name identical to the first generated sheet name when
910 // creating an internal document, e.g. 'Sheet1'.
911 return rOpt.GetInitTabPrefix() + "1";
915 void ScExternalRefCache::initializeDoc(sal_uInt16 nFileId, const vector<OUString>& rTabNames,
916 const OUString& rBaseName)
918 DocItem* pDoc = getDocItem(nFileId);
919 if (!pDoc)
920 return;
922 size_t n = rTabNames.size();
924 // table name list - the list must include all table names in the source
925 // document and only to be populated when loading the source document, not
926 // when loading cached data from, say, Excel XCT/CRN records.
927 vector<TableName> aNewTabNames;
928 aNewTabNames.reserve(n);
929 for (const auto& rTabName : rTabNames)
931 TableName aNameItem(ScGlobal::getCharClassPtr()->uppercase(rTabName), rTabName);
932 aNewTabNames.push_back(aNameItem);
934 pDoc->maTableNames.swap(aNewTabNames);
936 // data tables - preserve any existing data that may have been set during
937 // file import.
938 vector<TableTypeRef> aNewTables(n);
939 for (size_t i = 0; i < n; ++i)
941 size_t nIndex;
942 if (lcl_getStrictTableDataIndex(pDoc->maTableNameIndex, pDoc->maTableNames[i].maUpperName, nIndex))
944 aNewTables[i] = pDoc->maTables[nIndex];
947 pDoc->maTables.swap(aNewTables);
949 // name index map
950 TableNameIndexMap aNewNameIndex;
951 for (size_t i = 0; i < n; ++i)
952 aNewNameIndex.emplace(pDoc->maTableNames[i].maUpperName, i);
953 pDoc->maTableNameIndex.swap(aNewNameIndex);
955 // Setup name for Sheet1 vs base name to be able to load documents
956 // that store the base name as table name, or vice versa.
957 pDoc->maSingleTableNameAlias.clear();
958 if (!rBaseName.isEmpty() && pDoc->maTableNames.size() == 1)
960 OUString aSheetName = getFirstSheetName();
961 // If the one and only table name matches exactly, carry on the base
962 // file name for further alias use. If instead the table name matches
963 // the base name, carry on the sheet name as alias.
964 if (ScGlobal::GetpTransliteration()->isEqual( pDoc->maTableNames[0].maRealName, aSheetName))
965 pDoc->maSingleTableNameAlias = rBaseName;
966 else if (ScGlobal::GetpTransliteration()->isEqual( pDoc->maTableNames[0].maRealName, rBaseName))
967 pDoc->maSingleTableNameAlias = aSheetName;
970 pDoc->mbInitFromSource = true;
973 ScExternalRefCache::TableNameIndexMap::const_iterator ScExternalRefCache::DocItem::findTableNameIndex(
974 const OUString& rTabName ) const
976 const OUString aTabNameUpper = ScGlobal::getCharClassPtr()->uppercase( rTabName);
977 TableNameIndexMap::const_iterator itrTabName = maTableNameIndex.find( aTabNameUpper);
978 if (itrTabName != maTableNameIndex.end())
979 return itrTabName;
981 // Since some time for external references to CSV files the base name is
982 // used as sheet name instead of Sheet1, check if we can resolve that.
983 // Also helps users that got accustomed to one or the other way.
984 if (maSingleTableNameAlias.isEmpty() || maTableNameIndex.size() != 1)
985 return itrTabName;
987 // maSingleTableNameAlias has been set up only if the original file loaded
988 // had exactly one sheet and internal sheet name was Sheet1 or localized or
989 // customized equivalent, or base name.
990 if (aTabNameUpper == ScGlobal::getCharClassPtr()->uppercase( maSingleTableNameAlias))
991 return maTableNameIndex.begin();
993 return itrTabName;
996 bool ScExternalRefCache::DocItem::getSingleTableNameAlternative( OUString& rTabName ) const
998 if (maSingleTableNameAlias.isEmpty() || maTableNames.size() != 1)
999 return false;
1000 if (ScGlobal::GetpTransliteration()->isEqual( rTabName, maTableNames[0].maRealName))
1002 rTabName = maSingleTableNameAlias;
1003 return true;
1005 if (ScGlobal::GetpTransliteration()->isEqual( rTabName, maSingleTableNameAlias))
1007 rTabName = maTableNames[0].maRealName;
1008 return true;
1010 return false;
1013 bool ScExternalRefCache::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
1014 sal_uInt16 nFileId ) const
1016 bool bFound = rSrcDoc.GetTable( rTabName, rTab);
1017 if (!bFound)
1019 // Check the one table alias alternative.
1020 const DocItem* pDoc = getDocItem( nFileId );
1021 if (pDoc)
1023 OUString aTabName( rTabName);
1024 if (pDoc->getSingleTableNameAlternative( aTabName))
1025 bFound = rSrcDoc.GetTable( aTabName, rTab);
1028 return bFound;
1031 OUString ScExternalRefCache::getTableName(sal_uInt16 nFileId, size_t nCacheId) const
1033 if( DocItem* pDoc = getDocItem( nFileId ) )
1034 if( nCacheId < pDoc->maTableNames.size() )
1035 return pDoc->maTableNames[ nCacheId ].maRealName;
1036 return EMPTY_OUSTRING;
1039 void ScExternalRefCache::getAllTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
1041 rTabNames.clear();
1042 DocItem* pDoc = getDocItem(nFileId);
1043 if (!pDoc)
1044 return;
1046 size_t n = pDoc->maTableNames.size();
1047 rTabNames.reserve(n);
1048 for (const auto& rTableName : pDoc->maTableNames)
1049 rTabNames.push_back(rTableName.maRealName);
1052 SCTAB ScExternalRefCache::getTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
1054 DocItem* pDoc = getDocItem(nFileId);
1055 if (!pDoc)
1056 return -1;
1058 vector<TableName>::const_iterator itrBeg = pDoc->maTableNames.begin();
1059 vector<TableName>::const_iterator itrEnd = pDoc->maTableNames.end();
1061 vector<TableName>::const_iterator itrStartTab = ::std::find_if( itrBeg, itrEnd,
1062 TabNameSearchPredicate( rStartTabName));
1063 if (itrStartTab == itrEnd)
1064 return -1;
1066 vector<TableName>::const_iterator itrEndTab = ::std::find_if( itrBeg, itrEnd,
1067 TabNameSearchPredicate( rEndTabName));
1068 if (itrEndTab == itrEnd)
1069 return 0;
1071 size_t nStartDist = ::std::distance( itrBeg, itrStartTab);
1072 size_t nEndDist = ::std::distance( itrBeg, itrEndTab);
1073 return nStartDist <= nEndDist ? static_cast<SCTAB>(nEndDist - nStartDist + 1) : -static_cast<SCTAB>(nStartDist - nEndDist + 1);
1076 void ScExternalRefCache::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
1078 osl::MutexGuard aGuard(&maMtxDocs);
1080 using ::std::sort;
1081 using ::std::unique;
1083 vector<sal_uInt32> aNumFmts;
1084 for (const auto& rEntry : maDocs)
1086 const vector<TableTypeRef>& rTables = rEntry.second.maTables;
1087 for (const TableTypeRef& pTab : rTables)
1089 if (!pTab)
1090 continue;
1092 pTab->getAllNumberFormats(aNumFmts);
1096 // remove duplicates.
1097 sort(aNumFmts.begin(), aNumFmts.end());
1098 aNumFmts.erase(unique(aNumFmts.begin(), aNumFmts.end()), aNumFmts.end());
1099 rNumFmts.swap(aNumFmts);
1102 bool ScExternalRefCache::setCacheDocReferenced( sal_uInt16 nFileId )
1104 DocItem* pDocItem = getDocItem(nFileId);
1105 if (!pDocItem)
1106 return areAllCacheTablesReferenced();
1108 for (auto& rxTab : pDocItem->maTables)
1110 if (rxTab)
1111 rxTab->setReferenced(true);
1113 addCacheDocToReferenced( nFileId);
1114 return areAllCacheTablesReferenced();
1117 bool ScExternalRefCache::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
1119 DocItem* pDoc = getDocItem(nFileId);
1120 if (pDoc)
1122 size_t nIndex = 0;
1123 if (pDoc->getTableDataIndex( rTabName, nIndex))
1125 size_t nStop = ::std::min( nIndex + nSheets, pDoc->maTables.size());
1126 for (size_t i = nIndex; i < nStop; ++i)
1128 TableTypeRef pTab = pDoc->maTables[i];
1129 if (pTab)
1131 if (!pTab->isReferenced())
1133 pTab->setReferenced(true);
1134 addCacheTableToReferenced( nFileId, i);
1140 return areAllCacheTablesReferenced();
1143 void ScExternalRefCache::setAllCacheTableReferencedStati( bool bReferenced )
1145 osl::MutexGuard aGuard(&maMtxDocs);
1147 if (bReferenced)
1149 maReferenced.reset(0);
1150 for (auto& rEntry : maDocs)
1152 ScExternalRefCache::DocItem& rDocItem = rEntry.second;
1153 for (auto& rxTab : rDocItem.maTables)
1155 if (rxTab)
1156 rxTab->setReferenced(true);
1160 else
1162 size_t nDocs = 0;
1163 auto itrMax = std::max_element(maDocs.begin(), maDocs.end(),
1164 [](const DocDataType::value_type& a, const DocDataType::value_type& b) { return a.first < b.first; });
1165 if (itrMax != maDocs.end())
1166 nDocs = itrMax->first + 1;
1167 maReferenced.reset( nDocs);
1169 for (auto& [nFileId, rDocItem] : maDocs)
1171 size_t nTables = rDocItem.maTables.size();
1172 ReferencedStatus::DocReferenced & rDocReferenced = maReferenced.maDocs[nFileId];
1173 // All referenced => non-existing tables evaluate as completed.
1174 rDocReferenced.maTables.resize( nTables, true);
1175 for (size_t i=0; i < nTables; ++i)
1177 TableTypeRef & xTab = rDocItem.maTables[i];
1178 if (xTab)
1180 xTab->setReferenced(false);
1181 rDocReferenced.maTables[i] = false;
1182 rDocReferenced.mbAllTablesReferenced = false;
1183 // An addCacheTableToReferenced() actually may have
1184 // resulted in mbAllReferenced been set. Clear it.
1185 maReferenced.mbAllReferenced = false;
1192 void ScExternalRefCache::addCacheTableToReferenced( sal_uInt16 nFileId, size_t nIndex )
1194 if (nFileId >= maReferenced.maDocs.size())
1195 return;
1197 ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
1198 size_t nTables = rTables.size();
1199 if (nIndex >= nTables)
1200 return;
1202 if (!rTables[nIndex])
1204 rTables[nIndex] = true;
1205 size_t i = 0;
1206 while (i < nTables && rTables[i])
1207 ++i;
1208 if (i == nTables)
1210 maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
1211 maReferenced.checkAllDocs();
1216 void ScExternalRefCache::addCacheDocToReferenced( sal_uInt16 nFileId )
1218 if (nFileId >= maReferenced.maDocs.size())
1219 return;
1221 if (!maReferenced.maDocs[nFileId].mbAllTablesReferenced)
1223 ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
1224 size_t nSize = rTables.size();
1225 for (size_t i=0; i < nSize; ++i)
1226 rTables[i] = true;
1227 maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
1228 maReferenced.checkAllDocs();
1232 void ScExternalRefCache::getAllCachedDataSpans( const ScDocument& rSrcDoc, sal_uInt16 nFileId, sc::ColumnSpanSet& rSet ) const
1234 const DocItem* pDocItem = getDocItem(nFileId);
1235 if (!pDocItem)
1236 // This document is not cached.
1237 return;
1239 const std::vector<TableTypeRef>& rTables = pDocItem->maTables;
1240 for (size_t nTab = 0, nTabCount = rTables.size(); nTab < nTabCount; ++nTab)
1242 TableTypeRef pTab = rTables[nTab];
1243 if (!pTab)
1244 continue;
1246 std::vector<SCROW> aRows;
1247 pTab->getAllRows(aRows);
1248 for (SCROW nRow : aRows)
1250 std::vector<SCCOL> aCols;
1251 pTab->getAllCols(nRow, aCols);
1252 for (SCCOL nCol : aCols)
1254 rSet.set(rSrcDoc, nTab, nCol, nRow, true);
1260 ScExternalRefCache::ReferencedStatus::ReferencedStatus() :
1261 mbAllReferenced(false)
1263 reset(0);
1266 void ScExternalRefCache::ReferencedStatus::reset( size_t nDocs )
1268 if (nDocs)
1270 mbAllReferenced = false;
1271 DocReferencedVec aRefs( nDocs);
1272 maDocs.swap( aRefs);
1274 else
1276 mbAllReferenced = true;
1277 DocReferencedVec aRefs;
1278 maDocs.swap( aRefs);
1282 void ScExternalRefCache::ReferencedStatus::checkAllDocs()
1284 if (std::all_of(maDocs.begin(), maDocs.end(), [](const DocReferenced& rDoc) { return rDoc.mbAllTablesReferenced; }))
1285 mbAllReferenced = true;
1288 ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
1290 DocItem* pDoc = getDocItem(nFileId);
1291 if (!pDoc || nTabIndex >= pDoc->maTables.size())
1292 return TableTypeRef();
1294 return pDoc->maTables[nTabIndex];
1297 ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, const OUString& rTabName,
1298 bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
1300 // In API, the index is transported as cached sheet ID of type sal_Int32 in
1301 // sheet::SingleReference.Sheet or sheet::ComplexReference.Reference1.Sheet
1302 // in a sheet::FormulaToken, choose a sensible value for N/A. Effectively
1303 // being 0xffffffff
1304 const size_t nNotAvailable = static_cast<size_t>( static_cast<sal_Int32>( -1));
1306 DocItem* pDoc = getDocItem(nFileId);
1307 if (!pDoc)
1309 if (pnIndex) *pnIndex = nNotAvailable;
1310 return TableTypeRef();
1313 DocItem& rDoc = *pDoc;
1315 size_t nIndex;
1316 if (rDoc.getTableDataIndex(rTabName, nIndex))
1318 // specified table found.
1319 if( pnIndex ) *pnIndex = nIndex;
1320 if (bCreateNew && !rDoc.maTables[nIndex])
1321 rDoc.maTables[nIndex] = std::make_shared<Table>();
1323 return rDoc.maTables[nIndex];
1326 if (!bCreateNew)
1328 if (pnIndex) *pnIndex = nNotAvailable;
1329 return TableTypeRef();
1332 // If this is the first table to be created propagate the base name or
1333 // Sheet1 as an alias. For subsequent tables remove it again.
1334 if (rDoc.maTableNames.empty())
1336 if (pExtUrl)
1338 const OUString aBaseName( INetURLObject( *pExtUrl).GetBase());
1339 const OUString aSheetName( getFirstSheetName());
1340 if (ScGlobal::GetpTransliteration()->isEqual( rTabName, aSheetName))
1341 pDoc->maSingleTableNameAlias = aBaseName;
1342 else if (ScGlobal::GetpTransliteration()->isEqual( rTabName, aBaseName))
1343 pDoc->maSingleTableNameAlias = aSheetName;
1346 else
1348 rDoc.maSingleTableNameAlias.clear();
1351 // Specified table doesn't exist yet. Create one.
1352 OUString aTabNameUpper = ScGlobal::getCharClassPtr()->uppercase(rTabName);
1353 nIndex = rDoc.maTables.size();
1354 if( pnIndex ) *pnIndex = nIndex;
1355 TableTypeRef pTab = std::make_shared<Table>();
1356 rDoc.maTables.push_back(pTab);
1357 rDoc.maTableNames.emplace_back(aTabNameUpper, rTabName);
1358 rDoc.maTableNameIndex.emplace(aTabNameUpper, nIndex);
1359 return pTab;
1362 void ScExternalRefCache::clearCache(sal_uInt16 nFileId)
1364 osl::MutexGuard aGuard(&maMtxDocs);
1365 maDocs.erase(nFileId);
1368 void ScExternalRefCache::clearCacheTables(sal_uInt16 nFileId)
1370 osl::MutexGuard aGuard(&maMtxDocs);
1371 DocItem* pDocItem = getDocItem(nFileId);
1372 if (!pDocItem)
1373 // This document is not cached at all.
1374 return;
1376 // Clear all cache table content, but keep the tables.
1377 std::vector<TableTypeRef>& rTabs = pDocItem->maTables;
1378 for (TableTypeRef & pTab : rTabs)
1380 if (!pTab)
1381 continue;
1383 pTab->clear();
1386 // Clear the external range name caches.
1387 pDocItem->maRangeNames.clear();
1388 pDocItem->maRangeArrays.clear();
1389 pDocItem->maRealRangeNameMap.clear();
1392 ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(sal_uInt16 nFileId) const
1394 osl::MutexGuard aGuard(&maMtxDocs);
1396 using ::std::pair;
1397 DocDataType::iterator itrDoc = maDocs.find(nFileId);
1398 if (itrDoc == maDocs.end())
1400 // specified document is not cached.
1401 pair<DocDataType::iterator, bool> res = maDocs.emplace(
1402 nFileId, DocItem());
1404 if (!res.second)
1405 // insertion failed.
1406 return nullptr;
1408 itrDoc = res.first;
1411 return &itrDoc->second;
1414 ScExternalRefLink::ScExternalRefLink(ScDocument& rDoc, sal_uInt16 nFileId) :
1415 ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE),
1416 mnFileId(nFileId),
1417 mrDoc(rDoc),
1418 mbDoRefresh(true)
1422 ScExternalRefLink::~ScExternalRefLink()
1426 void ScExternalRefLink::Closed()
1428 ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
1429 pMgr->breakLink(mnFileId);
1432 ::sfx2::SvBaseLink::UpdateResult ScExternalRefLink::DataChanged(const OUString& /*rMimeType*/, const Any& /*rValue*/)
1434 if (!mbDoRefresh)
1435 return SUCCESS;
1437 OUString aFile, aFilter;
1438 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, nullptr, &aFilter);
1439 ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
1441 if (!pMgr->isFileLoadable(aFile))
1442 return ERROR_GENERAL;
1444 const OUString* pCurFile = pMgr->getExternalFileName(mnFileId);
1445 if (!pCurFile)
1446 return ERROR_GENERAL;
1448 if (*pCurFile == aFile)
1450 // Refresh the current source document.
1451 if (!pMgr->refreshSrcDocument(mnFileId))
1452 return ERROR_GENERAL;
1454 else
1456 // The source document has changed.
1457 ScDocShell* pDocShell = ScDocShell::GetViewData()->GetDocShell();
1458 ScDocShellModificator aMod(*pDocShell);
1459 pMgr->switchSrcFile(mnFileId, aFile, aFilter);
1460 aMod.SetDocumentModified();
1463 return SUCCESS;
1466 void ScExternalRefLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /*rEndEditHdl*/)
1468 SvBaseLink::Edit(pParent, Link<SvBaseLink&,void>());
1471 void ScExternalRefLink::SetDoRefresh(bool b)
1473 mbDoRefresh = b;
1476 static FormulaToken* convertToToken( ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRefCellValue& rCell )
1478 if (rCell.hasEmptyValue())
1480 bool bInherited = (rCell.meType == CELLTYPE_FORMULA);
1481 return new ScEmptyCellToken(bInherited, false);
1484 switch (rCell.meType)
1486 case CELLTYPE_EDIT:
1487 case CELLTYPE_STRING:
1489 OUString aStr = rCell.getString(&rSrcDoc);
1490 svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern(aStr);
1491 return new formula::FormulaStringToken(aSS);
1493 case CELLTYPE_VALUE:
1494 return new formula::FormulaDoubleToken(rCell.mfValue);
1495 case CELLTYPE_FORMULA:
1497 ScFormulaCell* pFCell = rCell.mpFormula;
1498 FormulaError nError = pFCell->GetErrCode();
1499 if (nError != FormulaError::NONE)
1500 return new FormulaErrorToken( nError);
1501 else if (pFCell->IsValue())
1503 double fVal = pFCell->GetValue();
1504 return new formula::FormulaDoubleToken(fVal);
1506 else
1508 svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern( pFCell->GetString().getString());
1509 return new formula::FormulaStringToken(aSS);
1512 default:
1513 OSL_FAIL("attempted to convert an unknown cell type.");
1516 return nullptr;
1519 static std::unique_ptr<ScTokenArray> convertToTokenArray(
1520 ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRange& rRange, vector<ScExternalRefCache::SingleRangeData>& rCacheData )
1522 ScAddress& s = rRange.aStart;
1523 ScAddress& e = rRange.aEnd;
1525 const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
1526 const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
1527 const SCROW nRow1 = s.Row(), nRow2 = e.Row();
1529 if (nTab2 != nTab1)
1530 // For now, we don't support multi-sheet ranges intentionally because
1531 // we don't have a way to express them in a single token. In the
1532 // future we can introduce a new stack variable type svMatrixList with
1533 // a new token type that can store a 3D matrix value and convert a 3D
1534 // range to it.
1535 return nullptr;
1537 std::unique_ptr<ScRange> pUsedRange;
1539 unique_ptr<ScTokenArray> pArray(new ScTokenArray(rSrcDoc));
1540 bool bFirstTab = true;
1541 vector<ScExternalRefCache::SingleRangeData>::iterator
1542 itrCache = rCacheData.begin(), itrCacheEnd = rCacheData.end();
1544 for (SCTAB nTab = nTab1; nTab <= nTab2 && itrCache != itrCacheEnd; ++nTab, ++itrCache)
1546 // Only loop within the data area.
1547 SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
1548 SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
1549 bool bShrunk;
1550 if (!rSrcDoc.ShrinkToUsedDataArea( bShrunk, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, false))
1551 // no data within specified range.
1552 continue;
1554 if (pUsedRange)
1555 // Make sure the used area only grows, not shrinks.
1556 pUsedRange->ExtendTo(ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
1557 else
1558 pUsedRange.reset(new ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
1560 SCSIZE nMatrixColumns = static_cast<SCSIZE>(nCol2-nCol1+1);
1561 SCSIZE nMatrixRows = static_cast<SCSIZE>(nRow2-nRow1+1);
1562 ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
1564 // Check if size could be allocated and if not skip the fill, there's
1565 // one error element instead. But retry first with the actual data area
1566 // if that is smaller than the original range, which works for most
1567 // functions just not some that operate/compare with the original size
1568 // and expect empty values in non-data areas.
1569 // Restrict this though to ranges of entire columns or rows, other
1570 // ranges might be on purpose. (Other special cases to handle?)
1571 /* TODO: sparse matrix could help */
1572 SCSIZE nMatCols, nMatRows;
1573 xMat->GetDimensions( nMatCols, nMatRows);
1574 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
1576 rSrcDoc.FillMatrix(*xMat, nTab, nCol1, nRow1, nCol2, nRow2, &rHostDoc.GetSharedStringPool());
1578 else if ((nCol1 == 0 && nCol2 == MAXCOL) || (nRow1 == 0 && nRow2 == MAXROW))
1580 if ((o3tl::make_unsigned(nDataCol2-nDataCol1+1) < nMatrixColumns) ||
1581 (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows))
1583 nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
1584 nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
1585 xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
1586 xMat->GetDimensions( nMatCols, nMatRows);
1587 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
1588 rSrcDoc.FillMatrix(*xMat, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, &rHostDoc.GetSharedStringPool());
1592 if (!bFirstTab)
1593 pArray->AddOpCode(ocSep);
1595 ScMatrixToken aToken(xMat);
1596 pArray->AddToken(aToken);
1598 itrCache->mpRangeData = xMat;
1600 bFirstTab = false;
1603 if (!pUsedRange)
1604 return nullptr;
1606 s.SetCol(pUsedRange->aStart.Col());
1607 s.SetRow(pUsedRange->aStart.Row());
1608 e.SetCol(pUsedRange->aEnd.Col());
1609 e.SetRow(pUsedRange->aEnd.Row());
1611 return pArray;
1614 static std::unique_ptr<ScTokenArray> lcl_fillEmptyMatrix(const ScDocument& rDoc, const ScRange& rRange)
1616 SCSIZE nC = static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1);
1617 SCSIZE nR = static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1);
1618 ScMatrixRef xMat = new ScMatrix(nC, nR);
1620 ScMatrixToken aToken(xMat);
1621 unique_ptr<ScTokenArray> pArray(new ScTokenArray(rDoc));
1622 pArray->AddToken(aToken);
1623 return pArray;
1626 namespace {
1627 bool isLinkUpdateAllowedInDoc(const ScDocument& rDoc)
1629 SfxObjectShell* pDocShell = rDoc.GetDocumentShell();
1630 if (!pDocShell)
1631 return false;
1633 return pDocShell->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate();
1637 ScExternalRefManager::ScExternalRefManager(ScDocument& rDoc) :
1638 mrDoc(rDoc),
1639 mbInReferenceMarking(false),
1640 mbUserInteractionEnabled(true),
1641 mbDocTimerEnabled(true)
1643 maSrcDocTimer.SetInvokeHandler( LINK(this, ScExternalRefManager, TimeOutHdl) );
1644 maSrcDocTimer.SetTimeout(SRCDOC_SCAN_INTERVAL);
1645 maSrcDocTimer.SetDebugName( "sc::ScExternalRefManager maSrcDocTimer" );
1648 ScExternalRefManager::~ScExternalRefManager()
1650 clear();
1653 OUString ScExternalRefManager::getCacheTableName(sal_uInt16 nFileId, size_t nTabIndex) const
1655 return maRefCache.getTableName(nFileId, nTabIndex);
1658 ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
1660 return maRefCache.getCacheTable(nFileId, nTabIndex);
1663 ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(
1664 sal_uInt16 nFileId, const OUString& rTabName, bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
1666 return maRefCache.getCacheTable(nFileId, rTabName, bCreateNew, pnIndex, pExtUrl);
1669 ScExternalRefManager::LinkListener::LinkListener()
1673 ScExternalRefManager::LinkListener::~LinkListener()
1677 ScExternalRefManager::ApiGuard::ApiGuard(const ScDocument& rDoc) :
1678 mpMgr(rDoc.GetExternalRefManager()),
1679 mbOldInteractionEnabled(mpMgr->mbUserInteractionEnabled)
1681 // We don't want user interaction handled in the API.
1682 mpMgr->mbUserInteractionEnabled = false;
1685 ScExternalRefManager::ApiGuard::~ApiGuard()
1687 // Restore old value.
1688 mpMgr->mbUserInteractionEnabled = mbOldInteractionEnabled;
1691 void ScExternalRefManager::getAllCachedTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
1693 maRefCache.getAllTableNames(nFileId, rTabNames);
1696 SCTAB ScExternalRefManager::getCachedTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
1698 return maRefCache.getTabSpan( nFileId, rStartTabName, rEndTabName);
1701 void ScExternalRefManager::getAllCachedNumberFormats(vector<sal_uInt32>& rNumFmts) const
1703 maRefCache.getAllNumberFormats(rNumFmts);
1706 sal_uInt16 ScExternalRefManager::getExternalFileCount() const
1708 return static_cast< sal_uInt16 >( maSrcFiles.size() );
1711 void ScExternalRefManager::markUsedByLinkListeners()
1713 bool bAllMarked = false;
1714 for (const auto& [rFileId, rLinkListeners] : maLinkListeners)
1716 if (!rLinkListeners.empty())
1717 bAllMarked = maRefCache.setCacheDocReferenced(rFileId);
1719 if (bAllMarked)
1720 break;
1721 /* TODO: LinkListeners should remember the table they're listening to.
1722 * As is, listening to one table will mark all tables of the document
1723 * being referenced. */
1727 void ScExternalRefManager::markUsedExternalRefCells()
1729 for (const auto& rEntry : maRefCells)
1731 for (ScFormulaCell* pCell : rEntry.second)
1733 bool bUsed = pCell->MarkUsedExternalReferences();
1734 if (bUsed)
1735 // Return true when at least one cell references external docs.
1736 return;
1741 bool ScExternalRefManager::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
1743 return maRefCache.setCacheTableReferenced( nFileId, rTabName, nSheets);
1746 void ScExternalRefManager::setAllCacheTableReferencedStati( bool bReferenced )
1748 mbInReferenceMarking = !bReferenced;
1749 maRefCache.setAllCacheTableReferencedStati( bReferenced );
1752 void ScExternalRefManager::storeRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const ScTokenArray& rArray)
1754 ScExternalRefCache::TokenArrayRef pArray(rArray.Clone());
1755 maRefCache.setRangeNameTokens(nFileId, rName, pArray);
1758 namespace {
1761 * Put a single cell data into internal cache table.
1763 * @param pFmt optional cell format index that may need to be stored with
1764 * the cell value.
1766 void putCellDataIntoCache(
1767 ScExternalRefCache& rRefCache, const ScExternalRefCache::TokenRef& pToken,
1768 sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
1769 const ScExternalRefCache::CellFormat* pFmt)
1771 // Now, insert the token into cache table but don't cache empty cells.
1772 if (pToken->GetType() != formula::svEmptyCell)
1774 sal_uLong nFmtIndex = (pFmt && pFmt->mbIsSet) ? pFmt->mnIndex : 0;
1775 rRefCache.setCellData(nFileId, rTabName, rCell.Col(), rCell.Row(), pToken, nFmtIndex);
1780 * Put the data into our internal cache table.
1782 * @param rRefCache cache table set.
1783 * @param pArray single range data to be returned.
1784 * @param nFileId external file ID
1785 * @param rTabName name of the table where the data should be cached.
1786 * @param rCacheData range data to be cached.
1787 * @param rCacheRange original cache range, including the empty region if
1788 * any.
1789 * @param rDataRange reduced cache range that includes only the non-empty
1790 * data area.
1792 void putRangeDataIntoCache(
1793 ScExternalRefCache& rRefCache, ScExternalRefCache::TokenArrayRef& pArray,
1794 sal_uInt16 nFileId, const OUString& rTabName,
1795 const vector<ScExternalRefCache::SingleRangeData>& rCacheData,
1796 const ScRange& rCacheRange, const ScRange& rDataRange)
1798 if (pArray)
1799 // Cache these values.
1800 rRefCache.setCellRangeData(nFileId, rDataRange, rCacheData, pArray);
1801 else
1803 // Array is empty. Fill it with an empty matrix of the required size.
1804 pArray = lcl_fillEmptyMatrix(*rRefCache.getFakeDoc(), rCacheRange);
1806 // Make sure to set this range 'cached', to prevent unnecessarily
1807 // accessing the src document time and time again.
1808 ScExternalRefCache::TableTypeRef pCacheTab =
1809 rRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
1810 if (pCacheTab)
1811 pCacheTab->setCachedCellRange(
1812 rCacheRange.aStart.Col(), rCacheRange.aStart.Row(), rCacheRange.aEnd.Col(), rCacheRange.aEnd.Row());
1817 * When accessing an external document for the first time, we need to
1818 * populate the cache with all its sheet names (whether they are referenced
1819 * or not) in the correct order. Many client codes that use external
1820 * references make this assumption.
1822 * @param rRefCache cache table set.
1823 * @param pSrcDoc source document instance.
1824 * @param nFileId external file ID associated with the source document.
1826 void initDocInCache(ScExternalRefCache& rRefCache, const ScDocument* pSrcDoc, sal_uInt16 nFileId)
1828 if (!pSrcDoc)
1829 return;
1831 if (rRefCache.isDocInitialized(nFileId))
1832 // Already initialized. No need to do this twice.
1833 return;
1835 SCTAB nTabCount = pSrcDoc->GetTableCount();
1836 if (!nTabCount)
1837 return;
1839 // Populate the cache with all table names in the source document.
1840 vector<OUString> aTabNames;
1841 aTabNames.reserve(nTabCount);
1842 for (SCTAB i = 0; i < nTabCount; ++i)
1844 OUString aName;
1845 pSrcDoc->GetName(i, aName);
1846 aTabNames.push_back(aName);
1849 // Obtain the base name, don't bother if there are more than one sheets.
1850 OUString aBaseName;
1851 if (nTabCount == 1)
1853 const SfxObjectShell* pShell = pSrcDoc->GetDocumentShell();
1854 if (pShell && pShell->GetMedium())
1856 OUString aName = pShell->GetMedium()->GetName();
1857 aBaseName = INetURLObject( aName).GetBase();
1861 rRefCache.initializeDoc(nFileId, aTabNames, aBaseName);
1866 bool ScExternalRefManager::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
1867 sal_uInt16 nFileId ) const
1869 return maRefCache.getSrcDocTable( rSrcDoc, rTabName, rTab, nFileId);
1872 ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefToken(
1873 sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
1874 const ScAddress* pCurPos, SCTAB* pTab, ScExternalRefCache::CellFormat* pFmt)
1876 if (pCurPos)
1877 insertRefCell(nFileId, *pCurPos);
1879 maybeLinkExternalFile(nFileId);
1881 if (pTab)
1882 *pTab = -1;
1884 if (pFmt)
1885 pFmt->mbIsSet = false;
1887 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
1888 if (pSrcDoc)
1890 // source document already loaded in memory. Re-use this instance.
1891 SCTAB nTab;
1892 if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
1894 // specified table name doesn't exist in the source document.
1895 ScExternalRefCache::TokenRef pToken(new FormulaErrorToken(FormulaError::NoRef));
1896 return pToken;
1899 if (pTab)
1900 *pTab = nTab;
1902 ScExternalRefCache::TokenRef pToken =
1903 getSingleRefTokenFromSrcDoc(
1904 nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
1906 putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
1907 return pToken;
1910 // Check if the given table name and the cell position is cached.
1911 sal_uInt32 nFmtIndex = 0;
1912 ScExternalRefCache::TokenRef pToken = maRefCache.getCellData(
1913 nFileId, rTabName, rCell.Col(), rCell.Row(), &nFmtIndex);
1914 if (pToken)
1916 // Cache hit !
1917 fillCellFormat(nFmtIndex, pFmt);
1918 return pToken;
1921 // reference not cached. read from the source document.
1922 pSrcDoc = getSrcDocument(nFileId);
1923 if (!pSrcDoc)
1925 // Source document not reachable.
1926 if (!isLinkUpdateAllowedInDoc(mrDoc))
1928 // Indicate with specific error.
1929 pToken.reset(new FormulaErrorToken(FormulaError::LinkFormulaNeedingCheck));
1931 else
1933 // Throw a reference error.
1934 pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
1936 return pToken;
1939 SCTAB nTab;
1940 if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
1942 // specified table name doesn't exist in the source document.
1943 pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
1944 return pToken;
1947 if (pTab)
1948 *pTab = nTab;
1950 SCCOL nDataCol1 = 0, nDataCol2 = MAXCOL;
1951 SCROW nDataRow1 = 0, nDataRow2 = MAXROW;
1952 bool bData = pSrcDoc->ShrinkToDataArea(nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2);
1953 if (!bData || rCell.Col() < nDataCol1 || nDataCol2 < rCell.Col() || rCell.Row() < nDataRow1 || nDataRow2 < rCell.Row())
1955 // requested cell is outside the data area. Don't even bother caching
1956 // this data, but add it to the cached range to prevent accessing the
1957 // source document time and time again.
1958 ScExternalRefCache::TableTypeRef pCacheTab =
1959 maRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
1960 if (pCacheTab)
1961 pCacheTab->setCachedCell(rCell.Col(), rCell.Row());
1963 pToken.reset(new ScEmptyCellToken(false, false));
1964 return pToken;
1967 pToken = getSingleRefTokenFromSrcDoc(
1968 nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
1970 putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
1971 return pToken;
1974 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokens(
1975 sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange, const ScAddress* pCurPos)
1977 if (pCurPos)
1978 insertRefCell(nFileId, *pCurPos);
1980 maybeLinkExternalFile(nFileId);
1982 ScRange aDataRange(rRange);
1983 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
1984 if (pSrcDoc)
1986 // Document already loaded in memory.
1987 vector<ScExternalRefCache::SingleRangeData> aCacheData;
1988 ScExternalRefCache::TokenArrayRef pArray =
1989 getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
1991 // Put the data into cache.
1992 putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
1993 return pArray;
1996 // Check if the given table name and the cell position is cached.
1997 ScExternalRefCache::TokenArrayRef pArray =
1998 maRefCache.getCellRangeData(nFileId, rTabName, rRange);
1999 if (pArray)
2000 // Cache hit !
2001 return pArray;
2003 pSrcDoc = getSrcDocument(nFileId);
2004 if (!pSrcDoc)
2006 // Source document is not reachable. Throw a reference error.
2007 pArray = std::make_shared<ScTokenArray>(*maRefCache.getFakeDoc());
2008 pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
2009 return pArray;
2012 vector<ScExternalRefCache::SingleRangeData> aCacheData;
2013 pArray = getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
2015 // Put the data into cache.
2016 putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
2017 return pArray;
2020 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokens(
2021 sal_uInt16 nFileId, const OUString& rName, const ScAddress* pCurPos)
2023 if (pCurPos)
2024 insertRefCell(nFileId, *pCurPos);
2026 maybeLinkExternalFile(nFileId);
2028 OUString aName = rName; // make a copy to have the casing corrected.
2029 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
2030 if (pSrcDoc)
2032 // Document already loaded in memory.
2033 ScExternalRefCache::TokenArrayRef pArray =
2034 getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
2036 if (pArray)
2037 // Cache this range name array.
2038 maRefCache.setRangeNameTokens(nFileId, aName, pArray);
2040 return pArray;
2043 ScExternalRefCache::TokenArrayRef pArray = maRefCache.getRangeNameTokens(nFileId, rName);
2044 if (pArray)
2045 // This range name is cached.
2046 return pArray;
2048 pSrcDoc = getSrcDocument(nFileId);
2049 if (!pSrcDoc)
2050 // failed to load document from disk.
2051 return ScExternalRefCache::TokenArrayRef();
2053 pArray = getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
2055 if (pArray)
2056 // Cache this range name array.
2057 maRefCache.setRangeNameTokens(nFileId, aName, pArray);
2059 return pArray;
2062 namespace {
2064 bool hasRangeName(const ScDocument& rDoc, const OUString& rName)
2066 ScRangeName* pExtNames = rDoc.GetRangeName();
2067 OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(rName);
2068 const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
2069 return pRangeData != nullptr;
2074 bool ScExternalRefManager::isValidRangeName(sal_uInt16 nFileId, const OUString& rName)
2076 maybeLinkExternalFile(nFileId);
2077 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
2078 if (pSrcDoc)
2080 // Only check the presence of the name.
2081 if (hasRangeName(*pSrcDoc, rName))
2083 maRefCache.setRangeName(nFileId, rName);
2084 return true;
2086 return false;
2089 if (maRefCache.isValidRangeName(nFileId, rName))
2090 // Range name is cached.
2091 return true;
2093 pSrcDoc = getSrcDocument(nFileId);
2094 if (!pSrcDoc)
2095 // failed to load document from disk.
2096 return false;
2098 if (hasRangeName(*pSrcDoc, rName))
2100 maRefCache.setRangeName(nFileId, rName);
2101 return true;
2104 return false;
2107 void ScExternalRefManager::refreshAllRefCells(sal_uInt16 nFileId)
2109 RefCellMap::iterator itrFile = maRefCells.find(nFileId);
2110 if (itrFile == maRefCells.end())
2111 return;
2113 RefCellSet& rRefCells = itrFile->second;
2114 for_each(rRefCells.begin(), rRefCells.end(), UpdateFormulaCell());
2116 ScViewData* pViewData = ScDocShell::GetViewData();
2117 if (!pViewData)
2118 return;
2120 ScTabViewShell* pVShell = pViewData->GetViewShell();
2121 if (!pVShell)
2122 return;
2124 // Repainting the grid also repaints the texts, but is there a better way
2125 // to refresh texts?
2126 pVShell->Invalidate(FID_REPAINT);
2127 pVShell->PaintGrid();
2130 namespace {
2132 void insertRefCellByIterator(
2133 const ScExternalRefManager::RefCellMap::iterator& itr, ScFormulaCell* pCell)
2135 if (pCell)
2137 itr->second.insert(pCell);
2138 pCell->SetIsExtRef();
2144 void ScExternalRefManager::insertRefCell(sal_uInt16 nFileId, const ScAddress& rCell)
2146 RefCellMap::iterator itr = maRefCells.find(nFileId);
2147 if (itr == maRefCells.end())
2149 RefCellSet aRefCells;
2150 pair<RefCellMap::iterator, bool> r = maRefCells.emplace(
2151 nFileId, aRefCells);
2152 if (!r.second)
2153 // insertion failed.
2154 return;
2156 itr = r.first;
2159 insertRefCellByIterator(itr, mrDoc.GetFormulaCell(rCell));
2162 void ScExternalRefManager::insertRefCellFromTemplate( ScFormulaCell* pTemplateCell, ScFormulaCell* pCell )
2164 if (!pTemplateCell || !pCell)
2165 return;
2167 for (RefCellMap::iterator itr = maRefCells.begin(); itr != maRefCells.end(); ++itr)
2169 if (itr->second.find(pTemplateCell) != itr->second.end())
2170 insertRefCellByIterator(itr, pCell);
2174 bool ScExternalRefManager::hasCellExternalReference(const ScAddress& rCell)
2176 ScFormulaCell* pCell = mrDoc.GetFormulaCell(rCell);
2178 if (pCell)
2179 return std::any_of(maRefCells.begin(), maRefCells.end(),
2180 [&pCell](const RefCellMap::value_type& rEntry) { return rEntry.second.find(pCell) != rEntry.second.end(); });
2182 return false;
2185 void ScExternalRefManager::enableDocTimer( bool bEnable )
2187 if (mbDocTimerEnabled == bEnable)
2188 return;
2190 mbDocTimerEnabled = bEnable;
2191 if (mbDocTimerEnabled)
2193 if (!maDocShells.empty())
2195 for (auto& rEntry : maDocShells)
2196 rEntry.second.maLastAccess = tools::Time(tools::Time::SYSTEM);
2198 maSrcDocTimer.Start();
2201 else
2202 maSrcDocTimer.Stop();
2205 void ScExternalRefManager::fillCellFormat(sal_uLong nFmtIndex, ScExternalRefCache::CellFormat* pFmt) const
2207 if (!pFmt)
2208 return;
2210 SvNumFormatType nFmtType = mrDoc.GetFormatTable()->GetType(nFmtIndex);
2211 if (nFmtType != SvNumFormatType::UNDEFINED)
2213 pFmt->mbIsSet = true;
2214 pFmt->mnIndex = nFmtIndex;
2215 pFmt->mnType = nFmtType;
2219 ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefTokenFromSrcDoc(
2220 sal_uInt16 nFileId, ScDocument& rSrcDoc, const ScAddress& rPos,
2221 ScExternalRefCache::CellFormat* pFmt)
2223 // Get the cell from src doc, and convert it into a token.
2224 ScRefCellValue aCell(rSrcDoc, rPos);
2225 ScExternalRefCache::TokenRef pToken(convertToToken(mrDoc, rSrcDoc, aCell));
2227 if (!pToken)
2229 // Generate an error for unresolvable cells.
2230 pToken.reset( new FormulaErrorToken( FormulaError::NoValue));
2233 // Get number format information.
2234 sal_uInt32 nFmtIndex = 0;
2235 rSrcDoc.GetNumberFormat(rPos.Col(), rPos.Row(), rPos.Tab(), nFmtIndex);
2236 nFmtIndex = getMappedNumberFormat(nFileId, nFmtIndex, rSrcDoc);
2237 fillCellFormat(nFmtIndex, pFmt);
2238 return pToken;
2241 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokensFromSrcDoc(
2242 const ScDocument& rSrcDoc, const OUString& rTabName, ScRange& rRange,
2243 vector<ScExternalRefCache::SingleRangeData>& rCacheData)
2245 ScExternalRefCache::TokenArrayRef pArray;
2246 SCTAB nTab1;
2248 if (!rSrcDoc.GetTable(rTabName, nTab1))
2250 // specified table name doesn't exist in the source document.
2251 pArray = std::make_shared<ScTokenArray>(rSrcDoc);
2252 pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
2253 return pArray;
2256 ScRange aRange(rRange);
2257 aRange.PutInOrder();
2258 SCTAB nTabSpan = aRange.aEnd.Tab() - aRange.aStart.Tab();
2260 vector<ScExternalRefCache::SingleRangeData> aCacheData;
2261 aCacheData.reserve(nTabSpan+1);
2262 aCacheData.emplace_back();
2263 aCacheData.back().maTableName = ScGlobal::getCharClassPtr()->uppercase(rTabName);
2265 for (SCTAB i = 1; i < nTabSpan + 1; ++i)
2267 OUString aTabName;
2268 if (!rSrcDoc.GetName(nTab1 + 1, aTabName))
2269 // source document doesn't have any table by the specified name.
2270 break;
2272 aCacheData.emplace_back();
2273 aCacheData.back().maTableName = ScGlobal::getCharClassPtr()->uppercase(aTabName);
2276 aRange.aStart.SetTab(nTab1);
2277 aRange.aEnd.SetTab(nTab1 + nTabSpan);
2279 pArray = convertToTokenArray(mrDoc, rSrcDoc, aRange, aCacheData);
2280 rRange = aRange;
2281 rCacheData.swap(aCacheData);
2282 return pArray;
2285 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokensFromSrcDoc(
2286 sal_uInt16 nFileId, const ScDocument& rSrcDoc, OUString& rName)
2288 ScRangeName* pExtNames = rSrcDoc.GetRangeName();
2289 OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(rName);
2290 const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
2291 if (!pRangeData)
2292 return ScExternalRefCache::TokenArrayRef();
2294 // Parse all tokens in this external range data, and replace each absolute
2295 // reference token with an external reference token, and cache them. Also
2296 // register the source document with the link manager if it's a new
2297 // source.
2299 ScExternalRefCache::TokenArrayRef pNew = std::make_shared<ScTokenArray>(rSrcDoc);
2301 ScTokenArray aCode(*pRangeData->GetCode());
2302 FormulaTokenArrayPlainIterator aIter(aCode);
2303 for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next())
2305 bool bTokenAdded = false;
2306 switch (pToken->GetType())
2308 case svSingleRef:
2310 const ScSingleRefData& rRef = *pToken->GetSingleRef();
2311 OUString aTabName;
2312 rSrcDoc.GetName(rRef.Tab(), aTabName);
2313 ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned
2314 *pToken->GetSingleRef());
2315 pNew->AddToken(aNewToken);
2316 bTokenAdded = true;
2318 break;
2319 case svDoubleRef:
2321 const ScSingleRefData& rRef = *pToken->GetSingleRef();
2322 OUString aTabName;
2323 rSrcDoc.GetName(rRef.Tab(), aTabName);
2324 ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned
2325 *pToken->GetDoubleRef());
2326 pNew->AddToken(aNewToken);
2327 bTokenAdded = true;
2329 break;
2330 default:
2331 ; // nothing
2334 if (!bTokenAdded)
2335 pNew->AddToken(*pToken);
2338 rName = pRangeData->GetName(); // Get the correctly-cased name.
2339 return pNew;
2342 ScDocument* ScExternalRefManager::getInMemorySrcDocument(sal_uInt16 nFileId)
2344 const OUString* pFileName = getExternalFileName(nFileId);
2345 if (!pFileName)
2346 return nullptr;
2348 // Do not load document until it was allowed.
2349 if (!isLinkUpdateAllowedInDoc(mrDoc))
2350 return nullptr;
2352 ScDocument* pSrcDoc = nullptr;
2353 ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
2354 while (pShell)
2356 SfxMedium* pMedium = pShell->GetMedium();
2357 if (pMedium && !pMedium->GetName().isEmpty())
2359 // TODO: We should make the case sensitivity platform dependent.
2360 if (pFileName->equalsIgnoreAsciiCase(pMedium->GetName()))
2362 // Found !
2363 pSrcDoc = &pShell->GetDocument();
2364 break;
2367 else
2369 // handle unsaved documents here
2370 OUString aName = pShell->GetName();
2371 if (pFileName->equalsIgnoreAsciiCase(aName))
2373 // Found !
2374 SrcShell aSrcDoc;
2375 aSrcDoc.maShell = pShell;
2376 maUnsavedDocShells.emplace(nFileId, aSrcDoc);
2377 StartListening(*pShell);
2378 pSrcDoc = &pShell->GetDocument();
2379 break;
2382 pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
2385 initDocInCache(maRefCache, pSrcDoc, nFileId);
2386 return pSrcDoc;
2389 ScDocument* ScExternalRefManager::getSrcDocument(sal_uInt16 nFileId)
2391 if (!mrDoc.IsExecuteLinkEnabled())
2392 return nullptr;
2394 DocShellMap::iterator itrEnd = maDocShells.end();
2395 DocShellMap::iterator itr = maDocShells.find(nFileId);
2397 if (itr != itrEnd)
2399 // document already loaded.
2401 SfxObjectShell* p = itr->second.maShell.get();
2402 itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
2403 return &static_cast<ScDocShell*>(p)->GetDocument();
2406 itrEnd = maUnsavedDocShells.end();
2407 itr = maUnsavedDocShells.find(nFileId);
2408 if (itr != itrEnd)
2410 //document is unsaved document
2412 SfxObjectShell* p = itr->second.maShell.get();
2413 itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
2414 return &static_cast<ScDocShell*>(p)->GetDocument();
2417 const OUString* pFile = getExternalFileName(nFileId);
2418 if (!pFile)
2419 // no file name associated with this ID.
2420 return nullptr;
2422 SrcShell aSrcDoc;
2425 OUString aFilter;
2426 aSrcDoc.maShell = loadSrcDocument(nFileId, aFilter);
2428 catch (const css::uno::Exception&)
2431 if (!aSrcDoc.maShell.is())
2433 // source document could not be loaded.
2434 return nullptr;
2437 return &cacheNewDocShell(nFileId, aSrcDoc);
2440 SfxObjectShellRef ScExternalRefManager::loadSrcDocument(sal_uInt16 nFileId, OUString& rFilter)
2442 // Do not load document until it was allowed.
2443 if (!isLinkUpdateAllowedInDoc(mrDoc))
2444 return nullptr;
2446 const SrcFileData* pFileData = getExternalFileData(nFileId);
2447 if (!pFileData)
2448 return nullptr;
2450 // Always load the document by using the path created from the relative
2451 // path. If the referenced document is not there, simply exit. The
2452 // original file name should be used only when the relative path is not
2453 // given.
2454 OUString aFile = pFileData->maFileName;
2455 maybeCreateRealFileName(nFileId);
2456 if (!pFileData->maRealFileName.isEmpty())
2457 aFile = pFileData->maRealFileName;
2459 if (!isFileLoadable(aFile))
2460 return nullptr;
2462 OUString aOptions = pFileData->maFilterOptions;
2463 if ( !pFileData->maFilterName.isEmpty() )
2464 rFilter = pFileData->maFilterName; // don't overwrite stored filter with guessed filter
2465 else
2466 ScDocumentLoader::GetFilterName(aFile, rFilter, aOptions, true, false);
2467 std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName(rFilter);
2469 if (pFileData->maRelativeName.isEmpty())
2471 // Generate a relative file path.
2472 INetURLObject aBaseURL(getOwnDocumentName());
2473 aBaseURL.insertName("content.xml");
2475 OUString aStr = URIHelper::simpleNormalizedMakeRelative(
2476 aBaseURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), aFile);
2478 setRelativeFileName(nFileId, aStr);
2481 std::unique_ptr<SfxItemSet> pSet(new SfxAllItemSet(SfxGetpApp()->GetPool()));
2482 if (!aOptions.isEmpty())
2483 pSet->Put(SfxStringItem(SID_FILE_FILTEROPTIONS, aOptions));
2485 // make medium hidden to prevent assertion from progress bar
2486 pSet->Put( SfxBoolItem(SID_HIDDEN, true) );
2488 // If the current document is allowed to execute macros then the referenced
2489 // document may execute macros according to the security configuration.
2490 // Similar for UpdateDocMode to update links, just that if we reach here
2491 // the user already allowed updates and intermediate documents are expected
2492 // to update as well. When loading the document ScDocShell::Load() will
2493 // check through ScDocShell::GetLinkUpdateModeState() if its location is
2494 // trusted.
2495 SfxObjectShell* pShell = mrDoc.GetDocumentShell();
2496 if (pShell)
2498 SfxMedium* pMedium = pShell->GetMedium();
2499 if (pMedium)
2501 const SfxPoolItem* pItem;
2502 if (pMedium->GetItemSet()->GetItemState( SID_MACROEXECMODE, false, &pItem ) == SfxItemState::SET &&
2503 static_cast<const SfxUInt16Item*>(pItem)->GetValue() != css::document::MacroExecMode::NEVER_EXECUTE)
2504 pSet->Put( SfxUInt16Item( SID_MACROEXECMODE, css::document::MacroExecMode::USE_CONFIG));
2507 pSet->Put( SfxUInt16Item( SID_UPDATEDOCMODE, css::document::UpdateDocMode::FULL_UPDATE));
2510 unique_ptr<SfxMedium> pMedium(new SfxMedium(aFile, StreamMode::STD_READ, pFilter, std::move(pSet)));
2511 if (pMedium->GetError() != ERRCODE_NONE)
2512 return nullptr;
2514 // To load encrypted documents with password, user interaction needs to be enabled.
2515 pMedium->UseInteractionHandler(mbUserInteractionEnabled);
2517 ScDocShell* pNewShell = new ScDocShell(SfxModelFlags::EXTERNAL_LINK);
2518 SfxObjectShellRef aRef = pNewShell;
2520 // increment the recursive link count of the source document.
2521 ScExtDocOptions* pExtOpt = mrDoc.GetExtDocOptions();
2522 sal_uInt32 nLinkCount = pExtOpt ? pExtOpt->GetDocSettings().mnLinkCnt : 0;
2523 ScDocument& rSrcDoc = pNewShell->GetDocument();
2524 rSrcDoc.EnableExecuteLink(false); // to prevent circular access of external references.
2525 rSrcDoc.EnableUndo(false);
2526 rSrcDoc.LockAdjustHeight();
2527 rSrcDoc.EnableUserInteraction(false);
2529 ScExtDocOptions* pExtOptNew = rSrcDoc.GetExtDocOptions();
2530 if (!pExtOptNew)
2532 rSrcDoc.SetExtDocOptions(std::make_unique<ScExtDocOptions>());
2533 pExtOptNew = rSrcDoc.GetExtDocOptions();
2535 pExtOptNew->GetDocSettings().mnLinkCnt = nLinkCount + 1;
2537 if (!pNewShell->DoLoad(pMedium.release()))
2539 aRef->DoClose();
2540 aRef.clear();
2541 return aRef;
2544 // with UseInteractionHandler, options may be set by dialog during DoLoad
2545 OUString aNew = ScDocumentLoader::GetOptions(*pNewShell->GetMedium());
2546 if (!aNew.isEmpty() && aNew != aOptions)
2547 aOptions = aNew;
2548 setFilterData(nFileId, rFilter, aOptions); // update the filter data, including the new options
2550 return aRef;
2553 ScDocument& ScExternalRefManager::cacheNewDocShell( sal_uInt16 nFileId, SrcShell& rSrcShell )
2555 if (mbDocTimerEnabled && maDocShells.empty())
2556 // If this is the first source document insertion, start up the timer.
2557 maSrcDocTimer.Start();
2559 maDocShells.emplace(nFileId, rSrcShell);
2560 SfxObjectShell& rShell = *rSrcShell.maShell;
2561 ScDocument& rSrcDoc = static_cast<ScDocShell&>(rShell).GetDocument();
2562 initDocInCache(maRefCache, &rSrcDoc, nFileId);
2563 return rSrcDoc;
2566 bool ScExternalRefManager::isFileLoadable(const OUString& rFile) const
2568 if (rFile.isEmpty())
2569 return false;
2571 if (isOwnDocument(rFile))
2572 return false;
2573 OUString aPhysical;
2574 if (osl::FileBase::getSystemPathFromFileURL(rFile, aPhysical)
2575 == osl::FileBase::E_None)
2577 // #i114504# try IsFolder/Exists only for file URLs
2579 if (utl::UCBContentHelper::IsFolder(rFile))
2580 return false;
2582 return utl::UCBContentHelper::Exists(rFile);
2584 else
2585 return true; // for http and others, Exists doesn't work, but the URL can still be opened
2588 void ScExternalRefManager::maybeLinkExternalFile( sal_uInt16 nFileId, bool bDeferFilterDetection )
2590 if (maLinkedDocs.count(nFileId))
2591 // file already linked, or the link has been broken.
2592 return;
2594 // Source document not linked yet. Link it now.
2595 const OUString* pFileName = getExternalFileName(nFileId);
2596 if (!pFileName)
2597 return;
2599 OUString aFilter, aOptions;
2600 const SrcFileData* pFileData = getExternalFileData(nFileId);
2601 if (pFileData)
2603 aFilter = pFileData->maFilterName;
2604 aOptions = pFileData->maFilterOptions;
2607 // Filter detection may access external links; defer it until we are allowed.
2608 if (!bDeferFilterDetection)
2609 bDeferFilterDetection = !isLinkUpdateAllowedInDoc(mrDoc);
2611 // If a filter was already set (for example, loading the cached table),
2612 // don't call GetFilterName which has to access the source file.
2613 // If filter detection is deferred, the next successful loadSrcDocument()
2614 // will update SrcFileData filter name.
2615 if (aFilter.isEmpty() && !bDeferFilterDetection)
2616 ScDocumentLoader::GetFilterName(*pFileName, aFilter, aOptions, true, false);
2617 sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager();
2618 if (!pLinkMgr)
2620 SAL_WARN( "sc.ui", "ScExternalRefManager::maybeLinkExternalFile: pLinkMgr==NULL");
2621 return;
2623 ScExternalRefLink* pLink = new ScExternalRefLink(mrDoc, nFileId);
2624 OSL_ENSURE(pFileName, "ScExternalRefManager::maybeLinkExternalFile: file name pointer is NULL");
2625 pLinkMgr->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, *pFileName,
2626 (aFilter.isEmpty() && bDeferFilterDetection ? nullptr : &aFilter));
2628 pLink->SetDoRefresh(false);
2629 pLink->Update();
2630 pLink->SetDoRefresh(true);
2632 maLinkedDocs.emplace(nFileId, true);
2635 void ScExternalRefManager::addFilesToLinkManager()
2637 if (maSrcFiles.empty())
2638 return;
2640 SAL_WARN_IF( maSrcFiles.size() >= SAL_MAX_UINT16,
2641 "sc.ui", "ScExternalRefManager::addFilesToLinkManager: files overflow");
2642 const sal_uInt16 nSize = static_cast<sal_uInt16>( std::min<size_t>( maSrcFiles.size(), SAL_MAX_UINT16));
2643 for (sal_uInt16 nFileId = 0; nFileId < nSize; ++nFileId)
2644 maybeLinkExternalFile( nFileId, true);
2647 void ScExternalRefManager::SrcFileData::maybeCreateRealFileName(const OUString& rOwnDocName)
2649 if (maRelativeName.isEmpty())
2650 // No relative path given. Nothing to do.
2651 return;
2653 if (!maRealFileName.isEmpty())
2654 // Real file name already created. Nothing to do.
2655 return;
2657 // Formulate the absolute file path from the relative path.
2658 const OUString& rRelPath = maRelativeName;
2659 INetURLObject aBaseURL(rOwnDocName);
2660 aBaseURL.insertName("content.xml");
2661 bool bWasAbs = false;
2662 maRealFileName = aBaseURL.smartRel2Abs(rRelPath, bWasAbs).GetMainURL(INetURLObject::DecodeMechanism::NONE);
2665 void ScExternalRefManager::maybeCreateRealFileName(sal_uInt16 nFileId)
2667 if (nFileId >= maSrcFiles.size())
2668 return;
2670 maSrcFiles[nFileId].maybeCreateRealFileName(getOwnDocumentName());
2673 OUString ScExternalRefManager::getOwnDocumentName() const
2675 if (utl::ConfigManager::IsFuzzing())
2676 return "file:///tmp/document";
2678 SfxObjectShell* pShell = mrDoc.GetDocumentShell();
2679 if (!pShell)
2680 // This should not happen!
2681 return OUString();
2683 SfxMedium* pMed = pShell->GetMedium();
2684 if (!pMed)
2685 return OUString();
2687 return pMed->GetName();
2690 bool ScExternalRefManager::isOwnDocument(const OUString& rFile) const
2692 return getOwnDocumentName() == rFile;
2695 void ScExternalRefManager::convertToAbsName(OUString& rFile) const
2697 // unsaved documents have no AbsName
2698 ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
2699 while (pShell)
2701 if (rFile == pShell->GetName())
2702 return;
2704 pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
2707 SfxObjectShell* pDocShell = mrDoc.GetDocumentShell();
2708 rFile = ScGlobal::GetAbsDocName(rFile, pDocShell);
2711 sal_uInt16 ScExternalRefManager::getExternalFileId(const OUString& rFile)
2713 vector<SrcFileData>::const_iterator itrBeg = maSrcFiles.begin(), itrEnd = maSrcFiles.end();
2714 vector<SrcFileData>::const_iterator itr = find_if(itrBeg, itrEnd, FindSrcFileByName(rFile));
2715 if (itr != itrEnd)
2717 size_t nId = distance(itrBeg, itr);
2718 return static_cast<sal_uInt16>(nId);
2721 SrcFileData aData;
2722 aData.maFileName = rFile;
2723 maSrcFiles.push_back(aData);
2724 return static_cast<sal_uInt16>(maSrcFiles.size() - 1);
2727 const OUString* ScExternalRefManager::getExternalFileName(sal_uInt16 nFileId, bool bForceOriginal)
2729 if (nFileId >= maSrcFiles.size())
2730 return nullptr;
2732 if (bForceOriginal)
2733 return &maSrcFiles[nFileId].maFileName;
2735 maybeCreateRealFileName(nFileId);
2737 if (!maSrcFiles[nFileId].maRealFileName.isEmpty())
2738 return &maSrcFiles[nFileId].maRealFileName;
2740 return &maSrcFiles[nFileId].maFileName;
2743 sal_uInt16 ScExternalRefManager::convertFileIdToUsedFileId(sal_uInt16 nFileId)
2745 if (!mbSkipUnusedFileIds)
2746 return nFileId;
2747 else
2748 return maConvertFileIdToUsedFileId[nFileId];
2751 void ScExternalRefManager::setSkipUnusedFileIds(std::vector<sal_uInt16>& rExternFileIds)
2753 mbSkipUnusedFileIds = true;
2754 maConvertFileIdToUsedFileId.resize(maSrcFiles.size());
2755 std::fill(maConvertFileIdToUsedFileId.begin(), maConvertFileIdToUsedFileId.end(), 0);
2756 int nUsedCount = 0;
2757 for (auto nEntry : rExternFileIds)
2759 maConvertFileIdToUsedFileId[nEntry] = nUsedCount++;
2763 void ScExternalRefManager::disableSkipUnusedFileIds()
2765 mbSkipUnusedFileIds = false;
2768 std::vector<OUString> ScExternalRefManager::getAllCachedExternalFileNames() const
2770 std::vector<OUString> aNames;
2771 aNames.reserve(maSrcFiles.size());
2772 for (const SrcFileData& rData : maSrcFiles)
2774 aNames.push_back(rData.maFileName);
2777 return aNames;
2780 bool ScExternalRefManager::hasExternalFile(sal_uInt16 nFileId) const
2782 return nFileId < maSrcFiles.size();
2785 bool ScExternalRefManager::hasExternalFile(const OUString& rFile) const
2787 return ::std::any_of(maSrcFiles.begin(), maSrcFiles.end(), FindSrcFileByName(rFile));
2790 const ScExternalRefManager::SrcFileData* ScExternalRefManager::getExternalFileData(sal_uInt16 nFileId) const
2792 if (nFileId >= maSrcFiles.size())
2793 return nullptr;
2795 return &maSrcFiles[nFileId];
2798 const OUString* ScExternalRefManager::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
2800 return maRefCache.getRealTableName(nFileId, rTabName);
2803 const OUString* ScExternalRefManager::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
2805 return maRefCache.getRealRangeName(nFileId, rRangeName);
2808 template<typename MapContainer>
2809 static void lcl_removeByFileId(sal_uInt16 nFileId, MapContainer& rMap)
2811 typename MapContainer::iterator itr = rMap.find(nFileId);
2812 if (itr != rMap.end())
2814 // Close this document shell.
2815 itr->second.maShell->DoClose();
2816 rMap.erase(itr);
2820 void ScExternalRefManager::clearCache(sal_uInt16 nFileId)
2822 maRefCache.clearCache(nFileId);
2825 namespace {
2827 class RefCacheFiller : public sc::ColumnSpanSet::ColumnAction
2829 svl::SharedStringPool& mrStrPool;
2831 ScExternalRefCache& mrRefCache;
2832 ScExternalRefCache::TableTypeRef mpRefTab;
2833 sal_uInt16 mnFileId;
2834 ScColumn* mpCurCol;
2835 sc::ColumnBlockConstPosition maBlockPos;
2837 public:
2838 RefCacheFiller( svl::SharedStringPool& rStrPool, ScExternalRefCache& rRefCache, sal_uInt16 nFileId ) :
2839 mrStrPool(rStrPool), mrRefCache(rRefCache), mnFileId(nFileId), mpCurCol(nullptr) {}
2841 virtual void startColumn( ScColumn* pCol ) override
2843 mpCurCol = pCol;
2844 if (!mpCurCol)
2845 return;
2847 mpCurCol->InitBlockPosition(maBlockPos);
2848 mpRefTab = mrRefCache.getCacheTable(mnFileId, mpCurCol->GetTab());
2851 virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
2853 if (!mpCurCol || !bVal)
2854 return;
2856 if (!mpRefTab)
2857 return;
2859 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
2861 ScExternalRefCache::TokenRef pTok;
2862 ScRefCellValue aCell = mpCurCol->GetCellValue(maBlockPos, nRow);
2863 switch (aCell.meType)
2865 case CELLTYPE_STRING:
2866 case CELLTYPE_EDIT:
2868 OUString aStr = aCell.getString(&mpCurCol->GetDoc());
2869 svl::SharedString aSS = mrStrPool.intern(aStr);
2870 pTok.reset(new formula::FormulaStringToken(aSS));
2872 break;
2873 case CELLTYPE_VALUE:
2874 pTok.reset(new formula::FormulaDoubleToken(aCell.mfValue));
2875 break;
2876 case CELLTYPE_FORMULA:
2878 sc::FormulaResultValue aRes = aCell.mpFormula->GetResult();
2879 switch (aRes.meType)
2881 case sc::FormulaResultValue::Value:
2882 pTok.reset(new formula::FormulaDoubleToken(aRes.mfValue));
2883 break;
2884 case sc::FormulaResultValue::String:
2886 // Re-intern the string to the host document pool.
2887 svl::SharedString aInterned = mrStrPool.intern(aRes.maString.getString());
2888 pTok.reset(new formula::FormulaStringToken(aInterned));
2890 break;
2891 case sc::FormulaResultValue::Error:
2892 case sc::FormulaResultValue::Invalid:
2893 default:
2894 pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
2897 break;
2898 default:
2899 pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
2902 if (pTok)
2904 // Cache this cell.
2905 mpRefTab->setCell(mpCurCol->GetCol(), nRow, pTok, mpCurCol->GetNumberFormat(mpCurCol->GetDoc().GetNonThreadedContext(), nRow));
2906 mpRefTab->setCachedCell(mpCurCol->GetCol(), nRow);
2914 bool ScExternalRefManager::refreshSrcDocument(sal_uInt16 nFileId)
2916 SfxObjectShellRef xDocShell;
2919 OUString aFilter;
2920 xDocShell = loadSrcDocument(nFileId, aFilter);
2922 catch ( const css::uno::Exception& ) {}
2924 if (!xDocShell.is())
2925 // Failed to load the document. Bail out.
2926 return false;
2928 ScDocShell& rDocSh = static_cast<ScDocShell&>(*xDocShell);
2929 ScDocument& rSrcDoc = rDocSh.GetDocument();
2931 sc::ColumnSpanSet aCachedArea;
2932 maRefCache.getAllCachedDataSpans(rSrcDoc, nFileId, aCachedArea);
2934 // Clear the existing cache, and refill it. Make sure we keep the
2935 // existing cache table instances here.
2936 maRefCache.clearCacheTables(nFileId);
2937 RefCacheFiller aAction(mrDoc.GetSharedStringPool(), maRefCache, nFileId);
2938 aCachedArea.executeColumnAction(rSrcDoc, aAction);
2940 DocShellMap::iterator it = maDocShells.find(nFileId);
2941 if (it != maDocShells.end())
2943 it->second.maShell->DoClose();
2944 it->second.maShell = xDocShell;
2945 it->second.maLastAccess = tools::Time(tools::Time::SYSTEM);
2947 else
2949 SrcShell aSrcDoc;
2950 aSrcDoc.maShell = xDocShell;
2951 aSrcDoc.maLastAccess = tools::Time(tools::Time::SYSTEM);
2952 cacheNewDocShell(nFileId, aSrcDoc);
2955 // Update all cells containing names from this source document.
2956 refreshAllRefCells(nFileId);
2958 notifyAllLinkListeners(nFileId, LINK_MODIFIED);
2960 return true;
2963 void ScExternalRefManager::breakLink(sal_uInt16 nFileId)
2965 // Turn all formula cells referencing this external document into static
2966 // cells.
2967 RefCellMap::iterator itrRefs = maRefCells.find(nFileId);
2968 if (itrRefs != maRefCells.end())
2970 // Make a copy because removing the formula cells below will modify
2971 // the original container.
2972 RefCellSet aSet = itrRefs->second;
2973 for_each(aSet.begin(), aSet.end(), ConvertFormulaToStatic(&mrDoc));
2974 maRefCells.erase(nFileId);
2977 // Remove all named ranges that reference this document.
2979 // Global named ranges.
2980 ScRangeName* pRanges = mrDoc.GetRangeName();
2981 if (pRanges)
2982 removeRangeNamesBySrcDoc(*pRanges, nFileId);
2984 // Sheet-local named ranges.
2985 for (SCTAB i = 0, n = mrDoc.GetTableCount(); i < n; ++i)
2987 pRanges = mrDoc.GetRangeName(i);
2988 if (pRanges)
2989 removeRangeNamesBySrcDoc(*pRanges, nFileId);
2992 clearCache(nFileId);
2993 lcl_removeByFileId(nFileId, maDocShells);
2995 if (maDocShells.empty())
2996 maSrcDocTimer.Stop();
2998 LinkedDocMap::iterator itr = maLinkedDocs.find(nFileId);
2999 if (itr != maLinkedDocs.end())
3000 itr->second = false;
3002 notifyAllLinkListeners(nFileId, LINK_BROKEN);
3005 void ScExternalRefManager::switchSrcFile(sal_uInt16 nFileId, const OUString& rNewFile, const OUString& rNewFilter)
3007 maSrcFiles[nFileId].maFileName = rNewFile;
3008 maSrcFiles[nFileId].maRelativeName.clear();
3009 maSrcFiles[nFileId].maRealFileName.clear();
3010 if (maSrcFiles[nFileId].maFilterName != rNewFilter)
3012 // Filter type has changed.
3013 maSrcFiles[nFileId].maFilterName = rNewFilter;
3014 maSrcFiles[nFileId].maFilterOptions.clear();
3016 refreshSrcDocument(nFileId);
3019 void ScExternalRefManager::setRelativeFileName(sal_uInt16 nFileId, const OUString& rRelUrl)
3021 if (nFileId >= maSrcFiles.size())
3022 return;
3023 maSrcFiles[nFileId].maRelativeName = rRelUrl;
3026 void ScExternalRefManager::setFilterData(sal_uInt16 nFileId, const OUString& rFilterName, const OUString& rOptions)
3028 if (nFileId >= maSrcFiles.size())
3029 return;
3030 maSrcFiles[nFileId].maFilterName = rFilterName;
3031 maSrcFiles[nFileId].maFilterOptions = rOptions;
3034 void ScExternalRefManager::clear()
3036 for (auto& rEntry : maDocShells)
3037 rEntry.second.maShell->DoClose();
3039 maDocShells.clear();
3040 maSrcDocTimer.Stop();
3043 bool ScExternalRefManager::hasExternalData() const
3045 return !maSrcFiles.empty();
3048 void ScExternalRefManager::resetSrcFileData(const OUString& rBaseFileUrl)
3050 for (auto& rSrcFile : maSrcFiles)
3052 // Re-generate relative file name from the absolute file name.
3053 OUString aAbsName = rSrcFile.maRealFileName;
3054 if (aAbsName.isEmpty())
3055 aAbsName = rSrcFile.maFileName;
3057 rSrcFile.maRelativeName = URIHelper::simpleNormalizedMakeRelative(
3058 rBaseFileUrl, aAbsName);
3062 void ScExternalRefManager::updateAbsAfterLoad()
3064 OUString aOwn( getOwnDocumentName() );
3065 for (auto& rSrcFile : maSrcFiles)
3067 // update maFileName to the real file name,
3068 // to be called when the original name is no longer needed (after CompileXML)
3070 rSrcFile.maybeCreateRealFileName( aOwn );
3071 OUString aReal = rSrcFile.maRealFileName;
3072 if (!aReal.isEmpty())
3073 rSrcFile.maFileName = aReal;
3077 void ScExternalRefManager::removeRefCell(ScFormulaCell* pCell)
3079 for_each(maRefCells.begin(), maRefCells.end(), RemoveFormulaCell(pCell));
3082 void ScExternalRefManager::addLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
3084 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3085 if (itr == maLinkListeners.end())
3087 pair<LinkListenerMap::iterator, bool> r = maLinkListeners.emplace(
3088 nFileId, LinkListeners());
3089 if (!r.second)
3091 OSL_FAIL("insertion of new link listener list failed");
3092 return;
3095 itr = r.first;
3098 LinkListeners& rList = itr->second;
3099 rList.insert(pListener);
3102 void ScExternalRefManager::removeLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
3104 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3105 if (itr == maLinkListeners.end())
3106 // no listeners for a specified file.
3107 return;
3109 LinkListeners& rList = itr->second;
3110 rList.erase(pListener);
3112 if (rList.empty())
3113 // No more listeners for this file. Remove its entry.
3114 maLinkListeners.erase(itr);
3117 void ScExternalRefManager::removeLinkListener(LinkListener* pListener)
3119 for (auto& rEntry : maLinkListeners)
3120 rEntry.second.erase(pListener);
3123 void ScExternalRefManager::notifyAllLinkListeners(sal_uInt16 nFileId, LinkUpdateType eType)
3125 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3126 if (itr == maLinkListeners.end())
3127 // no listeners for a specified file.
3128 return;
3130 LinkListeners& rList = itr->second;
3131 for_each(rList.begin(), rList.end(), NotifyLinkListener(nFileId, eType));
3134 void ScExternalRefManager::purgeStaleSrcDocument(sal_Int32 nTimeOut)
3136 // To avoid potentially freezing Calc, we close one stale document at a time.
3137 DocShellMap::iterator itr = std::find_if(maDocShells.begin(), maDocShells.end(),
3138 [nTimeOut](const DocShellMap::value_type& rEntry) {
3139 // in 100th of a second.
3140 sal_Int32 nSinceLastAccess = (tools::Time( tools::Time::SYSTEM ) - rEntry.second.maLastAccess).GetTime();
3141 return nSinceLastAccess >= nTimeOut;
3143 if (itr != maDocShells.end())
3145 // Timed out. Let's close this.
3146 itr->second.maShell->DoClose();
3147 maDocShells.erase(itr);
3150 if (maDocShells.empty())
3151 maSrcDocTimer.Stop();
3154 sal_uInt32 ScExternalRefManager::getMappedNumberFormat(sal_uInt16 nFileId, sal_uInt32 nNumFmt, const ScDocument& rSrcDoc)
3156 NumFmtMap::iterator itr = maNumFormatMap.find(nFileId);
3157 if (itr == maNumFormatMap.end())
3159 // Number formatter map is not initialized for this external document.
3160 pair<NumFmtMap::iterator, bool> r = maNumFormatMap.emplace(
3161 nFileId, SvNumberFormatterMergeMap());
3163 if (!r.second)
3164 // insertion failed.
3165 return nNumFmt;
3167 itr = r.first;
3168 mrDoc.GetFormatTable()->MergeFormatter(*rSrcDoc.GetFormatTable());
3169 SvNumberFormatterMergeMap aMap = mrDoc.GetFormatTable()->ConvertMergeTableToMap();
3170 itr->second.swap(aMap);
3172 const SvNumberFormatterMergeMap& rMap = itr->second;
3173 SvNumberFormatterMergeMap::const_iterator itrNumFmt = rMap.find(nNumFmt);
3174 if (itrNumFmt != rMap.end())
3175 // mapped value found.
3176 return itrNumFmt->second;
3178 return nNumFmt;
3181 void ScExternalRefManager::transformUnsavedRefToSavedRef( SfxObjectShell* pShell )
3183 DocShellMap::iterator itr = maUnsavedDocShells.begin();
3184 while( itr != maUnsavedDocShells.end() )
3186 if ( itr->second.maShell.get() == pShell )
3188 // found that the shell is marked as unsaved
3189 OUString aFileURL = pShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
3190 switchSrcFile(itr->first, aFileURL, OUString());
3191 EndListening(*pShell);
3192 maUnsavedDocShells.erase(itr++);
3197 void ScExternalRefManager::Notify( SfxBroadcaster&, const SfxHint& rHint )
3199 const SfxEventHint* pEventHint = dynamic_cast<const SfxEventHint*>(&rHint);
3200 if ( !pEventHint )
3201 return;
3203 SfxEventHintId nEventId = pEventHint->GetEventId();
3204 switch ( nEventId )
3206 case SfxEventHintId::PrepareCloseDoc:
3208 std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
3209 VclMessageType::Warning, VclButtonsType::Ok,
3210 ScResId(STR_CLOSE_WITH_UNSAVED_REFS)));
3211 xWarn->run();
3213 break;
3214 case SfxEventHintId::SaveDocDone:
3215 case SfxEventHintId::SaveAsDocDone:
3217 SfxObjectShell* pObjShell = static_cast<const SfxEventHint&>( rHint ).GetObjShell();
3218 transformUnsavedRefToSavedRef(pObjShell);
3220 break;
3221 default:
3222 break;
3226 IMPL_LINK(ScExternalRefManager, TimeOutHdl, Timer*, pTimer, void)
3228 if (pTimer == &maSrcDocTimer)
3229 purgeStaleSrcDocument(SRCDOC_LIFE_SPAN);
3232 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */