tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / ui / docshell / externalrefmgr.cxx
bloba5b7fd9e6f494746bdce46f4dc52f1ce0e7d75a8
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 <utility>
31 #include <viewdata.hxx>
32 #include <tabvwsh.hxx>
33 #include <sc.hrc>
34 #include <globstr.hrc>
35 #include <scresid.hxx>
36 #include <cellvalue.hxx>
37 #include <defaultsoptions.hxx>
38 #include <scmod.hxx>
40 #include <o3tl/safeint.hxx>
41 #include <osl/diagnose.h>
42 #include <osl/file.hxx>
43 #include <sfx2/app.hxx>
44 #include <sfx2/docfile.hxx>
45 #include <sfx2/event.hxx>
46 #include <sfx2/fcontnr.hxx>
47 #include <sfx2/objsh.hxx>
48 #include <svl/itemset.hxx>
49 #include <svl/numformat.hxx>
50 #include <svl/stritem.hxx>
51 #include <svl/urihelper.hxx>
52 #include <svl/sharedstringpool.hxx>
53 #include <sfx2/linkmgr.hxx>
54 #include <tools/hostfilter.hxx>
55 #include <tools/urlobj.hxx>
56 #include <unotools/charclass.hxx>
57 #include <comphelper/configuration.hxx>
58 #include <unotools/ucbhelper.hxx>
59 #include <vcl/svapp.hxx>
60 #include <vcl/weld.hxx>
61 #include <stringutil.hxx>
62 #include <scmatrix.hxx>
63 #include <columnspanset.hxx>
64 #include <column.hxx>
65 #include <com/sun/star/document/MacroExecMode.hpp>
66 #include <com/sun/star/document/UpdateDocMode.hpp>
67 #include <sal/log.hxx>
69 #include <memory>
70 #include <algorithm>
72 using ::std::unique_ptr;
73 using ::com::sun::star::uno::Any;
74 using ::std::vector;
75 using ::std::find_if;
76 using ::std::for_each;
77 using ::std::distance;
78 using ::std::pair;
79 using namespace formula;
81 #define SRCDOC_LIFE_SPAN 30000 // 5 minutes (in 100th of a sec)
82 #define SRCDOC_SCAN_INTERVAL 1000*30 // every 30 seconds (in msec)
84 namespace {
86 class TabNameSearchPredicate
88 public:
89 explicit TabNameSearchPredicate(const OUString& rSearchName) :
90 maSearchName(ScGlobal::getCharClass().uppercase(rSearchName))
94 bool operator()(const ScExternalRefCache::TableName& rTabNameSet) const
96 // Ok, I'm doing case insensitive search here.
97 return rTabNameSet.maUpperName == maSearchName;
100 private:
101 OUString maSearchName;
104 class FindSrcFileByName
106 public:
107 explicit FindSrcFileByName(const OUString& rMatchName) :
108 mrMatchName(rMatchName)
112 bool operator()(const ScExternalRefManager::SrcFileData& rSrcData) const
114 return rSrcData.maFileName == mrMatchName;
117 private:
118 const OUString& mrMatchName;
121 class NotifyLinkListener
123 public:
124 NotifyLinkListener(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType) :
125 mnFileId(nFileId), meType(eType) {}
127 void operator() (ScExternalRefManager::LinkListener* p) const
129 p->notify(mnFileId, meType);
131 private:
132 sal_uInt16 mnFileId;
133 ScExternalRefManager::LinkUpdateType meType;
136 struct UpdateFormulaCell
138 void operator() (ScFormulaCell* pCell) const
140 // Check to make sure the cell really contains svExternal*.
141 // External names, external cell and range references all have a
142 // token of svExternal*. Additionally check for INDIRECT() that can be
143 // called with any constructed URI string.
144 ScTokenArray* pCode = pCell->GetCode();
145 if (!pCode->HasExternalRef() && !pCode->HasOpCode(ocIndirect))
146 return;
148 if (pCode->GetCodeError() != FormulaError::NONE)
150 // Clear the error code, or a cell with error won't get re-compiled.
151 pCode->SetCodeError(FormulaError::NONE);
152 pCell->SetCompile(true);
153 pCell->CompileTokenArray();
156 pCell->SetDirty();
160 class RemoveFormulaCell
162 public:
163 explicit RemoveFormulaCell(ScFormulaCell* p) : mpCell(p) {}
164 void operator() (pair<const sal_uInt16, ScExternalRefManager::RefCellSet>& r) const
166 r.second.erase(mpCell);
168 private:
169 ScFormulaCell* mpCell;
172 class ConvertFormulaToStatic
174 public:
175 explicit ConvertFormulaToStatic(ScDocument* pDoc) : mpDoc(pDoc) {}
176 void operator() (ScFormulaCell* pCell) const
178 ScAddress aPos = pCell->aPos;
180 // We don't check for empty cells because empty external cells are
181 // treated as having a value of 0.
183 if (pCell->IsValue())
185 // Turn this into value cell.
186 mpDoc->SetValue(aPos, pCell->GetValue());
188 else
190 // string cell otherwise.
191 ScSetStringParam aParam;
192 aParam.setTextInput();
193 mpDoc->SetString(aPos, pCell->GetString().getString(), &aParam);
196 private:
197 ScDocument* mpDoc;
201 * Check whether a named range contains an external reference to a
202 * particular document.
204 bool hasRefsToSrcDoc(ScRangeData& rData, sal_uInt16 nFileId)
206 ScTokenArray* pArray = rData.GetCode();
207 if (!pArray)
208 return false;
210 formula::FormulaTokenArrayPlainIterator aIter(*pArray);
211 formula::FormulaToken* p = aIter.GetNextReference();
212 for (; p; p = aIter.GetNextReference())
214 if (!p->IsExternalRef())
215 continue;
217 if (p->GetIndex() == nFileId)
218 return true;
220 return false;
223 class EraseRangeByIterator
225 ScRangeName& mrRanges;
226 public:
227 explicit EraseRangeByIterator(ScRangeName& rRanges) : mrRanges(rRanges) {}
228 void operator() (const ScRangeName::const_iterator& itr)
230 mrRanges.erase(itr);
235 * Remove all named ranges that contain references to specified source
236 * document.
238 void removeRangeNamesBySrcDoc(ScRangeName& rRanges, sal_uInt16 nFileId)
240 ScRangeName::const_iterator itr = rRanges.begin(), itrEnd = rRanges.end();
241 vector<ScRangeName::const_iterator> v;
242 for (; itr != itrEnd; ++itr)
244 if (hasRefsToSrcDoc(*itr->second, nFileId))
245 v.push_back(itr);
247 for_each(v.begin(), v.end(), EraseRangeByIterator(rRanges));
252 ScExternalRefCache::Table::Table()
253 : mbReferenced( true )
254 // Prevent accidental data loss due to lack of knowledge.
258 ScExternalRefCache::Table::~Table()
262 void ScExternalRefCache::Table::clear()
264 maRows.clear();
265 maCachedRanges.RemoveAll();
266 mbReferenced = true;
269 void ScExternalRefCache::Table::setReferenced( bool bReferenced )
271 mbReferenced = bReferenced;
274 bool ScExternalRefCache::Table::isReferenced() const
276 return mbReferenced;
279 void ScExternalRefCache::Table::setCell(SCCOL nCol, SCROW nRow, TokenRef const & pToken, sal_uLong nFmtIndex, bool bSetCacheRange)
281 using ::std::pair;
282 RowsDataType::iterator itrRow = maRows.find(nRow);
283 if (itrRow == maRows.end())
285 // This row does not exist yet.
286 pair<RowsDataType::iterator, bool> res = maRows.emplace(
287 nRow, RowDataType());
289 if (!res.second)
290 return;
292 itrRow = res.first;
295 // Insert this token into the specified column location. I don't need to
296 // check for existing data. Just overwrite it.
297 RowDataType& rRow = itrRow->second;
298 ScExternalRefCache::Cell aCell;
299 aCell.mxToken = pToken;
300 aCell.mnFmtIndex = nFmtIndex;
301 rRow.emplace(nCol, aCell);
302 if (bSetCacheRange)
303 setCachedCell(nCol, nRow);
306 ScExternalRefCache::TokenRef ScExternalRefCache::Table::getCell(SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex) const
308 RowsDataType::const_iterator itrTable = maRows.find(nRow);
309 if (itrTable == maRows.end())
311 // this table doesn't have the specified row.
312 return getEmptyOrNullToken(nCol, nRow);
315 const RowDataType& rRowData = itrTable->second;
316 RowDataType::const_iterator itrRow = rRowData.find(nCol);
317 if (itrRow == rRowData.end())
319 // this row doesn't have the specified column.
320 return getEmptyOrNullToken(nCol, nRow);
323 const Cell& rCell = itrRow->second;
324 if (pnFmtIndex)
325 *pnFmtIndex = rCell.mnFmtIndex;
327 return rCell.mxToken;
330 bool ScExternalRefCache::Table::hasRow( SCROW nRow ) const
332 RowsDataType::const_iterator itrRow = maRows.find(nRow);
333 return itrRow != maRows.end();
336 template< typename P >
337 void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, P predicate) const
339 vector<SCROW> aRows;
340 aRows.reserve(maRows.size());
341 for (const auto& rEntry : maRows)
342 if (predicate(rEntry))
343 aRows.push_back(rEntry.first);
345 // hash map is not ordered, so we need to explicitly sort it.
346 ::std::sort(aRows.begin(), aRows.end());
347 rRows.swap(aRows);
350 void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, SCROW nLow, SCROW nHigh) const
352 getAllRows(rRows,
353 [nLow, nHigh](std::pair<SCROW, RowDataType> rEntry) { return (nLow <= rEntry.first && rEntry.first <= nHigh); });
356 void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows) const
358 getAllRows(rRows, [](std::pair<SCROW, RowDataType>) { return true; } );
361 ::std::pair< SCROW, SCROW > ScExternalRefCache::Table::getRowRange() const
363 ::std::pair< SCROW, SCROW > aRange( 0, 0 );
364 if( !maRows.empty() )
366 // iterate over entire container (hash map is not sorted by key)
367 auto itMinMax = std::minmax_element(maRows.begin(), maRows.end(),
368 [](const RowsDataType::value_type& a, const RowsDataType::value_type& b) { return a.first < b.first; });
369 aRange.first = itMinMax.first->first;
370 aRange.second = itMinMax.second->first + 1;
372 return aRange;
375 template< typename P >
376 void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, P predicate) const
378 RowsDataType::const_iterator itrRow = maRows.find(nRow);
379 if (itrRow == maRows.end())
380 // this table doesn't have the specified row.
381 return;
383 const RowDataType& rRowData = itrRow->second;
384 vector<SCCOL> aCols;
385 aCols.reserve(rRowData.size());
386 for (const auto& rCol : rRowData)
387 if (predicate(rCol))
388 aCols.push_back(rCol.first);
390 // hash map is not ordered, so we need to explicitly sort it.
391 ::std::sort(aCols.begin(), aCols.end());
392 rCols.swap(aCols);
395 void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, SCCOL nLow, SCCOL nHigh) const
397 getAllCols(nRow, rCols,
398 [nLow, nHigh](std::pair<SCCOL, Cell> rCol) { return nLow <= rCol.first && rCol.first <= nHigh; } );
401 void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols) const
403 getAllCols(nRow, rCols, [](std::pair<SCCOL, Cell>) { return true; } );
406 ::std::pair< SCCOL, SCCOL > ScExternalRefCache::Table::getColRange( SCROW nRow ) const
408 ::std::pair< SCCOL, SCCOL > aRange( 0, 0 );
410 RowsDataType::const_iterator itrRow = maRows.find( nRow );
411 if (itrRow == maRows.end())
412 // this table doesn't have the specified row.
413 return aRange;
415 const RowDataType& rRowData = itrRow->second;
416 if( !rRowData.empty() )
418 // iterate over entire container (hash map is not sorted by key)
419 auto itMinMax = std::minmax_element(rRowData.begin(), rRowData.end(),
420 [](const RowDataType::value_type& a, const RowDataType::value_type& b) { return a.first < b.first; });
421 aRange.first = itMinMax.first->first;
422 aRange.second = itMinMax.second->first + 1;
424 return aRange;
427 void ScExternalRefCache::Table::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
429 for (const auto& rRow : maRows)
431 const RowDataType& rRowData = rRow.second;
432 for (const auto& rCol : rRowData)
434 const Cell& rCell = rCol.second;
435 rNumFmts.push_back(rCell.mnFmtIndex);
440 bool ScExternalRefCache::Table::isRangeCached(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const
442 return maCachedRanges.Contains(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
445 void ScExternalRefCache::Table::setCachedCell(SCCOL nCol, SCROW nRow)
447 setCachedCellRange(nCol, nRow, nCol, nRow);
450 void ScExternalRefCache::Table::setCachedCellRange(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
452 ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
453 maCachedRanges.Join(aRange);
456 void ScExternalRefCache::Table::setWholeTableCached()
458 setCachedCellRange(0, 0, MAXCOL, MAXROW);
461 bool ScExternalRefCache::Table::isInCachedRanges(SCCOL nCol, SCROW nRow) const
463 return maCachedRanges.Contains(ScRange(nCol, nRow, 0, nCol, nRow, 0));
466 ScExternalRefCache::TokenRef ScExternalRefCache::Table::getEmptyOrNullToken(
467 SCCOL nCol, SCROW nRow) const
469 if (isInCachedRanges(nCol, nRow))
471 TokenRef p(new ScEmptyCellToken(false, false));
472 return p;
474 return TokenRef();
477 ScExternalRefCache::TableName::TableName(OUString aUpper, OUString aReal) :
478 maUpperName(std::move(aUpper)), maRealName(std::move(aReal))
482 ScExternalRefCache::CellFormat::CellFormat() :
483 mbIsSet(false), mnType(SvNumFormatType::ALL), mnIndex(0)
487 ScExternalRefCache::ScExternalRefCache(const ScDocument& rDoc)
488 : mrDoc(rDoc)
492 ScExternalRefCache::~ScExternalRefCache() {}
494 const OUString* ScExternalRefCache::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
496 std::unique_lock aGuard(maMtxDocs);
498 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
499 if (itrDoc == maDocs.end())
501 // specified document is not cached.
502 return nullptr;
505 const DocItem& rDoc = itrDoc->second;
506 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
507 if (itrTabId == rDoc.maTableNameIndex.end())
509 // the specified table is not in cache.
510 return nullptr;
513 return &rDoc.maTableNames[itrTabId->second].maRealName;
516 const OUString* ScExternalRefCache::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
518 std::unique_lock aGuard(maMtxDocs);
520 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
521 if (itrDoc == maDocs.end())
523 // specified document is not cached.
524 return nullptr;
527 const DocItem& rDoc = itrDoc->second;
528 NamePairMap::const_iterator itr = rDoc.maRealRangeNameMap.find(
529 ScGlobal::getCharClass().uppercase(rRangeName));
530 if (itr == rDoc.maRealRangeNameMap.end())
531 // range name not found.
532 return nullptr;
534 return &itr->second;
537 ScExternalRefCache::TokenRef ScExternalRefCache::getCellData(
538 sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex)
540 std::unique_lock aGuard(maMtxDocs);
542 DocDataType::const_iterator itrDoc = maDocs.find(nFileId);
543 if (itrDoc == maDocs.end())
545 // specified document is not cached.
546 return TokenRef();
549 const DocItem& rDoc = itrDoc->second;
550 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
551 if (itrTabId == rDoc.maTableNameIndex.end())
553 // the specified table is not in cache.
554 return TokenRef();
557 const TableTypeRef& pTableData = rDoc.maTables[itrTabId->second];
558 if (!pTableData)
560 // the table data is not instantiated yet.
561 return TokenRef();
564 return pTableData->getCell(nCol, nRow, pnFmtIndex);
567 ScExternalRefCache::TokenArrayRef ScExternalRefCache::getCellRangeData(
568 sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange)
570 std::unique_lock aGuard(maMtxDocs);
572 DocDataType::iterator itrDoc = maDocs.find(nFileId);
573 if (itrDoc == maDocs.end())
574 // specified document is not cached.
575 return TokenArrayRef();
577 DocItem& rDoc = itrDoc->second;
579 TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName);
580 if (itrTabId == rDoc.maTableNameIndex.end())
581 // the specified table is not in cache.
582 return TokenArrayRef();
584 const ScAddress& s = rRange.aStart;
585 const ScAddress& e = rRange.aEnd;
587 const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
588 const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
589 const SCROW nRow1 = s.Row(), nRow2 = e.Row();
591 // Make sure I have all the tables cached.
592 size_t nTabFirstId = itrTabId->second;
593 size_t nTabLastId = nTabFirstId + nTab2 - nTab1;
594 if (nTabLastId >= rDoc.maTables.size())
595 // not all tables are cached.
596 return TokenArrayRef();
598 ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
600 RangeArrayMap::const_iterator itrRange = rDoc.maRangeArrays.find( aCacheRange);
601 if (itrRange != rDoc.maRangeArrays.end())
602 // Cache hit!
603 return itrRange->second;
605 std::unique_ptr<ScRange> pNewRange;
606 TokenArrayRef pArray;
607 bool bFirstTab = true;
608 for (size_t nTab = nTabFirstId; nTab <= nTabLastId; ++nTab)
610 TableTypeRef pTab = rDoc.maTables[nTab];
611 if (!pTab)
612 return TokenArrayRef();
614 SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
615 SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
617 if (!pTab->isRangeCached(nDataCol1, nDataRow1, nDataCol2, nDataRow2))
619 // specified range is not entirely within cached ranges.
620 return TokenArrayRef();
623 SCSIZE nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
624 SCSIZE nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
625 ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
627 // Needed in shrink and fill.
628 vector<SCROW> aRows;
629 pTab->getAllRows(aRows, nDataRow1, nDataRow2);
630 bool bFill = true;
632 // Check if size could be allocated and if not skip the fill, there's
633 // one error element instead. But retry first with the actual data area
634 // if that is smaller than the original range, which works for most
635 // functions just not some that operate/compare with the original size
636 // and expect empty values in non-data areas.
637 // Restrict this though to ranges of entire columns or rows, other
638 // ranges might be on purpose. (Other special cases to handle?)
639 /* TODO: sparse matrix could help */
640 SCSIZE nMatCols, nMatRows;
641 xMat->GetDimensions( nMatCols, nMatRows);
642 if (nMatCols != nMatrixColumns || nMatRows != nMatrixRows)
644 bFill = false;
645 if (aRows.empty())
647 // There's no data at all. Set the one matrix element to empty
648 // for column-repeated and row-repeated access.
649 xMat->PutEmpty(0,0);
651 else if ((nCol1 == 0 && nCol2 == MAXCOL) || (nRow1 == 0 && nRow2 == MAXROW))
653 nDataRow1 = aRows.front();
654 nDataRow2 = aRows.back();
655 SCCOL nMinCol = std::numeric_limits<SCCOL>::max();
656 SCCOL nMaxCol = std::numeric_limits<SCCOL>::min();
657 for (const auto& rRow : aRows)
659 vector<SCCOL> aCols;
660 pTab->getAllCols(rRow, aCols, nDataCol1, nDataCol2);
661 if (!aCols.empty())
663 nMinCol = std::min( nMinCol, aCols.front());
664 nMaxCol = std::max( nMaxCol, aCols.back());
668 if (nMinCol <= nMaxCol && ((o3tl::make_unsigned(nMaxCol-nMinCol+1) < nMatrixColumns) ||
669 (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows)))
671 nMatrixColumns = static_cast<SCSIZE>(nMaxCol-nMinCol+1);
672 nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
673 xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
674 xMat->GetDimensions( nMatCols, nMatRows);
675 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
677 nDataCol1 = nMinCol;
678 nDataCol2 = nMaxCol;
679 bFill = true;
685 if (bFill)
687 // Only fill non-empty cells, for better performance.
688 for (SCROW nRow : aRows)
690 vector<SCCOL> aCols;
691 pTab->getAllCols(nRow, aCols, nDataCol1, nDataCol2);
692 for (SCCOL nCol : aCols)
694 TokenRef pToken = pTab->getCell(nCol, nRow);
695 if (!pToken)
696 // This should never happen!
697 return TokenArrayRef();
699 SCSIZE nC = nCol - nDataCol1, nR = nRow - nDataRow1;
700 switch (pToken->GetType())
702 case svDouble:
703 xMat->PutDouble(pToken->GetDouble(), nC, nR);
704 break;
705 case svString:
706 xMat->PutString(pToken->GetString(), nC, nR);
707 break;
708 default:
714 if (!bFirstTab)
715 pArray->AddOpCode(ocSep);
717 ScMatrixToken aToken(std::move(xMat));
718 if (!pArray)
719 pArray = std::make_shared<ScTokenArray>(mrDoc);
720 pArray->AddToken(aToken);
722 bFirstTab = false;
724 if (!pNewRange)
725 pNewRange.reset(new ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
726 else
727 pNewRange->ExtendTo(ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab));
731 rDoc.maRangeArrays.emplace(aCacheRange, pArray);
732 if (pNewRange && *pNewRange != aCacheRange)
733 rDoc.maRangeArrays.emplace(*pNewRange, pArray);
735 return pArray;
738 ScExternalRefCache::TokenArrayRef ScExternalRefCache::getRangeNameTokens(sal_uInt16 nFileId, const OUString& rName)
740 std::unique_lock aGuard(maMtxDocs);
742 DocItem* pDoc = getDocItem(aGuard, nFileId);
743 if (!pDoc)
744 return TokenArrayRef();
746 RangeNameMap& rMap = pDoc->maRangeNames;
747 RangeNameMap::const_iterator itr = rMap.find(
748 ScGlobal::getCharClass().uppercase(rName));
749 if (itr == rMap.end())
750 return TokenArrayRef();
752 return itr->second;
755 void ScExternalRefCache::setRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const TokenArrayRef& pArray)
757 std::unique_lock aGuard(maMtxDocs);
759 DocItem* pDoc = getDocItem(aGuard, nFileId);
760 if (!pDoc)
761 return;
763 OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
764 RangeNameMap& rMap = pDoc->maRangeNames;
765 rMap.emplace(aUpperName, pArray);
766 pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
769 bool ScExternalRefCache::isValidRangeName(sal_uInt16 nFileId, const OUString& rName) const
771 std::unique_lock aGuard(maMtxDocs);
773 DocItem* pDoc = getDocItem(aGuard, nFileId);
774 if (!pDoc)
775 return false;
777 OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
778 const RangeNameMap& rMap = pDoc->maRangeNames;
779 return rMap.count(aUpperName) > 0;
782 void ScExternalRefCache::setRangeName(sal_uInt16 nFileId, const OUString& rName)
784 std::unique_lock aGuard(maMtxDocs);
786 DocItem* pDoc = getDocItem(aGuard, nFileId);
787 if (!pDoc)
788 return;
790 OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
791 pDoc->maRealRangeNameMap.emplace(aUpperName, rName);
794 void ScExternalRefCache::setCellData(sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow,
795 TokenRef const & pToken, sal_uLong nFmtIndex)
797 if (!isDocInitialized(nFileId))
798 return;
800 using ::std::pair;
801 DocItem* pDocItem = getDocItem(nFileId);
802 if (!pDocItem)
803 return;
805 DocItem& rDoc = *pDocItem;
807 // See if the table by this name already exists.
808 TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rTabName);
809 if (itrTabName == rDoc.maTableNameIndex.end())
810 // Table not found. Maybe the table name or the file id is wrong ???
811 return;
813 TableTypeRef& pTableData = rDoc.maTables[itrTabName->second];
814 if (!pTableData)
815 pTableData = std::make_shared<Table>();
817 pTableData->setCell(nCol, nRow, pToken, nFmtIndex);
818 pTableData->setCachedCell(nCol, nRow);
821 void ScExternalRefCache::setCellRangeData(sal_uInt16 nFileId, const ScRange& rRange, const vector<SingleRangeData>& rData,
822 const TokenArrayRef& pArray)
824 using ::std::pair;
825 if (rData.empty() || !isDocInitialized(nFileId))
826 // nothing to cache
827 return;
829 // First, get the document item for the given file ID.
830 DocItem* pDocItem = getDocItem(nFileId);
831 if (!pDocItem)
832 return;
834 DocItem& rDoc = *pDocItem;
836 // Now, find the table position of the first table to cache.
837 const OUString& rFirstTabName = rData.front().maTableName;
838 TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rFirstTabName);
839 if (itrTabName == rDoc.maTableNameIndex.end())
841 // table index not found.
842 return;
845 size_t nTabFirstId = itrTabName->second;
846 SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
847 SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
848 size_t i = nTabFirstId;
849 for (const auto& rItem : rData)
851 TableTypeRef& pTabData = rDoc.maTables[i];
852 if (!pTabData)
853 pTabData = std::make_shared<Table>();
855 const ScMatrixRef& pMat = rItem.mpRangeData;
856 SCSIZE nMatCols, nMatRows;
857 pMat->GetDimensions( nMatCols, nMatRows);
858 if (nMatCols > o3tl::make_unsigned(nCol2 - nCol1) && nMatRows > o3tl::make_unsigned(nRow2 - nRow1))
860 ScMatrix::DoubleOpFunction aDoubleFunc = [=](size_t row, size_t col, double val) -> void
862 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val), 0, false);
864 ScMatrix::BoolOpFunction aBoolFunc = [=](size_t row, size_t col, bool val) -> void
866 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val ? 1.0 : 0.0), 0, false);
868 ScMatrix::StringOpFunction aStringFunc = [=](size_t row, size_t col, svl::SharedString val) -> void
870 pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaStringToken(std::move(val)), 0, false);
872 ScMatrix::EmptyOpFunction aEmptyFunc = [](size_t /*row*/, size_t /*col*/) -> void
874 // Nothing. Empty cell.
876 pMat->ExecuteOperation(std::pair<size_t, size_t>(0, 0),
877 std::pair<size_t, size_t>(nRow2-nRow1, nCol2-nCol1),
878 std::move(aDoubleFunc), std::move(aBoolFunc), std::move(aStringFunc), std::move(aEmptyFunc));
879 // Mark the whole range 'cached'.
880 pTabData->setCachedCellRange(nCol1, nRow1, nCol2, nRow2);
882 else
884 // This may happen due to a matrix not been allocated earlier, in
885 // which case it should have exactly one error element.
886 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix size mismatch");
887 if (nMatCols != 1 || nMatRows != 1)
888 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - not a one element matrix");
889 else
891 FormulaError nErr = GetDoubleErrorValue( pMat->GetDouble(0,0));
892 SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix error value is " << static_cast<int>(nErr) <<
893 (nErr == FormulaError::MatrixSize ? ", ok" : ", not ok"));
896 ++i;
899 size_t nTabLastId = nTabFirstId + rRange.aEnd.Tab() - rRange.aStart.Tab();
900 ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId));
902 rDoc.maRangeArrays.emplace(aCacheRange, pArray);
905 bool ScExternalRefCache::isDocInitialized(sal_uInt16 nFileId)
907 DocItem* pDoc = getDocItem(nFileId);
908 if (!pDoc)
909 return false;
911 return pDoc->mbInitFromSource;
914 static bool lcl_getStrictTableDataIndex(const ScExternalRefCache::TableNameIndexMap& rMap, const OUString& rName, size_t& rIndex)
916 ScExternalRefCache::TableNameIndexMap::const_iterator itr = rMap.find(rName);
917 if (itr == rMap.end())
918 return false;
920 rIndex = itr->second;
921 return true;
924 bool ScExternalRefCache::DocItem::getTableDataIndex( const OUString& rTabName, size_t& rIndex ) const
926 ScExternalRefCache::TableNameIndexMap::const_iterator itr = findTableNameIndex(rTabName);
927 if (itr == maTableNameIndex.end())
928 return false;
930 rIndex = itr->second;
931 return true;
934 namespace {
935 OUString getFirstSheetName()
937 // Get Custom prefix.
938 const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
939 // Form sheet name identical to the first generated sheet name when
940 // creating an internal document, e.g. 'Sheet1'.
941 return rOpt.GetInitTabPrefix() + "1";
945 void ScExternalRefCache::initializeDoc(sal_uInt16 nFileId, const vector<OUString>& rTabNames,
946 const OUString& rBaseName)
948 DocItem* pDoc = getDocItem(nFileId);
949 if (!pDoc)
950 return;
952 size_t n = rTabNames.size();
954 // table name list - the list must include all table names in the source
955 // document and only to be populated when loading the source document, not
956 // when loading cached data from, say, Excel XCT/CRN records.
957 vector<TableName> aNewTabNames;
958 aNewTabNames.reserve(n);
959 for (const auto& rTabName : rTabNames)
961 TableName aNameItem(ScGlobal::getCharClass().uppercase(rTabName), rTabName);
962 aNewTabNames.push_back(aNameItem);
964 pDoc->maTableNames.swap(aNewTabNames);
966 // data tables - preserve any existing data that may have been set during
967 // file import.
968 vector<TableTypeRef> aNewTables(n);
969 for (size_t i = 0; i < n; ++i)
971 size_t nIndex;
972 if (lcl_getStrictTableDataIndex(pDoc->maTableNameIndex, pDoc->maTableNames[i].maUpperName, nIndex))
974 aNewTables[i] = pDoc->maTables[nIndex];
977 pDoc->maTables.swap(aNewTables);
979 // name index map
980 TableNameIndexMap aNewNameIndex;
981 for (size_t i = 0; i < n; ++i)
982 aNewNameIndex.emplace(pDoc->maTableNames[i].maUpperName, i);
983 pDoc->maTableNameIndex.swap(aNewNameIndex);
985 // Setup name for Sheet1 vs base name to be able to load documents
986 // that store the base name as table name, or vice versa.
987 pDoc->maSingleTableNameAlias.clear();
988 if (!rBaseName.isEmpty() && pDoc->maTableNames.size() == 1)
990 OUString aSheetName = getFirstSheetName();
991 // If the one and only table name matches exactly, carry on the base
992 // file name for further alias use. If instead the table name matches
993 // the base name, carry on the sheet name as alias.
994 if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, aSheetName))
995 pDoc->maSingleTableNameAlias = rBaseName;
996 else if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, rBaseName))
997 pDoc->maSingleTableNameAlias = aSheetName;
1000 pDoc->mbInitFromSource = true;
1003 ScExternalRefCache::TableNameIndexMap::const_iterator ScExternalRefCache::DocItem::findTableNameIndex(
1004 const OUString& rTabName ) const
1006 const OUString aTabNameUpper = ScGlobal::getCharClass().uppercase( rTabName);
1007 TableNameIndexMap::const_iterator itrTabName = maTableNameIndex.find( aTabNameUpper);
1008 if (itrTabName != maTableNameIndex.end())
1009 return itrTabName;
1011 // Since some time for external references to CSV files the base name is
1012 // used as sheet name instead of Sheet1, check if we can resolve that.
1013 // Also helps users that got accustomed to one or the other way.
1014 if (maSingleTableNameAlias.isEmpty() || maTableNameIndex.size() != 1)
1015 return itrTabName;
1017 // maSingleTableNameAlias has been set up only if the original file loaded
1018 // had exactly one sheet and internal sheet name was Sheet1 or localized or
1019 // customized equivalent, or base name.
1020 if (aTabNameUpper == ScGlobal::getCharClass().uppercase( maSingleTableNameAlias))
1021 return maTableNameIndex.begin();
1023 return itrTabName;
1026 bool ScExternalRefCache::DocItem::getSingleTableNameAlternative( OUString& rTabName ) const
1028 if (maSingleTableNameAlias.isEmpty() || maTableNames.size() != 1)
1029 return false;
1030 if (ScGlobal::GetTransliteration().isEqual( rTabName, maTableNames[0].maRealName))
1032 rTabName = maSingleTableNameAlias;
1033 return true;
1035 if (ScGlobal::GetTransliteration().isEqual( rTabName, maSingleTableNameAlias))
1037 rTabName = maTableNames[0].maRealName;
1038 return true;
1040 return false;
1043 bool ScExternalRefCache::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
1044 sal_uInt16 nFileId ) const
1046 bool bFound = rSrcDoc.GetTable( rTabName, rTab);
1047 if (!bFound)
1049 // Check the one table alias alternative.
1050 const DocItem* pDoc = getDocItem( nFileId );
1051 if (pDoc)
1053 OUString aTabName( rTabName);
1054 if (pDoc->getSingleTableNameAlternative( aTabName))
1055 bFound = rSrcDoc.GetTable( aTabName, rTab);
1058 return bFound;
1061 OUString ScExternalRefCache::getTableName(sal_uInt16 nFileId, size_t nCacheId) const
1063 if( DocItem* pDoc = getDocItem( nFileId ) )
1064 if( nCacheId < pDoc->maTableNames.size() )
1065 return pDoc->maTableNames[ nCacheId ].maRealName;
1066 return OUString();
1069 void ScExternalRefCache::getAllTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
1071 rTabNames.clear();
1072 DocItem* pDoc = getDocItem(nFileId);
1073 if (!pDoc)
1074 return;
1076 size_t n = pDoc->maTableNames.size();
1077 rTabNames.reserve(n);
1078 for (const auto& rTableName : pDoc->maTableNames)
1079 rTabNames.push_back(rTableName.maRealName);
1082 SCTAB ScExternalRefCache::getTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
1084 DocItem* pDoc = getDocItem(nFileId);
1085 if (!pDoc)
1086 return -1;
1088 vector<TableName>::const_iterator itrBeg = pDoc->maTableNames.begin();
1089 vector<TableName>::const_iterator itrEnd = pDoc->maTableNames.end();
1091 vector<TableName>::const_iterator itrStartTab = ::std::find_if( itrBeg, itrEnd,
1092 TabNameSearchPredicate( rStartTabName));
1093 if (itrStartTab == itrEnd)
1094 return -1;
1096 vector<TableName>::const_iterator itrEndTab = ::std::find_if( itrBeg, itrEnd,
1097 TabNameSearchPredicate( rEndTabName));
1098 if (itrEndTab == itrEnd)
1099 return 0;
1101 size_t nStartDist = ::std::distance( itrBeg, itrStartTab);
1102 size_t nEndDist = ::std::distance( itrBeg, itrEndTab);
1103 return nStartDist <= nEndDist ? static_cast<SCTAB>(nEndDist - nStartDist + 1) : -static_cast<SCTAB>(nStartDist - nEndDist + 1);
1106 void ScExternalRefCache::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const
1108 std::unique_lock aGuard(maMtxDocs);
1110 using ::std::sort;
1111 using ::std::unique;
1113 vector<sal_uInt32> aNumFmts;
1114 for (const auto& rEntry : maDocs)
1116 const vector<TableTypeRef>& rTables = rEntry.second.maTables;
1117 for (const TableTypeRef& pTab : rTables)
1119 if (!pTab)
1120 continue;
1122 pTab->getAllNumberFormats(aNumFmts);
1126 // remove duplicates.
1127 sort(aNumFmts.begin(), aNumFmts.end());
1128 aNumFmts.erase(unique(aNumFmts.begin(), aNumFmts.end()), aNumFmts.end());
1129 rNumFmts.swap(aNumFmts);
1132 bool ScExternalRefCache::setCacheDocReferenced( sal_uInt16 nFileId )
1134 DocItem* pDocItem = getDocItem(nFileId);
1135 if (!pDocItem)
1136 return areAllCacheTablesReferenced();
1138 for (auto& rxTab : pDocItem->maTables)
1140 if (rxTab)
1141 rxTab->setReferenced(true);
1143 addCacheDocToReferenced( nFileId);
1144 return areAllCacheTablesReferenced();
1147 bool ScExternalRefCache::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
1149 DocItem* pDoc = getDocItem(nFileId);
1150 if (pDoc)
1152 size_t nIndex = 0;
1153 if (pDoc->getTableDataIndex( rTabName, nIndex))
1155 size_t nStop = ::std::min( nIndex + nSheets, pDoc->maTables.size());
1156 for (size_t i = nIndex; i < nStop; ++i)
1158 TableTypeRef pTab = pDoc->maTables[i];
1159 if (pTab)
1161 if (!pTab->isReferenced())
1163 pTab->setReferenced(true);
1164 addCacheTableToReferenced( nFileId, i);
1170 return areAllCacheTablesReferenced();
1173 void ScExternalRefCache::setAllCacheTableReferencedStati( bool bReferenced )
1175 std::unique_lock aGuard(maMtxDocs);
1177 if (bReferenced)
1179 maReferenced.reset(0);
1180 for (auto& rEntry : maDocs)
1182 ScExternalRefCache::DocItem& rDocItem = rEntry.second;
1183 for (auto& rxTab : rDocItem.maTables)
1185 if (rxTab)
1186 rxTab->setReferenced(true);
1190 else
1192 size_t nDocs = 0;
1193 auto itrMax = std::max_element(maDocs.begin(), maDocs.end(),
1194 [](const DocDataType::value_type& a, const DocDataType::value_type& b) { return a.first < b.first; });
1195 if (itrMax != maDocs.end())
1196 nDocs = itrMax->first + 1;
1197 maReferenced.reset( nDocs);
1199 for (auto& [nFileId, rDocItem] : maDocs)
1201 size_t nTables = rDocItem.maTables.size();
1202 ReferencedStatus::DocReferenced & rDocReferenced = maReferenced.maDocs[nFileId];
1203 // All referenced => non-existing tables evaluate as completed.
1204 rDocReferenced.maTables.resize( nTables, true);
1205 for (size_t i=0; i < nTables; ++i)
1207 TableTypeRef & xTab = rDocItem.maTables[i];
1208 if (xTab)
1210 xTab->setReferenced(false);
1211 rDocReferenced.maTables[i] = false;
1212 rDocReferenced.mbAllTablesReferenced = false;
1213 // An addCacheTableToReferenced() actually may have
1214 // resulted in mbAllReferenced been set. Clear it.
1215 maReferenced.mbAllReferenced = false;
1222 void ScExternalRefCache::addCacheTableToReferenced( sal_uInt16 nFileId, size_t nIndex )
1224 if (nFileId >= maReferenced.maDocs.size())
1225 return;
1227 ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
1228 size_t nTables = rTables.size();
1229 if (nIndex >= nTables)
1230 return;
1232 if (!rTables[nIndex])
1234 rTables[nIndex] = true;
1235 size_t i = 0;
1236 while (i < nTables && rTables[i])
1237 ++i;
1238 if (i == nTables)
1240 maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
1241 maReferenced.checkAllDocs();
1246 void ScExternalRefCache::addCacheDocToReferenced( sal_uInt16 nFileId )
1248 if (nFileId >= maReferenced.maDocs.size())
1249 return;
1251 if (!maReferenced.maDocs[nFileId].mbAllTablesReferenced)
1253 ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables;
1254 size_t nSize = rTables.size();
1255 for (size_t i=0; i < nSize; ++i)
1256 rTables[i] = true;
1257 maReferenced.maDocs[nFileId].mbAllTablesReferenced = true;
1258 maReferenced.checkAllDocs();
1262 void ScExternalRefCache::getAllCachedDataSpans( const ScDocument& rSrcDoc, sal_uInt16 nFileId, sc::ColumnSpanSet& rSet ) const
1264 const DocItem* pDocItem = getDocItem(nFileId);
1265 if (!pDocItem)
1266 // This document is not cached.
1267 return;
1269 const std::vector<TableTypeRef>& rTables = pDocItem->maTables;
1270 for (size_t nTab = 0, nTabCount = rTables.size(); nTab < nTabCount; ++nTab)
1272 TableTypeRef pTab = rTables[nTab];
1273 if (!pTab)
1274 continue;
1276 std::vector<SCROW> aRows;
1277 pTab->getAllRows(aRows);
1278 for (SCROW nRow : aRows)
1280 std::vector<SCCOL> aCols;
1281 pTab->getAllCols(nRow, aCols);
1282 for (SCCOL nCol : aCols)
1284 rSet.set(rSrcDoc, nTab, nCol, nRow, true);
1290 ScExternalRefCache::ReferencedStatus::ReferencedStatus() :
1291 mbAllReferenced(false)
1293 reset(0);
1296 void ScExternalRefCache::ReferencedStatus::reset( size_t nDocs )
1298 if (nDocs)
1300 mbAllReferenced = false;
1301 DocReferencedVec aRefs( nDocs);
1302 maDocs.swap( aRefs);
1304 else
1306 mbAllReferenced = true;
1307 DocReferencedVec aRefs;
1308 maDocs.swap( aRefs);
1312 void ScExternalRefCache::ReferencedStatus::checkAllDocs()
1314 if (std::all_of(maDocs.begin(), maDocs.end(), [](const DocReferenced& rDoc) { return rDoc.mbAllTablesReferenced; }))
1315 mbAllReferenced = true;
1318 ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
1320 DocItem* pDoc = getDocItem(nFileId);
1321 if (!pDoc || nTabIndex >= pDoc->maTables.size())
1322 return TableTypeRef();
1324 return pDoc->maTables[nTabIndex];
1327 ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, const OUString& rTabName,
1328 bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
1330 // In API, the index is transported as cached sheet ID of type sal_Int32 in
1331 // sheet::SingleReference.Sheet or sheet::ComplexReference.Reference1.Sheet
1332 // in a sheet::FormulaToken, choose a sensible value for N/A. Effectively
1333 // being 0xffffffff
1334 const size_t nNotAvailable = static_cast<size_t>( static_cast<sal_Int32>( -1));
1336 DocItem* pDoc = getDocItem(nFileId);
1337 if (!pDoc)
1339 if (pnIndex) *pnIndex = nNotAvailable;
1340 return TableTypeRef();
1343 DocItem& rDoc = *pDoc;
1345 size_t nIndex;
1346 if (rDoc.getTableDataIndex(rTabName, nIndex))
1348 // specified table found.
1349 if( pnIndex ) *pnIndex = nIndex;
1350 if (bCreateNew && !rDoc.maTables[nIndex])
1351 rDoc.maTables[nIndex] = std::make_shared<Table>();
1353 return rDoc.maTables[nIndex];
1356 if (!bCreateNew)
1358 if (pnIndex) *pnIndex = nNotAvailable;
1359 return TableTypeRef();
1362 // If this is the first table to be created propagate the base name or
1363 // Sheet1 as an alias. For subsequent tables remove it again.
1364 if (rDoc.maTableNames.empty())
1366 if (pExtUrl)
1368 const OUString aBaseName( INetURLObject( *pExtUrl).GetBase());
1369 const OUString aSheetName( getFirstSheetName());
1370 if (ScGlobal::GetTransliteration().isEqual( rTabName, aSheetName))
1371 pDoc->maSingleTableNameAlias = aBaseName;
1372 else if (ScGlobal::GetTransliteration().isEqual( rTabName, aBaseName))
1373 pDoc->maSingleTableNameAlias = aSheetName;
1376 else
1378 rDoc.maSingleTableNameAlias.clear();
1381 // Specified table doesn't exist yet. Create one.
1382 OUString aTabNameUpper = ScGlobal::getCharClass().uppercase(rTabName);
1383 nIndex = rDoc.maTables.size();
1384 if( pnIndex ) *pnIndex = nIndex;
1385 TableTypeRef pTab = std::make_shared<Table>();
1386 rDoc.maTables.push_back(pTab);
1387 rDoc.maTableNames.emplace_back(aTabNameUpper, rTabName);
1388 rDoc.maTableNameIndex.emplace(aTabNameUpper, nIndex);
1389 return pTab;
1392 void ScExternalRefCache::clearCache(sal_uInt16 nFileId)
1394 std::unique_lock aGuard(maMtxDocs);
1395 maDocs.erase(nFileId);
1398 void ScExternalRefCache::clearCacheTables(sal_uInt16 nFileId)
1400 std::unique_lock aGuard(maMtxDocs);
1401 DocItem* pDocItem = getDocItem(aGuard, nFileId);
1402 if (!pDocItem)
1403 // This document is not cached at all.
1404 return;
1406 // Clear all cache table content, but keep the tables.
1407 std::vector<TableTypeRef>& rTabs = pDocItem->maTables;
1408 for (TableTypeRef & pTab : rTabs)
1410 if (!pTab)
1411 continue;
1413 pTab->clear();
1416 // Clear the external range name caches.
1417 pDocItem->maRangeNames.clear();
1418 pDocItem->maRangeArrays.clear();
1419 pDocItem->maRealRangeNameMap.clear();
1422 ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(sal_uInt16 nFileId) const
1424 std::unique_lock aGuard(maMtxDocs);
1425 return getDocItem(aGuard, nFileId);
1428 ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(std::unique_lock<std::mutex>& /*rGuard*/, sal_uInt16 nFileId) const
1431 using ::std::pair;
1432 DocDataType::iterator itrDoc = maDocs.find(nFileId);
1433 if (itrDoc == maDocs.end())
1435 // specified document is not cached.
1436 pair<DocDataType::iterator, bool> res = maDocs.emplace(
1437 nFileId, DocItem());
1439 if (!res.second)
1440 // insertion failed.
1441 return nullptr;
1443 itrDoc = res.first;
1446 return &itrDoc->second;
1449 ScExternalRefLink::ScExternalRefLink(ScDocument& rDoc, sal_uInt16 nFileId) :
1450 ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE),
1451 mnFileId(nFileId),
1452 mrDoc(rDoc),
1453 mbDoRefresh(true)
1457 ScExternalRefLink::~ScExternalRefLink()
1461 void ScExternalRefLink::Closed()
1463 ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
1464 pMgr->breakLink(mnFileId);
1467 ::sfx2::SvBaseLink::UpdateResult ScExternalRefLink::DataChanged(const OUString& /*rMimeType*/, const Any& /*rValue*/)
1469 if (!mbDoRefresh)
1470 return SUCCESS;
1472 OUString aFile, aFilter;
1473 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, nullptr, &aFilter);
1474 ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager();
1476 if (!pMgr->isFileLoadable(aFile))
1477 return ERROR_GENERAL;
1479 const OUString* pCurFile = pMgr->getExternalFileName(mnFileId);
1480 if (!pCurFile)
1481 return ERROR_GENERAL;
1483 if (*pCurFile == aFile)
1485 // Refresh the current source document.
1486 if (!pMgr->refreshSrcDocument(mnFileId))
1487 return ERROR_GENERAL;
1489 else
1491 // The source document has changed.
1492 ScViewData* pViewData = ScDocShell::GetViewData();
1493 if (!pViewData)
1494 return ERROR_GENERAL;
1496 ScDocShell* pDocShell = pViewData->GetDocShell();
1497 ScDocShellModificator aMod(*pDocShell);
1498 pMgr->switchSrcFile(mnFileId, aFile, aFilter);
1499 aMod.SetDocumentModified();
1502 return SUCCESS;
1505 void ScExternalRefLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /*rEndEditHdl*/)
1507 SvBaseLink::Edit(pParent, Link<SvBaseLink&,void>());
1510 void ScExternalRefLink::SetDoRefresh(bool b)
1512 mbDoRefresh = b;
1515 static FormulaToken* convertToToken( ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRefCellValue& rCell )
1517 if (rCell.hasEmptyValue())
1519 bool bInherited = (rCell.getType() == CELLTYPE_FORMULA);
1520 return new ScEmptyCellToken(bInherited, false);
1523 switch (rCell.getType())
1525 case CELLTYPE_EDIT:
1526 case CELLTYPE_STRING:
1528 OUString aStr = rCell.getString(&rSrcDoc);
1529 svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern(aStr);
1530 return new formula::FormulaStringToken(std::move(aSS));
1532 case CELLTYPE_VALUE:
1533 return new formula::FormulaDoubleToken(rCell.getDouble());
1534 case CELLTYPE_FORMULA:
1536 ScFormulaCell* pFCell = rCell.getFormula();
1537 FormulaError nError = pFCell->GetErrCode();
1538 if (nError != FormulaError::NONE)
1539 return new FormulaErrorToken( nError);
1540 else if (pFCell->IsValue())
1542 double fVal = pFCell->GetValue();
1543 return new formula::FormulaDoubleToken(fVal);
1545 else
1547 svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern( pFCell->GetString().getString());
1548 return new formula::FormulaStringToken(std::move(aSS));
1551 default:
1552 OSL_FAIL("attempted to convert an unknown cell type.");
1555 return nullptr;
1558 static std::unique_ptr<ScTokenArray> convertToTokenArray(
1559 ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRange& rRange, vector<ScExternalRefCache::SingleRangeData>& rCacheData )
1561 ScAddress& s = rRange.aStart;
1562 ScAddress& e = rRange.aEnd;
1564 const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
1565 const SCCOL nCol1 = s.Col(), nCol2 = e.Col();
1566 const SCROW nRow1 = s.Row(), nRow2 = e.Row();
1568 if (nTab2 != nTab1)
1569 // For now, we don't support multi-sheet ranges intentionally because
1570 // we don't have a way to express them in a single token. In the
1571 // future we can introduce a new stack variable type svMatrixList with
1572 // a new token type that can store a 3D matrix value and convert a 3D
1573 // range to it.
1574 return nullptr;
1576 std::unique_ptr<ScRange> pUsedRange;
1578 unique_ptr<ScTokenArray> pArray(new ScTokenArray(rSrcDoc));
1579 bool bFirstTab = true;
1580 vector<ScExternalRefCache::SingleRangeData>::iterator
1581 itrCache = rCacheData.begin(), itrCacheEnd = rCacheData.end();
1583 for (SCTAB nTab = nTab1; nTab <= nTab2 && itrCache != itrCacheEnd; ++nTab, ++itrCache)
1585 // Only loop within the data area.
1586 SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2;
1587 SCROW nDataRow1 = nRow1, nDataRow2 = nRow2;
1588 bool bShrunk;
1589 if (!rSrcDoc.ShrinkToUsedDataArea( bShrunk, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, false))
1590 // no data within specified range.
1591 continue;
1593 if (pUsedRange)
1594 // Make sure the used area only grows, not shrinks.
1595 pUsedRange->ExtendTo(ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
1596 else
1597 pUsedRange.reset(new ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0));
1599 SCSIZE nMatrixColumns = static_cast<SCSIZE>(nCol2-nCol1+1);
1600 SCSIZE nMatrixRows = static_cast<SCSIZE>(nRow2-nRow1+1);
1601 ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
1603 // Check if size could be allocated and if not skip the fill, there's
1604 // one error element instead. But retry first with the actual data area
1605 // if that is smaller than the original range, which works for most
1606 // functions just not some that operate/compare with the original size
1607 // and expect empty values in non-data areas.
1608 // Restrict this though to ranges of entire columns or rows, other
1609 // ranges might be on purpose. (Other special cases to handle?)
1610 /* TODO: sparse matrix could help */
1611 SCSIZE nMatCols, nMatRows;
1612 xMat->GetDimensions( nMatCols, nMatRows);
1613 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
1615 rSrcDoc.FillMatrix(*xMat, nTab, nCol1, nRow1, nCol2, nRow2, &rHostDoc.GetSharedStringPool());
1617 else if ((nCol1 == 0 && nCol2 == rSrcDoc.MaxCol()) || (nRow1 == 0 && nRow2 == rSrcDoc.MaxRow()))
1619 if ((o3tl::make_unsigned(nDataCol2-nDataCol1+1) < nMatrixColumns) ||
1620 (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows))
1622 nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1);
1623 nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1);
1624 xMat = new ScMatrix( nMatrixColumns, nMatrixRows);
1625 xMat->GetDimensions( nMatCols, nMatRows);
1626 if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows)
1627 rSrcDoc.FillMatrix(*xMat, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, &rHostDoc.GetSharedStringPool());
1631 if (!bFirstTab)
1632 pArray->AddOpCode(ocSep);
1634 ScMatrixToken aToken(xMat);
1635 pArray->AddToken(aToken);
1637 itrCache->mpRangeData = std::move(xMat);
1639 bFirstTab = false;
1642 if (!pUsedRange)
1643 return nullptr;
1645 s.SetCol(pUsedRange->aStart.Col());
1646 s.SetRow(pUsedRange->aStart.Row());
1647 e.SetCol(pUsedRange->aEnd.Col());
1648 e.SetRow(pUsedRange->aEnd.Row());
1650 return pArray;
1653 static std::unique_ptr<ScTokenArray> lcl_fillEmptyMatrix(const ScDocument& rDoc, const ScRange& rRange)
1655 SCSIZE nC = static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1);
1656 SCSIZE nR = static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1);
1657 ScMatrixRef xMat = new ScMatrix(nC, nR);
1659 ScMatrixToken aToken(std::move(xMat));
1660 unique_ptr<ScTokenArray> pArray(new ScTokenArray(rDoc));
1661 pArray->AddToken(aToken);
1662 return pArray;
1665 namespace {
1666 bool isLinkUpdateAllowedInDoc(const ScDocument& rDoc)
1668 ScDocShell* pDocShell = rDoc.GetDocumentShell();
1669 if (!pDocShell)
1670 return rDoc.IsFunctionAccess();
1672 return pDocShell->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate();
1676 ScExternalRefManager::ScExternalRefManager(ScDocument& rDoc) :
1677 mrDoc(rDoc),
1678 maRefCache(rDoc),
1679 mbInReferenceMarking(false),
1680 mbUserInteractionEnabled(true),
1681 mbDocTimerEnabled(true),
1682 maSrcDocTimer( "sc::ScExternalRefManager maSrcDocTimer" )
1684 maSrcDocTimer.SetInvokeHandler( LINK(this, ScExternalRefManager, TimeOutHdl) );
1685 maSrcDocTimer.SetTimeout(SRCDOC_SCAN_INTERVAL);
1688 ScExternalRefManager::~ScExternalRefManager()
1690 clear();
1693 OUString ScExternalRefManager::getCacheTableName(sal_uInt16 nFileId, size_t nTabIndex) const
1695 return maRefCache.getTableName(nFileId, nTabIndex);
1698 ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const
1700 return maRefCache.getCacheTable(nFileId, nTabIndex);
1703 ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(
1704 sal_uInt16 nFileId, const OUString& rTabName, bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl)
1706 return maRefCache.getCacheTable(nFileId, rTabName, bCreateNew, pnIndex, pExtUrl);
1709 ScExternalRefManager::LinkListener::LinkListener()
1713 ScExternalRefManager::LinkListener::~LinkListener()
1717 ScExternalRefManager::ApiGuard::ApiGuard(const ScDocument& rDoc) :
1718 mpMgr(rDoc.GetExternalRefManager()),
1719 mbOldInteractionEnabled(mpMgr->mbUserInteractionEnabled)
1721 // We don't want user interaction handled in the API.
1722 mpMgr->mbUserInteractionEnabled = false;
1725 ScExternalRefManager::ApiGuard::~ApiGuard()
1727 // Restore old value.
1728 mpMgr->mbUserInteractionEnabled = mbOldInteractionEnabled;
1731 void ScExternalRefManager::getAllCachedTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const
1733 maRefCache.getAllTableNames(nFileId, rTabNames);
1736 SCTAB ScExternalRefManager::getCachedTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const
1738 return maRefCache.getTabSpan( nFileId, rStartTabName, rEndTabName);
1741 void ScExternalRefManager::getAllCachedNumberFormats(vector<sal_uInt32>& rNumFmts) const
1743 maRefCache.getAllNumberFormats(rNumFmts);
1746 sal_uInt16 ScExternalRefManager::getExternalFileCount() const
1748 return static_cast< sal_uInt16 >( maSrcFiles.size() );
1751 void ScExternalRefManager::markUsedByLinkListeners()
1753 bool bAllMarked = false;
1754 for (const auto& [rFileId, rLinkListeners] : maLinkListeners)
1756 if (!rLinkListeners.empty())
1757 bAllMarked = maRefCache.setCacheDocReferenced(rFileId);
1759 if (bAllMarked)
1760 break;
1761 /* TODO: LinkListeners should remember the table they're listening to.
1762 * As is, listening to one table will mark all tables of the document
1763 * being referenced. */
1767 void ScExternalRefManager::markUsedExternalRefCells()
1769 for (const auto& rEntry : maRefCells)
1771 for (ScFormulaCell* pCell : rEntry.second)
1773 bool bUsed = pCell->MarkUsedExternalReferences();
1774 if (bUsed)
1775 // Return true when at least one cell references external docs.
1776 return;
1781 bool ScExternalRefManager::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets )
1783 return maRefCache.setCacheTableReferenced( nFileId, rTabName, nSheets);
1786 void ScExternalRefManager::setAllCacheTableReferencedStati( bool bReferenced )
1788 mbInReferenceMarking = !bReferenced;
1789 maRefCache.setAllCacheTableReferencedStati( bReferenced );
1792 void ScExternalRefManager::storeRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const ScTokenArray& rArray)
1794 ScExternalRefCache::TokenArrayRef pNewArray;
1795 if (!rArray.HasExternalRef())
1797 // Parse all tokens in this external range data, and replace each absolute
1798 // reference token with an external reference token, and cache them.
1799 pNewArray = std::make_shared<ScTokenArray>(mrDoc);
1800 FormulaTokenArrayPlainIterator aIter(rArray);
1801 for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next())
1803 bool bTokenAdded = false;
1804 switch (pToken->GetType())
1806 case svSingleRef:
1808 const ScSingleRefData& rRef = *pToken->GetSingleRef();
1809 OUString aTabName;
1810 if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0)
1811 aTabName = maRefCache.getTableName(nFileId, nCacheId);
1812 ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString(aTabName), // string not interned
1813 *pToken->GetSingleRef());
1814 pNewArray->AddToken(aNewToken);
1815 bTokenAdded = true;
1817 break;
1818 case svDoubleRef:
1820 const ScSingleRefData& rRef = *pToken->GetSingleRef();
1821 OUString aTabName;
1822 if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0)
1823 aTabName = maRefCache.getTableName(nFileId, nCacheId);
1824 ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString(aTabName), // string not interned
1825 *pToken->GetDoubleRef());
1826 pNewArray->AddToken(aNewToken);
1827 bTokenAdded = true;
1829 break;
1830 default:
1831 ; // nothing
1834 if (!bTokenAdded)
1835 pNewArray->AddToken(*pToken);
1838 else
1839 pNewArray = rArray.Clone();
1841 maRefCache.setRangeNameTokens(nFileId, rName, pNewArray);
1844 namespace {
1847 * Put a single cell data into internal cache table.
1849 * @param pFmt optional cell format index that may need to be stored with
1850 * the cell value.
1852 void putCellDataIntoCache(
1853 ScExternalRefCache& rRefCache, const ScExternalRefCache::TokenRef& pToken,
1854 sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
1855 const ScExternalRefCache::CellFormat* pFmt)
1857 // Now, insert the token into cache table but don't cache empty cells.
1858 if (pToken->GetType() != formula::svEmptyCell)
1860 sal_uLong nFmtIndex = (pFmt && pFmt->mbIsSet) ? pFmt->mnIndex : 0;
1861 rRefCache.setCellData(nFileId, rTabName, rCell.Col(), rCell.Row(), pToken, nFmtIndex);
1866 * Put the data into our internal cache table.
1868 * @param rRefCache cache table set.
1869 * @param pArray single range data to be returned.
1870 * @param nFileId external file ID
1871 * @param rTabName name of the table where the data should be cached.
1872 * @param rCacheData range data to be cached.
1873 * @param rCacheRange original cache range, including the empty region if
1874 * any.
1875 * @param rDataRange reduced cache range that includes only the non-empty
1876 * data area.
1878 void putRangeDataIntoCache(
1879 ScExternalRefCache& rRefCache, ScExternalRefCache::TokenArrayRef& pArray,
1880 sal_uInt16 nFileId, const OUString& rTabName,
1881 const vector<ScExternalRefCache::SingleRangeData>& rCacheData,
1882 const ScRange& rCacheRange, const ScRange& rDataRange)
1884 if (pArray)
1885 // Cache these values.
1886 rRefCache.setCellRangeData(nFileId, rDataRange, rCacheData, pArray);
1887 else
1889 // Array is empty. Fill it with an empty matrix of the required size.
1890 pArray = lcl_fillEmptyMatrix(rRefCache.getDoc(), rCacheRange);
1892 // Make sure to set this range 'cached', to prevent unnecessarily
1893 // accessing the src document time and time again.
1894 ScExternalRefCache::TableTypeRef pCacheTab =
1895 rRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
1896 if (pCacheTab)
1897 pCacheTab->setCachedCellRange(
1898 rCacheRange.aStart.Col(), rCacheRange.aStart.Row(), rCacheRange.aEnd.Col(), rCacheRange.aEnd.Row());
1903 * When accessing an external document for the first time, we need to
1904 * populate the cache with all its sheet names (whether they are referenced
1905 * or not) in the correct order. Many client codes that use external
1906 * references make this assumption.
1908 * @param rRefCache cache table set.
1909 * @param pSrcDoc source document instance.
1910 * @param nFileId external file ID associated with the source document.
1912 void initDocInCache(ScExternalRefCache& rRefCache, const ScDocument* pSrcDoc, sal_uInt16 nFileId)
1914 if (!pSrcDoc)
1915 return;
1917 if (rRefCache.isDocInitialized(nFileId))
1918 // Already initialized. No need to do this twice.
1919 return;
1921 SCTAB nTabCount = pSrcDoc->GetTableCount();
1922 if (!nTabCount)
1923 return;
1925 // Populate the cache with all table names in the source document.
1926 vector<OUString> aTabNames;
1927 aTabNames.reserve(nTabCount);
1928 for (SCTAB i = 0; i < nTabCount; ++i)
1930 OUString aName;
1931 pSrcDoc->GetName(i, aName);
1932 aTabNames.push_back(aName);
1935 // Obtain the base name, don't bother if there are more than one sheets.
1936 OUString aBaseName;
1937 if (nTabCount == 1)
1939 const ScDocShell* pShell = pSrcDoc->GetDocumentShell();
1940 if (pShell && pShell->GetMedium())
1942 OUString aName = pShell->GetMedium()->GetName();
1943 aBaseName = INetURLObject( aName).GetBase();
1947 rRefCache.initializeDoc(nFileId, aTabNames, aBaseName);
1952 bool ScExternalRefManager::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab,
1953 sal_uInt16 nFileId ) const
1955 return maRefCache.getSrcDocTable( rSrcDoc, rTabName, rTab, nFileId);
1958 ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefToken(
1959 sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell,
1960 const ScAddress* pCurPos, SCTAB* pTab, ScExternalRefCache::CellFormat* pFmt)
1962 if (pCurPos)
1963 insertRefCell(nFileId, *pCurPos);
1965 maybeLinkExternalFile(nFileId);
1967 if (pTab)
1968 *pTab = -1;
1970 if (pFmt)
1971 pFmt->mbIsSet = false;
1973 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
1974 if (pSrcDoc)
1976 // source document already loaded in memory. Re-use this instance.
1977 SCTAB nTab;
1978 if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
1980 // specified table name doesn't exist in the source document.
1981 ScExternalRefCache::TokenRef pToken(new FormulaErrorToken(FormulaError::NoRef));
1982 return pToken;
1985 if (pTab)
1986 *pTab = nTab;
1988 ScExternalRefCache::TokenRef pToken =
1989 getSingleRefTokenFromSrcDoc(
1990 nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
1992 putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
1993 return pToken;
1996 // Check if the given table name and the cell position is cached.
1997 sal_uInt32 nFmtIndex = 0;
1998 ScExternalRefCache::TokenRef pToken = maRefCache.getCellData(
1999 nFileId, rTabName, rCell.Col(), rCell.Row(), &nFmtIndex);
2000 if (pToken)
2002 // Cache hit !
2003 fillCellFormat(nFmtIndex, pFmt);
2004 return pToken;
2007 // reference not cached. read from the source document.
2008 pSrcDoc = getSrcDocument(nFileId);
2009 if (!pSrcDoc)
2011 // Source document not reachable.
2012 if (!isLinkUpdateAllowedInDoc(mrDoc))
2014 // Indicate with specific error.
2015 pToken.reset(new FormulaErrorToken(FormulaError::LinkFormulaNeedingCheck));
2017 else
2019 // Throw a reference error.
2020 pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
2022 return pToken;
2025 SCTAB nTab;
2026 if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId))
2028 // specified table name doesn't exist in the source document.
2029 pToken.reset(new FormulaErrorToken(FormulaError::NoRef));
2030 return pToken;
2033 if (pTab)
2034 *pTab = nTab;
2036 SCCOL nDataCol1 = 0, nDataCol2 = pSrcDoc->MaxCol();
2037 SCROW nDataRow1 = 0, nDataRow2 = pSrcDoc->MaxRow();
2038 bool bData = pSrcDoc->ShrinkToDataArea(nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2);
2039 if (!bData || rCell.Col() < nDataCol1 || nDataCol2 < rCell.Col() || rCell.Row() < nDataRow1 || nDataRow2 < rCell.Row())
2041 // requested cell is outside the data area. Don't even bother caching
2042 // this data, but add it to the cached range to prevent accessing the
2043 // source document time and time again.
2044 ScExternalRefCache::TableTypeRef pCacheTab =
2045 maRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr);
2046 if (pCacheTab)
2047 pCacheTab->setCachedCell(rCell.Col(), rCell.Row());
2049 pToken.reset(new ScEmptyCellToken(false, false));
2050 return pToken;
2053 pToken = getSingleRefTokenFromSrcDoc(
2054 nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt);
2056 putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt);
2057 return pToken;
2060 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokens(
2061 sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange, const ScAddress* pCurPos)
2063 if (pCurPos)
2064 insertRefCell(nFileId, *pCurPos);
2066 maybeLinkExternalFile(nFileId);
2068 ScRange aDataRange(rRange);
2069 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
2070 if (pSrcDoc)
2072 // Document already loaded in memory.
2073 vector<ScExternalRefCache::SingleRangeData> aCacheData;
2074 ScExternalRefCache::TokenArrayRef pArray =
2075 getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
2077 // Put the data into cache.
2078 putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
2079 return pArray;
2082 // Check if the given table name and the cell position is cached.
2083 ScExternalRefCache::TokenArrayRef pArray =
2084 maRefCache.getCellRangeData(nFileId, rTabName, rRange);
2085 if (pArray)
2086 // Cache hit !
2087 return pArray;
2089 pSrcDoc = getSrcDocument(nFileId);
2090 if (!pSrcDoc)
2092 // Source document is not reachable. Throw a reference error.
2093 pArray = std::make_shared<ScTokenArray>(maRefCache.getDoc());
2094 pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
2095 return pArray;
2098 vector<ScExternalRefCache::SingleRangeData> aCacheData;
2099 pArray = getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData);
2101 // Put the data into cache.
2102 putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange);
2103 return pArray;
2106 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokens(
2107 sal_uInt16 nFileId, const OUString& rName, const ScAddress* pCurPos)
2109 if (pCurPos)
2110 insertRefCell(nFileId, *pCurPos);
2112 maybeLinkExternalFile(nFileId);
2114 OUString aName = rName; // make a copy to have the casing corrected.
2115 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
2116 if (pSrcDoc)
2118 // Document already loaded in memory.
2119 ScExternalRefCache::TokenArrayRef pArray =
2120 getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
2122 if (pArray)
2123 // Cache this range name array.
2124 maRefCache.setRangeNameTokens(nFileId, aName, pArray);
2126 return pArray;
2129 ScExternalRefCache::TokenArrayRef pArray = maRefCache.getRangeNameTokens(nFileId, rName);
2130 if (pArray)
2131 // This range name is cached.
2132 return pArray;
2134 pSrcDoc = getSrcDocument(nFileId);
2135 if (!pSrcDoc)
2136 // failed to load document from disk.
2137 return ScExternalRefCache::TokenArrayRef();
2139 pArray = getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName);
2141 if (pArray)
2142 // Cache this range name array.
2143 maRefCache.setRangeNameTokens(nFileId, aName, pArray);
2145 return pArray;
2148 namespace {
2150 bool hasRangeName(const ScDocument& rDoc, const OUString& rName)
2152 ScRangeName* pExtNames = rDoc.GetRangeName();
2153 OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
2154 const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
2155 return pRangeData != nullptr;
2160 bool ScExternalRefManager::isValidRangeName(sal_uInt16 nFileId, const OUString& rName)
2162 maybeLinkExternalFile(nFileId);
2163 ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId);
2164 if (pSrcDoc)
2166 // Only check the presence of the name.
2167 if (hasRangeName(*pSrcDoc, rName))
2169 maRefCache.setRangeName(nFileId, rName);
2170 return true;
2172 return false;
2175 if (maRefCache.isValidRangeName(nFileId, rName))
2176 // Range name is cached.
2177 return true;
2179 pSrcDoc = getSrcDocument(nFileId);
2180 if (!pSrcDoc)
2181 // failed to load document from disk.
2182 return false;
2184 if (hasRangeName(*pSrcDoc, rName))
2186 maRefCache.setRangeName(nFileId, rName);
2187 return true;
2190 return false;
2193 void ScExternalRefManager::refreshAllRefCells(sal_uInt16 nFileId)
2195 RefCellMap::iterator itrFile = maRefCells.find(nFileId);
2196 if (itrFile == maRefCells.end())
2197 return;
2199 RefCellSet& rRefCells = itrFile->second;
2200 for_each(rRefCells.begin(), rRefCells.end(), UpdateFormulaCell());
2202 ScViewData* pViewData = ScDocShell::GetViewData();
2203 if (!pViewData)
2204 return;
2206 ScTabViewShell* pVShell = pViewData->GetViewShell();
2207 if (!pVShell)
2208 return;
2210 // Repainting the grid also repaints the texts, but is there a better way
2211 // to refresh texts?
2212 pVShell->Invalidate(FID_REPAINT);
2213 pVShell->PaintGrid();
2216 namespace {
2218 void insertRefCellByIterator(
2219 const ScExternalRefManager::RefCellMap::iterator& itr, ScFormulaCell* pCell)
2221 if (pCell)
2223 itr->second.insert(pCell);
2224 pCell->SetIsExtRef();
2230 void ScExternalRefManager::insertRefCell(sal_uInt16 nFileId, const ScAddress& rCell)
2232 RefCellMap::iterator itr = maRefCells.find(nFileId);
2233 if (itr == maRefCells.end())
2235 RefCellSet aRefCells;
2236 pair<RefCellMap::iterator, bool> r = maRefCells.emplace(
2237 nFileId, aRefCells);
2238 if (!r.second)
2239 // insertion failed.
2240 return;
2242 itr = r.first;
2245 insertRefCellByIterator(itr, mrDoc.GetFormulaCell(rCell));
2248 void ScExternalRefManager::insertRefCellFromTemplate( ScFormulaCell* pTemplateCell, ScFormulaCell* pCell )
2250 if (!pTemplateCell || !pCell)
2251 return;
2253 for (RefCellMap::iterator itr = maRefCells.begin(); itr != maRefCells.end(); ++itr)
2255 if (itr->second.find(pTemplateCell) != itr->second.end())
2256 insertRefCellByIterator(itr, pCell);
2260 bool ScExternalRefManager::hasCellExternalReference(const ScAddress& rCell)
2262 ScFormulaCell* pCell = mrDoc.GetFormulaCell(rCell);
2264 if (pCell)
2265 return std::any_of(maRefCells.begin(), maRefCells.end(),
2266 [&pCell](const RefCellMap::value_type& rEntry) { return rEntry.second.find(pCell) != rEntry.second.end(); });
2268 return false;
2271 void ScExternalRefManager::enableDocTimer( bool bEnable )
2273 if (mbDocTimerEnabled == bEnable)
2274 return;
2276 mbDocTimerEnabled = bEnable;
2277 if (mbDocTimerEnabled)
2279 if (!maDocShells.empty())
2281 for (auto& rEntry : maDocShells)
2282 rEntry.second.maLastAccess = tools::Time(tools::Time::SYSTEM);
2284 maSrcDocTimer.Start();
2287 else
2288 maSrcDocTimer.Stop();
2291 void ScExternalRefManager::fillCellFormat(sal_uLong nFmtIndex, ScExternalRefCache::CellFormat* pFmt) const
2293 if (!pFmt)
2294 return;
2296 SvNumFormatType nFmtType = mrDoc.GetFormatTable()->GetType(nFmtIndex);
2297 if (nFmtType != SvNumFormatType::UNDEFINED)
2299 pFmt->mbIsSet = true;
2300 pFmt->mnIndex = nFmtIndex;
2301 pFmt->mnType = nFmtType;
2305 ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefTokenFromSrcDoc(
2306 sal_uInt16 nFileId, ScDocument& rSrcDoc, const ScAddress& rPos,
2307 ScExternalRefCache::CellFormat* pFmt)
2309 // Get the cell from src doc, and convert it into a token.
2310 ScRefCellValue aCell(rSrcDoc, rPos);
2311 ScExternalRefCache::TokenRef pToken(convertToToken(mrDoc, rSrcDoc, aCell));
2313 if (!pToken)
2315 // Generate an error for unresolvable cells.
2316 pToken.reset( new FormulaErrorToken( FormulaError::NoValue));
2319 // Get number format information.
2320 sal_uInt32 nFmtIndex = rSrcDoc.GetNumberFormat(rPos.Col(), rPos.Row(), rPos.Tab());
2321 nFmtIndex = getMappedNumberFormat(nFileId, nFmtIndex, rSrcDoc);
2322 fillCellFormat(nFmtIndex, pFmt);
2323 return pToken;
2326 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokensFromSrcDoc(
2327 const ScDocument& rSrcDoc, const OUString& rTabName, ScRange& rRange,
2328 vector<ScExternalRefCache::SingleRangeData>& rCacheData)
2330 ScExternalRefCache::TokenArrayRef pArray;
2331 SCTAB nTab1;
2333 if (!rSrcDoc.GetTable(rTabName, nTab1))
2335 // specified table name doesn't exist in the source document.
2336 pArray = std::make_shared<ScTokenArray>(rSrcDoc);
2337 pArray->AddToken(FormulaErrorToken(FormulaError::NoRef));
2338 return pArray;
2341 ScRange aRange(rRange);
2342 aRange.PutInOrder();
2343 SCTAB nTabSpan = aRange.aEnd.Tab() - aRange.aStart.Tab();
2345 vector<ScExternalRefCache::SingleRangeData> aCacheData;
2346 aCacheData.reserve(nTabSpan+1);
2347 aCacheData.emplace_back();
2348 aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(rTabName);
2350 for (SCTAB i = 1; i < nTabSpan + 1; ++i)
2352 OUString aTabName;
2353 if (!rSrcDoc.GetName(nTab1 + 1, aTabName))
2354 // source document doesn't have any table by the specified name.
2355 break;
2357 aCacheData.emplace_back();
2358 aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(aTabName);
2361 aRange.aStart.SetTab(nTab1);
2362 aRange.aEnd.SetTab(nTab1 + nTabSpan);
2364 pArray = convertToTokenArray(mrDoc, rSrcDoc, aRange, aCacheData);
2365 rRange = aRange;
2366 rCacheData.swap(aCacheData);
2367 return pArray;
2370 ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokensFromSrcDoc(
2371 sal_uInt16 nFileId, const ScDocument& rSrcDoc, OUString& rName)
2373 ScRangeName* pExtNames = rSrcDoc.GetRangeName();
2374 OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
2375 const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName);
2376 if (!pRangeData)
2377 return ScExternalRefCache::TokenArrayRef();
2379 // Parse all tokens in this external range data, and replace each absolute
2380 // reference token with an external reference token, and cache them. Also
2381 // register the source document with the link manager if it's a new
2382 // source.
2384 ScExternalRefCache::TokenArrayRef pNew = std::make_shared<ScTokenArray>(rSrcDoc);
2386 ScTokenArray aCode(*pRangeData->GetCode());
2387 FormulaTokenArrayPlainIterator aIter(aCode);
2388 for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next())
2390 bool bTokenAdded = false;
2391 switch (pToken->GetType())
2393 case svSingleRef:
2395 const ScSingleRefData& rRef = *pToken->GetSingleRef();
2396 OUString aTabName;
2397 rSrcDoc.GetName(rRef.Tab(), aTabName);
2398 ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned
2399 *pToken->GetSingleRef());
2400 pNew->AddToken(aNewToken);
2401 bTokenAdded = true;
2403 break;
2404 case svDoubleRef:
2406 const ScSingleRefData& rRef = *pToken->GetSingleRef();
2407 OUString aTabName;
2408 rSrcDoc.GetName(rRef.Tab(), aTabName);
2409 ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned
2410 *pToken->GetDoubleRef());
2411 pNew->AddToken(aNewToken);
2412 bTokenAdded = true;
2414 break;
2415 default:
2416 ; // nothing
2419 if (!bTokenAdded)
2420 pNew->AddToken(*pToken);
2423 rName = pRangeData->GetName(); // Get the correctly-cased name.
2424 return pNew;
2427 ScDocument* ScExternalRefManager::getInMemorySrcDocument(sal_uInt16 nFileId)
2429 const OUString* pFileName = getExternalFileName(nFileId);
2430 if (!pFileName)
2431 return nullptr;
2433 // Do not load document until it was allowed.
2434 if (!isLinkUpdateAllowedInDoc(mrDoc))
2435 return nullptr;
2437 ScDocument* pSrcDoc = nullptr;
2438 ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
2439 while (pShell)
2441 SfxMedium* pMedium = pShell->GetMedium();
2442 if (pMedium && !pMedium->GetName().isEmpty())
2444 // TODO: We should make the case sensitivity platform dependent.
2445 if (pFileName->equalsIgnoreAsciiCase(pMedium->GetName()))
2447 // Found !
2448 pSrcDoc = &pShell->GetDocument();
2449 break;
2452 else
2454 // handle unsaved documents here
2455 OUString aName = pShell->GetName();
2456 if (pFileName->equalsIgnoreAsciiCase(aName))
2458 // Found !
2459 SrcShell aSrcDoc;
2460 aSrcDoc.maShell = pShell;
2461 maUnsavedDocShells.emplace(nFileId, aSrcDoc);
2462 StartListening(*pShell);
2463 pSrcDoc = &pShell->GetDocument();
2464 break;
2467 pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
2470 initDocInCache(maRefCache, pSrcDoc, nFileId);
2471 return pSrcDoc;
2474 ScDocument* ScExternalRefManager::getSrcDocument(sal_uInt16 nFileId)
2476 if (!mrDoc.IsExecuteLinkEnabled())
2477 return nullptr;
2479 DocShellMap::iterator itrEnd = maDocShells.end();
2480 DocShellMap::iterator itr = maDocShells.find(nFileId);
2482 if (itr != itrEnd)
2484 // document already loaded.
2486 SfxObjectShell* p = itr->second.maShell.get();
2487 itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
2488 return &static_cast<ScDocShell*>(p)->GetDocument();
2491 itrEnd = maUnsavedDocShells.end();
2492 itr = maUnsavedDocShells.find(nFileId);
2493 if (itr != itrEnd)
2495 //document is unsaved document
2497 SfxObjectShell* p = itr->second.maShell.get();
2498 itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM );
2499 return &static_cast<ScDocShell*>(p)->GetDocument();
2502 const OUString* pFile = getExternalFileName(nFileId);
2503 if (!pFile)
2504 // no file name associated with this ID.
2505 return nullptr;
2507 SrcShell aSrcDoc;
2510 OUString aFilter;
2511 aSrcDoc.maShell = loadSrcDocument(nFileId, aFilter);
2513 catch (const css::uno::Exception&)
2516 if (!aSrcDoc.maShell.is())
2518 // source document could not be loaded.
2519 return nullptr;
2522 return &cacheNewDocShell(nFileId, aSrcDoc);
2525 SfxObjectShellRef ScExternalRefManager::loadSrcDocument(sal_uInt16 nFileId, OUString& rFilter)
2527 // Do not load document until it was allowed.
2528 if (!isLinkUpdateAllowedInDoc(mrDoc))
2529 return nullptr;
2531 const SrcFileData* pFileData = getExternalFileData(nFileId);
2532 if (!pFileData)
2533 return nullptr;
2535 // Always load the document by using the path created from the relative
2536 // path. If the referenced document is not there, simply exit. The
2537 // original file name should be used only when the relative path is not
2538 // given.
2539 OUString aFile = pFileData->maFileName;
2540 maybeCreateRealFileName(nFileId);
2541 if (!pFileData->maRealFileName.isEmpty())
2542 aFile = pFileData->maRealFileName;
2544 if (!isFileLoadable(aFile))
2545 return nullptr;
2547 INetURLObject aURLObject(aFile);
2548 const OUString sHost = aURLObject.GetHost();
2549 if (HostFilter::isForbidden(sHost))
2551 SAL_WARN( "sc.ui", "ScExternalRefManager::loadSrcDocument: blocked access to external file: \"" << aFile << "\"");
2552 return nullptr;
2555 OUString aOptions = pFileData->maFilterOptions;
2556 if ( !pFileData->maFilterName.isEmpty() )
2557 rFilter = pFileData->maFilterName; // don't overwrite stored filter with guessed filter
2558 else
2559 ScDocumentLoader::GetFilterName(aFile, rFilter, aOptions, true, false);
2560 std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName(rFilter);
2562 if (pFileData->maRelativeName.isEmpty())
2564 // Generate a relative file path.
2565 INetURLObject aBaseURL(getOwnDocumentName());
2566 aBaseURL.insertName(u"content.xml");
2568 OUString aStr = URIHelper::simpleNormalizedMakeRelative(
2569 aBaseURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), aFile);
2571 setRelativeFileName(nFileId, aStr);
2574 std::unique_ptr<SfxItemSet> pSet(new SfxAllItemSet(SfxGetpApp()->GetPool()));
2575 if (!aOptions.isEmpty())
2576 pSet->Put(SfxStringItem(SID_FILE_FILTEROPTIONS, aOptions));
2578 // make medium hidden to prevent assertion from progress bar
2579 pSet->Put( SfxBoolItem(SID_HIDDEN, true) );
2581 // If the current document is allowed to execute macros then the referenced
2582 // document may execute macros according to the security configuration.
2583 // Similar for UpdateDocMode to update links, just that if we reach here
2584 // the user already allowed updates and intermediate documents are expected
2585 // to update as well. When loading the document ScDocShell::Load() will
2586 // check through ScDocShell::GetLinkUpdateModeState() if its location is
2587 // trusted.
2588 ScDocShell* pShell = mrDoc.GetDocumentShell();
2589 if (pShell)
2591 SfxMedium* pMedium = pShell->GetMedium();
2592 if (pMedium)
2594 const SfxUInt16Item* pItem = pMedium->GetItemSet().GetItemIfSet( SID_MACROEXECMODE, false );
2595 if (pItem &&
2596 pItem->GetValue() != css::document::MacroExecMode::NEVER_EXECUTE)
2597 pSet->Put( SfxUInt16Item( SID_MACROEXECMODE, css::document::MacroExecMode::USE_CONFIG));
2600 pSet->Put( SfxUInt16Item( SID_UPDATEDOCMODE, css::document::UpdateDocMode::FULL_UPDATE));
2603 unique_ptr<SfxMedium> pMedium(new SfxMedium(aFile, StreamMode::STD_READ,
2604 std::move(pFilter), std::move(pSet)));
2605 if (pMedium->GetErrorIgnoreWarning() != ERRCODE_NONE)
2606 return nullptr;
2608 // To load encrypted documents with password, user interaction needs to be enabled.
2609 pMedium->UseInteractionHandler(mbUserInteractionEnabled);
2611 rtl::Reference<ScDocShell> pNewShell = new ScDocShell(SfxModelFlags::EXTERNAL_LINK);
2613 // increment the recursive link count of the source document.
2614 ScExtDocOptions* pExtOpt = mrDoc.GetExtDocOptions();
2615 sal_uInt32 nLinkCount = pExtOpt ? pExtOpt->GetDocSettings().mnLinkCnt : 0;
2616 ScDocument& rSrcDoc = pNewShell->GetDocument();
2617 rSrcDoc.EnableExecuteLink(false); // to prevent circular access of external references.
2618 rSrcDoc.EnableUndo(false);
2619 rSrcDoc.LockAdjustHeight();
2620 rSrcDoc.EnableUserInteraction(false);
2622 ScExtDocOptions* pExtOptNew = rSrcDoc.GetExtDocOptions();
2623 if (!pExtOptNew)
2625 rSrcDoc.SetExtDocOptions(std::make_unique<ScExtDocOptions>());
2626 pExtOptNew = rSrcDoc.GetExtDocOptions();
2628 pExtOptNew->GetDocSettings().mnLinkCnt = nLinkCount + 1;
2630 if (!pNewShell->DoLoad(pMedium.release()))
2632 pNewShell->DoClose();
2633 pNewShell.clear();
2634 return pNewShell;
2637 // with UseInteractionHandler, options may be set by dialog during DoLoad
2638 OUString aNew = ScDocumentLoader::GetOptions(*pNewShell->GetMedium());
2639 if (!aNew.isEmpty() && aNew != aOptions)
2640 aOptions = aNew;
2641 setFilterData(nFileId, rFilter, aOptions); // update the filter data, including the new options
2643 return pNewShell;
2646 ScDocument& ScExternalRefManager::cacheNewDocShell( sal_uInt16 nFileId, SrcShell& rSrcShell )
2648 if (mbDocTimerEnabled && maDocShells.empty())
2649 // If this is the first source document insertion, start up the timer.
2650 maSrcDocTimer.Start();
2652 maDocShells.emplace(nFileId, rSrcShell);
2653 SfxObjectShell& rShell = *rSrcShell.maShell;
2654 ScDocument& rSrcDoc = static_cast<ScDocShell&>(rShell).GetDocument();
2655 initDocInCache(maRefCache, &rSrcDoc, nFileId);
2656 return rSrcDoc;
2659 bool ScExternalRefManager::isFileLoadable(const OUString& rFile) const
2661 if (rFile.isEmpty())
2662 return false;
2664 if (isOwnDocument(rFile))
2665 return false;
2666 OUString aPhysical;
2667 if (osl::FileBase::getSystemPathFromFileURL(rFile, aPhysical)
2668 == osl::FileBase::E_None)
2670 // #i114504# try IsFolder/Exists only for file URLs
2672 if (utl::UCBContentHelper::IsFolder(rFile))
2673 return false;
2675 return utl::UCBContentHelper::Exists(rFile);
2677 else
2678 return true; // for http and others, Exists doesn't work, but the URL can still be opened
2681 void ScExternalRefManager::maybeLinkExternalFile( sal_uInt16 nFileId, bool bDeferFilterDetection )
2683 if (maLinkedDocs.count(nFileId))
2684 // file already linked, or the link has been broken.
2685 return;
2687 // Source document not linked yet. Link it now.
2688 const OUString* pFileName = getExternalFileName(nFileId);
2689 if (!pFileName)
2690 return;
2692 OUString aFilter, aOptions;
2693 const SrcFileData* pFileData = getExternalFileData(nFileId);
2694 if (pFileData)
2696 aFilter = pFileData->maFilterName;
2697 aOptions = pFileData->maFilterOptions;
2700 // Filter detection may access external links; defer it until we are allowed.
2701 if (!bDeferFilterDetection)
2702 bDeferFilterDetection = !isLinkUpdateAllowedInDoc(mrDoc);
2704 // If a filter was already set (for example, loading the cached table),
2705 // don't call GetFilterName which has to access the source file.
2706 // If filter detection is deferred, the next successful loadSrcDocument()
2707 // will update SrcFileData filter name.
2708 if (aFilter.isEmpty() && !bDeferFilterDetection)
2709 ScDocumentLoader::GetFilterName(*pFileName, aFilter, aOptions, true, false);
2710 sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager();
2711 if (!pLinkMgr)
2713 SAL_WARN( "sc.ui", "ScExternalRefManager::maybeLinkExternalFile: pLinkMgr==NULL");
2714 return;
2716 ScExternalRefLink* pLink = new ScExternalRefLink(mrDoc, nFileId);
2717 OSL_ENSURE(pFileName, "ScExternalRefManager::maybeLinkExternalFile: file name pointer is NULL");
2718 pLinkMgr->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, *pFileName,
2719 (aFilter.isEmpty() && bDeferFilterDetection ? nullptr : &aFilter));
2721 pLink->SetDoRefresh(false);
2722 pLink->Update();
2723 pLink->SetDoRefresh(true);
2725 maLinkedDocs.emplace(nFileId, true);
2728 void ScExternalRefManager::addFilesToLinkManager()
2730 if (maSrcFiles.empty())
2731 return;
2733 SAL_WARN_IF( maSrcFiles.size() >= SAL_MAX_UINT16,
2734 "sc.ui", "ScExternalRefManager::addFilesToLinkManager: files overflow");
2735 const sal_uInt16 nSize = static_cast<sal_uInt16>( std::min<size_t>( maSrcFiles.size(), SAL_MAX_UINT16));
2736 for (sal_uInt16 nFileId = 0; nFileId < nSize; ++nFileId)
2737 maybeLinkExternalFile( nFileId, true);
2740 void ScExternalRefManager::SrcFileData::maybeCreateRealFileName(std::u16string_view rOwnDocName)
2742 if (maRelativeName.isEmpty())
2743 // No relative path given. Nothing to do.
2744 return;
2746 if (!maRealFileName.isEmpty())
2747 // Real file name already created. Nothing to do.
2748 return;
2750 // Formulate the absolute file path from the relative path.
2751 const OUString& rRelPath = maRelativeName;
2752 INetURLObject aBaseURL(rOwnDocName);
2753 aBaseURL.insertName(u"content.xml");
2754 bool bWasAbs = false;
2755 maRealFileName = aBaseURL.smartRel2Abs(rRelPath, bWasAbs).GetMainURL(INetURLObject::DecodeMechanism::NONE);
2758 void ScExternalRefManager::maybeCreateRealFileName(sal_uInt16 nFileId)
2760 if (nFileId >= maSrcFiles.size())
2761 return;
2763 maSrcFiles[nFileId].maybeCreateRealFileName(getOwnDocumentName());
2766 OUString ScExternalRefManager::getOwnDocumentName() const
2768 if (comphelper::IsFuzzing())
2769 return u"file:///tmp/document"_ustr;
2771 ScDocShell* pShell = mrDoc.GetDocumentShell();
2772 if (!pShell)
2773 // This should not happen!
2774 return OUString();
2776 SfxMedium* pMed = pShell->GetMedium();
2777 if (!pMed)
2778 return OUString();
2780 return pMed->GetName();
2783 bool ScExternalRefManager::isOwnDocument(std::u16string_view rFile) const
2785 return getOwnDocumentName() == rFile;
2788 void ScExternalRefManager::convertToAbsName(OUString& rFile) const
2790 // unsaved documents have no AbsName
2791 ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false));
2792 while (pShell)
2794 if (rFile == pShell->GetName())
2795 return;
2797 pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false));
2800 ScDocShell* pDocShell = mrDoc.GetDocumentShell();
2801 rFile = ScGlobal::GetAbsDocName(rFile, pDocShell);
2804 sal_uInt16 ScExternalRefManager::getExternalFileId(const OUString& rFile)
2806 vector<SrcFileData>::const_iterator itrBeg = maSrcFiles.begin(), itrEnd = maSrcFiles.end();
2807 vector<SrcFileData>::const_iterator itr = find_if(itrBeg, itrEnd, FindSrcFileByName(rFile));
2808 if (itr != itrEnd)
2810 size_t nId = distance(itrBeg, itr);
2811 return static_cast<sal_uInt16>(nId);
2814 SrcFileData aData;
2815 aData.maFileName = rFile;
2816 maSrcFiles.push_back(aData);
2817 return static_cast<sal_uInt16>(maSrcFiles.size() - 1);
2820 const OUString* ScExternalRefManager::getExternalFileName(sal_uInt16 nFileId, bool bForceOriginal)
2822 if (nFileId >= maSrcFiles.size())
2823 return nullptr;
2825 if (bForceOriginal)
2826 return &maSrcFiles[nFileId].maFileName;
2828 maybeCreateRealFileName(nFileId);
2830 if (!maSrcFiles[nFileId].maRealFileName.isEmpty())
2831 return &maSrcFiles[nFileId].maRealFileName;
2833 return &maSrcFiles[nFileId].maFileName;
2836 sal_uInt16 ScExternalRefManager::convertFileIdToUsedFileId(sal_uInt16 nFileId)
2838 if (!mbSkipUnusedFileIds)
2839 return nFileId;
2840 else
2841 return maConvertFileIdToUsedFileId[nFileId];
2844 void ScExternalRefManager::setSkipUnusedFileIds(std::vector<sal_uInt16>& rExternFileIds)
2846 mbSkipUnusedFileIds = true;
2847 maConvertFileIdToUsedFileId.resize(maSrcFiles.size());
2848 std::fill(maConvertFileIdToUsedFileId.begin(), maConvertFileIdToUsedFileId.end(), 0);
2849 int nUsedCount = 0;
2850 for (auto nEntry : rExternFileIds)
2852 maConvertFileIdToUsedFileId[nEntry] = nUsedCount++;
2856 void ScExternalRefManager::disableSkipUnusedFileIds()
2858 mbSkipUnusedFileIds = false;
2861 std::vector<OUString> ScExternalRefManager::getAllCachedExternalFileNames() const
2863 std::vector<OUString> aNames;
2864 aNames.reserve(maSrcFiles.size());
2865 for (const SrcFileData& rData : maSrcFiles)
2867 aNames.push_back(rData.maFileName);
2870 return aNames;
2873 bool ScExternalRefManager::hasExternalFile(sal_uInt16 nFileId) const
2875 return nFileId < maSrcFiles.size();
2878 bool ScExternalRefManager::hasExternalFile(const OUString& rFile) const
2880 return ::std::any_of(maSrcFiles.begin(), maSrcFiles.end(), FindSrcFileByName(rFile));
2883 const ScExternalRefManager::SrcFileData* ScExternalRefManager::getExternalFileData(sal_uInt16 nFileId) const
2885 if (nFileId >= maSrcFiles.size())
2886 return nullptr;
2888 return &maSrcFiles[nFileId];
2891 const OUString* ScExternalRefManager::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const
2893 return maRefCache.getRealTableName(nFileId, rTabName);
2896 const OUString* ScExternalRefManager::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const
2898 return maRefCache.getRealRangeName(nFileId, rRangeName);
2901 template<typename MapContainer>
2902 static void lcl_removeByFileId(sal_uInt16 nFileId, MapContainer& rMap)
2904 typename MapContainer::iterator itr = rMap.find(nFileId);
2905 if (itr != rMap.end())
2907 // Close this document shell.
2908 itr->second.maShell->DoClose();
2909 rMap.erase(itr);
2913 void ScExternalRefManager::clearCache(sal_uInt16 nFileId)
2915 maRefCache.clearCache(nFileId);
2918 namespace {
2920 class RefCacheFiller : public sc::ColumnSpanSet::ColumnAction
2922 svl::SharedStringPool& mrStrPool;
2924 ScExternalRefCache& mrRefCache;
2925 ScExternalRefCache::TableTypeRef mpRefTab;
2926 sal_uInt16 mnFileId;
2927 ScColumn* mpCurCol;
2928 sc::ColumnBlockConstPosition maBlockPos;
2930 public:
2931 RefCacheFiller( svl::SharedStringPool& rStrPool, ScExternalRefCache& rRefCache, sal_uInt16 nFileId ) :
2932 mrStrPool(rStrPool), mrRefCache(rRefCache), mnFileId(nFileId), mpCurCol(nullptr) {}
2934 virtual void startColumn( ScColumn* pCol ) override
2936 mpCurCol = pCol;
2937 if (!mpCurCol)
2938 return;
2940 mpCurCol->InitBlockPosition(maBlockPos);
2941 mpRefTab = mrRefCache.getCacheTable(mnFileId, mpCurCol->GetTab());
2944 virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
2946 if (!mpCurCol || !bVal)
2947 return;
2949 if (!mpRefTab)
2950 return;
2952 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
2954 ScExternalRefCache::TokenRef pTok;
2955 ScRefCellValue aCell = mpCurCol->GetCellValue(maBlockPos, nRow);
2956 switch (aCell.getType())
2958 case CELLTYPE_STRING:
2959 case CELLTYPE_EDIT:
2961 OUString aStr = aCell.getString(&mpCurCol->GetDoc());
2962 svl::SharedString aSS = mrStrPool.intern(aStr);
2963 pTok.reset(new formula::FormulaStringToken(std::move(aSS)));
2965 break;
2966 case CELLTYPE_VALUE:
2967 pTok.reset(new formula::FormulaDoubleToken(aCell.getDouble()));
2968 break;
2969 case CELLTYPE_FORMULA:
2971 sc::FormulaResultValue aRes = aCell.getFormula()->GetResult();
2972 switch (aRes.meType)
2974 case sc::FormulaResultValue::Value:
2975 pTok.reset(new formula::FormulaDoubleToken(aRes.mfValue));
2976 break;
2977 case sc::FormulaResultValue::String:
2979 // Re-intern the string to the host document pool.
2980 svl::SharedString aInterned = mrStrPool.intern(aRes.maString.getString());
2981 pTok.reset(new formula::FormulaStringToken(std::move(aInterned)));
2983 break;
2984 case sc::FormulaResultValue::Error:
2985 case sc::FormulaResultValue::Invalid:
2986 default:
2987 pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
2990 break;
2991 default:
2992 pTok.reset(new FormulaErrorToken(FormulaError::NoValue));
2995 if (pTok)
2997 // Cache this cell.
2998 mpRefTab->setCell(mpCurCol->GetCol(), nRow, pTok, mpCurCol->GetNumberFormat(mpCurCol->GetDoc().GetNonThreadedContext(), nRow));
2999 mpRefTab->setCachedCell(mpCurCol->GetCol(), nRow);
3007 bool ScExternalRefManager::refreshSrcDocument(sal_uInt16 nFileId)
3009 SfxObjectShellRef xDocShell;
3012 OUString aFilter;
3013 xDocShell = loadSrcDocument(nFileId, aFilter);
3015 catch ( const css::uno::Exception& ) {}
3017 if (!xDocShell.is())
3018 // Failed to load the document. Bail out.
3019 return false;
3021 ScDocShell& rDocSh = static_cast<ScDocShell&>(*xDocShell);
3022 ScDocument& rSrcDoc = rDocSh.GetDocument();
3024 sc::ColumnSpanSet aCachedArea;
3025 maRefCache.getAllCachedDataSpans(rSrcDoc, nFileId, aCachedArea);
3027 // Clear the existing cache, and refill it. Make sure we keep the
3028 // existing cache table instances here.
3029 maRefCache.clearCacheTables(nFileId);
3030 RefCacheFiller aAction(mrDoc.GetSharedStringPool(), maRefCache, nFileId);
3031 aCachedArea.executeColumnAction(rSrcDoc, aAction);
3033 DocShellMap::iterator it = maDocShells.find(nFileId);
3034 if (it != maDocShells.end())
3036 it->second.maShell->DoClose();
3037 it->second.maShell = std::move(xDocShell);
3038 it->second.maLastAccess = tools::Time(tools::Time::SYSTEM);
3040 else
3042 SrcShell aSrcDoc;
3043 aSrcDoc.maShell = std::move(xDocShell);
3044 aSrcDoc.maLastAccess = tools::Time(tools::Time::SYSTEM);
3045 cacheNewDocShell(nFileId, aSrcDoc);
3048 // Update all cells containing names from this source document.
3049 refreshAllRefCells(nFileId);
3051 notifyAllLinkListeners(nFileId, LINK_MODIFIED);
3053 return true;
3056 void ScExternalRefManager::breakLink(sal_uInt16 nFileId)
3058 // Turn all formula cells referencing this external document into static
3059 // cells.
3060 RefCellMap::iterator itrRefs = maRefCells.find(nFileId);
3061 if (itrRefs != maRefCells.end())
3063 // Make a copy because removing the formula cells below will modify
3064 // the original container.
3065 RefCellSet aSet = itrRefs->second;
3066 for_each(aSet.begin(), aSet.end(), ConvertFormulaToStatic(&mrDoc));
3067 maRefCells.erase(nFileId);
3070 // Remove all named ranges that reference this document.
3072 // Global named ranges.
3073 ScRangeName* pRanges = mrDoc.GetRangeName();
3074 if (pRanges)
3075 removeRangeNamesBySrcDoc(*pRanges, nFileId);
3077 // Sheet-local named ranges.
3078 for (SCTAB i = 0, n = mrDoc.GetTableCount(); i < n; ++i)
3080 pRanges = mrDoc.GetRangeName(i);
3081 if (pRanges)
3082 removeRangeNamesBySrcDoc(*pRanges, nFileId);
3085 clearCache(nFileId);
3086 lcl_removeByFileId(nFileId, maDocShells);
3088 if (maDocShells.empty())
3089 maSrcDocTimer.Stop();
3091 LinkedDocMap::iterator itr = maLinkedDocs.find(nFileId);
3092 if (itr != maLinkedDocs.end())
3093 itr->second = false;
3095 notifyAllLinkListeners(nFileId, LINK_BROKEN);
3098 void ScExternalRefManager::switchSrcFile(sal_uInt16 nFileId, const OUString& rNewFile, const OUString& rNewFilter)
3100 maSrcFiles[nFileId].maFileName = rNewFile;
3101 maSrcFiles[nFileId].maRelativeName.clear();
3102 maSrcFiles[nFileId].maRealFileName.clear();
3103 if (maSrcFiles[nFileId].maFilterName != rNewFilter)
3105 // Filter type has changed.
3106 maSrcFiles[nFileId].maFilterName = rNewFilter;
3107 maSrcFiles[nFileId].maFilterOptions.clear();
3109 refreshSrcDocument(nFileId);
3112 void ScExternalRefManager::setRelativeFileName(sal_uInt16 nFileId, const OUString& rRelUrl)
3114 if (nFileId >= maSrcFiles.size())
3115 return;
3116 maSrcFiles[nFileId].maRelativeName = rRelUrl;
3119 void ScExternalRefManager::setFilterData(sal_uInt16 nFileId, const OUString& rFilterName, const OUString& rOptions)
3121 if (nFileId >= maSrcFiles.size())
3122 return;
3123 maSrcFiles[nFileId].maFilterName = rFilterName;
3124 maSrcFiles[nFileId].maFilterOptions = rOptions;
3127 void ScExternalRefManager::clear()
3129 for (auto& rEntry : maLinkListeners)
3131 for (auto& it : rEntry.second)
3133 it->notify(0, OH_NO_WE_ARE_GOING_TO_DIE);
3137 for (auto& rEntry : maDocShells)
3138 rEntry.second.maShell->DoClose();
3140 maDocShells.clear();
3141 maSrcDocTimer.Stop();
3144 bool ScExternalRefManager::hasExternalData() const
3146 return !maSrcFiles.empty();
3149 void ScExternalRefManager::resetSrcFileData(const OUString& rBaseFileUrl)
3151 for (auto& rSrcFile : maSrcFiles)
3153 // Re-generate relative file name from the absolute file name.
3154 OUString aAbsName = rSrcFile.maRealFileName;
3155 if (aAbsName.isEmpty())
3156 aAbsName = rSrcFile.maFileName;
3158 rSrcFile.maRelativeName = URIHelper::simpleNormalizedMakeRelative(
3159 rBaseFileUrl, aAbsName);
3163 void ScExternalRefManager::updateAbsAfterLoad()
3165 OUString aOwn( getOwnDocumentName() );
3166 for (auto& rSrcFile : maSrcFiles)
3168 // update maFileName to the real file name,
3169 // to be called when the original name is no longer needed (after CompileXML)
3171 rSrcFile.maybeCreateRealFileName( aOwn );
3172 OUString aReal = rSrcFile.maRealFileName;
3173 if (!aReal.isEmpty())
3174 rSrcFile.maFileName = aReal;
3178 void ScExternalRefManager::removeRefCell(ScFormulaCell* pCell)
3180 for_each(maRefCells.begin(), maRefCells.end(), RemoveFormulaCell(pCell));
3183 void ScExternalRefManager::addLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
3185 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3186 if (itr == maLinkListeners.end())
3188 pair<LinkListenerMap::iterator, bool> r = maLinkListeners.emplace(
3189 nFileId, LinkListeners());
3190 if (!r.second)
3192 OSL_FAIL("insertion of new link listener list failed");
3193 return;
3196 itr = r.first;
3199 LinkListeners& rList = itr->second;
3200 rList.insert(pListener);
3203 void ScExternalRefManager::removeLinkListener(sal_uInt16 nFileId, LinkListener* pListener)
3205 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3206 if (itr == maLinkListeners.end())
3207 // no listeners for a specified file.
3208 return;
3210 LinkListeners& rList = itr->second;
3211 rList.erase(pListener);
3213 if (rList.empty())
3214 // No more listeners for this file. Remove its entry.
3215 maLinkListeners.erase(itr);
3218 void ScExternalRefManager::removeLinkListener(LinkListener* pListener)
3220 for (auto& rEntry : maLinkListeners)
3221 rEntry.second.erase(pListener);
3224 void ScExternalRefManager::notifyAllLinkListeners(sal_uInt16 nFileId, LinkUpdateType eType)
3226 LinkListenerMap::iterator itr = maLinkListeners.find(nFileId);
3227 if (itr == maLinkListeners.end())
3228 // no listeners for a specified file.
3229 return;
3231 LinkListeners& rList = itr->second;
3232 for_each(rList.begin(), rList.end(), NotifyLinkListener(nFileId, eType));
3235 void ScExternalRefManager::purgeStaleSrcDocument(sal_Int32 nTimeOut)
3237 // To avoid potentially freezing Calc, we close one stale document at a time.
3238 DocShellMap::iterator itr = std::find_if(maDocShells.begin(), maDocShells.end(),
3239 [nTimeOut](const DocShellMap::value_type& rEntry) {
3240 // in 100th of a second.
3241 sal_Int32 nSinceLastAccess = (tools::Time( tools::Time::SYSTEM ) - rEntry.second.maLastAccess).GetTime();
3242 return nSinceLastAccess >= nTimeOut;
3244 if (itr != maDocShells.end())
3246 // Timed out. Let's close this.
3247 itr->second.maShell->DoClose();
3248 maDocShells.erase(itr);
3251 if (maDocShells.empty())
3252 maSrcDocTimer.Stop();
3255 sal_uInt32 ScExternalRefManager::getMappedNumberFormat(sal_uInt16 nFileId, sal_uInt32 nNumFmt, const ScDocument& rSrcDoc)
3257 NumFmtMap::iterator itr = maNumFormatMap.find(nFileId);
3258 if (itr == maNumFormatMap.end())
3260 // Number formatter map is not initialized for this external document.
3261 pair<NumFmtMap::iterator, bool> r = maNumFormatMap.emplace(
3262 nFileId, SvNumberFormatterMergeMap());
3264 if (!r.second)
3265 // insertion failed.
3266 return nNumFmt;
3268 itr = r.first;
3269 mrDoc.GetFormatTable()->MergeFormatter(*rSrcDoc.GetFormatTable());
3270 SvNumberFormatterMergeMap aMap = mrDoc.GetFormatTable()->ConvertMergeTableToMap();
3271 itr->second.swap(aMap);
3273 const SvNumberFormatterMergeMap& rMap = itr->second;
3274 SvNumberFormatterMergeMap::const_iterator itrNumFmt = rMap.find(nNumFmt);
3275 if (itrNumFmt != rMap.end())
3276 // mapped value found.
3277 return itrNumFmt->second;
3279 return nNumFmt;
3282 void ScExternalRefManager::transformUnsavedRefToSavedRef( SfxObjectShell* pShell )
3284 DocShellMap::iterator itr = maUnsavedDocShells.begin();
3285 while( itr != maUnsavedDocShells.end() )
3287 if ( itr->second.maShell.get() == pShell )
3289 // found that the shell is marked as unsaved
3290 OUString aFileURL = pShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
3291 switchSrcFile(itr->first, aFileURL, OUString());
3292 EndListening(*pShell);
3293 itr = maUnsavedDocShells.erase(itr);
3295 else
3296 ++itr;
3300 void ScExternalRefManager::Notify( SfxBroadcaster&, const SfxHint& rHint )
3302 if (rHint.GetId() != SfxHintId::ThisIsAnSfxEventHint)
3303 return;
3305 switch (static_cast<const SfxEventHint&>(rHint).GetEventId())
3307 case SfxEventHintId::PrepareCloseDoc:
3309 std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
3310 VclMessageType::Warning, VclButtonsType::Ok,
3311 ScResId(STR_CLOSE_WITH_UNSAVED_REFS)));
3312 xWarn->run();
3314 break;
3315 case SfxEventHintId::SaveDocDone:
3316 case SfxEventHintId::SaveAsDocDone:
3318 rtl::Reference<SfxObjectShell> pObjShell = static_cast<const SfxEventHint&>( rHint ).GetObjShell();
3319 transformUnsavedRefToSavedRef(pObjShell.get());
3321 break;
3322 default:
3323 break;
3327 IMPL_LINK(ScExternalRefManager, TimeOutHdl, Timer*, pTimer, void)
3329 if (pTimer == &maSrcDocTimer)
3330 purgeStaleSrcDocument(SRCDOC_LIFE_SPAN);
3333 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */