1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
23 #include <tokenarray.hxx>
24 #include <address.hxx>
25 #include <tablink.hxx>
27 #include <scextopt.hxx>
28 #include <rangenam.hxx>
29 #include <formulacell.hxx>
31 #include <viewdata.hxx>
32 #include <tabvwsh.hxx>
34 #include <globstr.hrc>
35 #include <scresid.hxx>
36 #include <cellvalue.hxx>
37 #include <defaultsoptions.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>
65 #include <com/sun/star/document/MacroExecMode.hpp>
66 #include <com/sun/star/document/UpdateDocMode.hpp>
67 #include <sal/log.hxx>
72 using ::std::unique_ptr
;
73 using ::com::sun::star::uno::Any
;
76 using ::std::for_each
;
77 using ::std::distance
;
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)
86 class TabNameSearchPredicate
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
;
101 OUString maSearchName
;
104 class FindSrcFileByName
107 explicit FindSrcFileByName(const OUString
& rMatchName
) :
108 mrMatchName(rMatchName
)
112 bool operator()(const ScExternalRefManager::SrcFileData
& rSrcData
) const
114 return rSrcData
.maFileName
== mrMatchName
;
118 const OUString
& mrMatchName
;
121 class NotifyLinkListener
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
);
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
))
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();
160 class RemoveFormulaCell
163 explicit RemoveFormulaCell(ScFormulaCell
* p
) : mpCell(p
) {}
164 void operator() (pair
<const sal_uInt16
, ScExternalRefManager::RefCellSet
>& r
) const
166 r
.second
.erase(mpCell
);
169 ScFormulaCell
* mpCell
;
172 class ConvertFormulaToStatic
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());
190 // string cell otherwise.
191 ScSetStringParam aParam
;
192 aParam
.setTextInput();
193 mpDoc
->SetString(aPos
, pCell
->GetString().getString(), &aParam
);
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();
210 formula::FormulaTokenArrayPlainIterator
aIter(*pArray
);
211 formula::FormulaToken
* p
= aIter
.GetNextReference();
212 for (; p
; p
= aIter
.GetNextReference())
214 if (!p
->IsExternalRef())
217 if (p
->GetIndex() == nFileId
)
223 class EraseRangeByIterator
225 ScRangeName
& mrRanges
;
227 explicit EraseRangeByIterator(ScRangeName
& rRanges
) : mrRanges(rRanges
) {}
228 void operator() (const ScRangeName::const_iterator
& itr
)
235 * Remove all named ranges that contain references to specified source
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
))
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()
265 maCachedRanges
.RemoveAll();
269 void ScExternalRefCache::Table::setReferenced( bool bReferenced
)
271 mbReferenced
= bReferenced
;
274 bool ScExternalRefCache::Table::isReferenced() const
279 void ScExternalRefCache::Table::setCell(SCCOL nCol
, SCROW nRow
, TokenRef
const & pToken
, sal_uLong nFmtIndex
, bool bSetCacheRange
)
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());
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
);
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
;
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
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());
350 void ScExternalRefCache::Table::getAllRows(vector
<SCROW
>& rRows
, SCROW nLow
, SCROW nHigh
) const
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;
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.
383 const RowDataType
& rRowData
= itrRow
->second
;
385 aCols
.reserve(rRowData
.size());
386 for (const auto& rCol
: rRowData
)
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());
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.
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;
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));
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
)
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.
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.
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.
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.
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.
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.
557 const TableTypeRef
& pTableData
= rDoc
.maTables
[itrTabId
->second
];
560 // the table data is not instantiated yet.
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())
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
];
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.
629 pTab
->getAllRows(aRows
, nDataRow1
, nDataRow2
);
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
)
647 // There's no data at all. Set the one matrix element to empty
648 // for column-repeated and row-repeated access.
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
)
660 pTab
->getAllCols(rRow
, aCols
, nDataCol1
, nDataCol2
);
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
)
687 // Only fill non-empty cells, for better performance.
688 for (SCROW nRow
: aRows
)
691 pTab
->getAllCols(nRow
, aCols
, nDataCol1
, nDataCol2
);
692 for (SCCOL nCol
: aCols
)
694 TokenRef pToken
= pTab
->getCell(nCol
, nRow
);
696 // This should never happen!
697 return TokenArrayRef();
699 SCSIZE nC
= nCol
- nDataCol1
, nR
= nRow
- nDataRow1
;
700 switch (pToken
->GetType())
703 xMat
->PutDouble(pToken
->GetDouble(), nC
, nR
);
706 xMat
->PutString(pToken
->GetString(), nC
, nR
);
715 pArray
->AddOpCode(ocSep
);
717 ScMatrixToken
aToken(std::move(xMat
));
719 pArray
= std::make_shared
<ScTokenArray
>(mrDoc
);
720 pArray
->AddToken(aToken
);
725 pNewRange
.reset(new ScRange(nDataCol1
, nDataRow1
, nTab
, nDataCol2
, nDataRow2
, nTab
));
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
);
738 ScExternalRefCache::TokenArrayRef
ScExternalRefCache::getRangeNameTokens(sal_uInt16 nFileId
, const OUString
& rName
)
740 std::unique_lock
aGuard(maMtxDocs
);
742 DocItem
* pDoc
= getDocItem(aGuard
, nFileId
);
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();
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
);
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
);
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
);
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
))
801 DocItem
* pDocItem
= getDocItem(nFileId
);
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 ???
813 TableTypeRef
& pTableData
= rDoc
.maTables
[itrTabName
->second
];
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
)
825 if (rData
.empty() || !isDocInitialized(nFileId
))
829 // First, get the document item for the given file ID.
830 DocItem
* pDocItem
= getDocItem(nFileId
);
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.
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
];
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
);
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");
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"));
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
);
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())
920 rIndex
= itr
->second
;
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())
930 rIndex
= itr
->second
;
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
);
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
968 vector
<TableTypeRef
> aNewTables(n
);
969 for (size_t i
= 0; i
< n
; ++i
)
972 if (lcl_getStrictTableDataIndex(pDoc
->maTableNameIndex
, pDoc
->maTableNames
[i
].maUpperName
, nIndex
))
974 aNewTables
[i
] = pDoc
->maTables
[nIndex
];
977 pDoc
->maTables
.swap(aNewTables
);
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())
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)
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();
1026 bool ScExternalRefCache::DocItem::getSingleTableNameAlternative( OUString
& rTabName
) const
1028 if (maSingleTableNameAlias
.isEmpty() || maTableNames
.size() != 1)
1030 if (ScGlobal::GetTransliteration().isEqual( rTabName
, maTableNames
[0].maRealName
))
1032 rTabName
= maSingleTableNameAlias
;
1035 if (ScGlobal::GetTransliteration().isEqual( rTabName
, maSingleTableNameAlias
))
1037 rTabName
= maTableNames
[0].maRealName
;
1043 bool ScExternalRefCache::getSrcDocTable( const ScDocument
& rSrcDoc
, const OUString
& rTabName
, SCTAB
& rTab
,
1044 sal_uInt16 nFileId
) const
1046 bool bFound
= rSrcDoc
.GetTable( rTabName
, rTab
);
1049 // Check the one table alias alternative.
1050 const DocItem
* pDoc
= getDocItem( nFileId
);
1053 OUString
aTabName( rTabName
);
1054 if (pDoc
->getSingleTableNameAlternative( aTabName
))
1055 bFound
= rSrcDoc
.GetTable( aTabName
, rTab
);
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
;
1069 void ScExternalRefCache::getAllTableNames(sal_uInt16 nFileId
, vector
<OUString
>& rTabNames
) const
1072 DocItem
* pDoc
= getDocItem(nFileId
);
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
);
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
)
1096 vector
<TableName
>::const_iterator itrEndTab
= ::std::find_if( itrBeg
, itrEnd
,
1097 TabNameSearchPredicate( rEndTabName
));
1098 if (itrEndTab
== itrEnd
)
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
);
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
)
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
);
1136 return areAllCacheTablesReferenced();
1138 for (auto& rxTab
: pDocItem
->maTables
)
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
);
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
];
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
);
1179 maReferenced
.reset(0);
1180 for (auto& rEntry
: maDocs
)
1182 ScExternalRefCache::DocItem
& rDocItem
= rEntry
.second
;
1183 for (auto& rxTab
: rDocItem
.maTables
)
1186 rxTab
->setReferenced(true);
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
];
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())
1227 ::std::vector
<bool> & rTables
= maReferenced
.maDocs
[nFileId
].maTables
;
1228 size_t nTables
= rTables
.size();
1229 if (nIndex
>= nTables
)
1232 if (!rTables
[nIndex
])
1234 rTables
[nIndex
] = true;
1236 while (i
< nTables
&& rTables
[i
])
1240 maReferenced
.maDocs
[nFileId
].mbAllTablesReferenced
= true;
1241 maReferenced
.checkAllDocs();
1246 void ScExternalRefCache::addCacheDocToReferenced( sal_uInt16 nFileId
)
1248 if (nFileId
>= maReferenced
.maDocs
.size())
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
)
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
);
1266 // This document is not cached.
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
];
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)
1296 void ScExternalRefCache::ReferencedStatus::reset( size_t nDocs
)
1300 mbAllReferenced
= false;
1301 DocReferencedVec
aRefs( nDocs
);
1302 maDocs
.swap( aRefs
);
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
1334 const size_t nNotAvailable
= static_cast<size_t>( static_cast<sal_Int32
>( -1));
1336 DocItem
* pDoc
= getDocItem(nFileId
);
1339 if (pnIndex
) *pnIndex
= nNotAvailable
;
1340 return TableTypeRef();
1343 DocItem
& rDoc
= *pDoc
;
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
];
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())
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
;
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
);
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
);
1403 // This document is not cached at all.
1406 // Clear all cache table content, but keep the tables.
1407 std::vector
<TableTypeRef
>& rTabs
= pDocItem
->maTables
;
1408 for (TableTypeRef
& pTab
: rTabs
)
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
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());
1440 // insertion failed.
1446 return &itrDoc
->second
;
1449 ScExternalRefLink::ScExternalRefLink(ScDocument
& rDoc
, sal_uInt16 nFileId
) :
1450 ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL
, SotClipboardFormatId::SIMPLE_FILE
),
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*/)
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
);
1481 return ERROR_GENERAL
;
1483 if (*pCurFile
== aFile
)
1485 // Refresh the current source document.
1486 if (!pMgr
->refreshSrcDocument(mnFileId
))
1487 return ERROR_GENERAL
;
1491 // The source document has changed.
1492 ScViewData
* pViewData
= ScDocShell::GetViewData();
1494 return ERROR_GENERAL
;
1496 ScDocShell
* pDocShell
= pViewData
->GetDocShell();
1497 ScDocShellModificator
aMod(*pDocShell
);
1498 pMgr
->switchSrcFile(mnFileId
, aFile
, aFilter
);
1499 aMod
.SetDocumentModified();
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
)
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())
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
);
1547 svl::SharedString aSS
= rHostDoc
.GetSharedStringPool().intern( pFCell
->GetString().getString());
1548 return new formula::FormulaStringToken(std::move(aSS
));
1552 OSL_FAIL("attempted to convert an unknown cell type.");
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();
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
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
;
1589 if (!rSrcDoc
.ShrinkToUsedDataArea( bShrunk
, nTab
, nDataCol1
, nDataRow1
, nDataCol2
, nDataRow2
, false))
1590 // no data within specified range.
1594 // Make sure the used area only grows, not shrinks.
1595 pUsedRange
->ExtendTo(ScRange(nDataCol1
, nDataRow1
, 0, nDataCol2
, nDataRow2
, 0));
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());
1632 pArray
->AddOpCode(ocSep
);
1634 ScMatrixToken
aToken(xMat
);
1635 pArray
->AddToken(aToken
);
1637 itrCache
->mpRangeData
= std::move(xMat
);
1645 s
.SetCol(pUsedRange
->aStart
.Col());
1646 s
.SetRow(pUsedRange
->aStart
.Row());
1647 e
.SetCol(pUsedRange
->aEnd
.Col());
1648 e
.SetRow(pUsedRange
->aEnd
.Row());
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
);
1666 bool isLinkUpdateAllowedInDoc(const ScDocument
& rDoc
)
1668 ScDocShell
* pDocShell
= rDoc
.GetDocumentShell();
1670 return rDoc
.IsFunctionAccess();
1672 return pDocShell
->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate();
1676 ScExternalRefManager::ScExternalRefManager(ScDocument
& 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()
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
);
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();
1775 // Return true when at least one cell references external docs.
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())
1808 const ScSingleRefData
& rRef
= *pToken
->GetSingleRef();
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
);
1820 const ScSingleRefData
& rRef
= *pToken
->GetSingleRef();
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
);
1835 pNewArray
->AddToken(*pToken
);
1839 pNewArray
= rArray
.Clone();
1841 maRefCache
.setRangeNameTokens(nFileId
, rName
, pNewArray
);
1847 * Put a single cell data into internal cache table.
1849 * @param pFmt optional cell format index that may need to be stored with
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
1875 * @param rDataRange reduced cache range that includes only the non-empty
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
)
1885 // Cache these values.
1886 rRefCache
.setCellRangeData(nFileId
, rDataRange
, rCacheData
, pArray
);
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);
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
)
1917 if (rRefCache
.isDocInitialized(nFileId
))
1918 // Already initialized. No need to do this twice.
1921 SCTAB nTabCount
= pSrcDoc
->GetTableCount();
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
)
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.
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
)
1963 insertRefCell(nFileId
, *pCurPos
);
1965 maybeLinkExternalFile(nFileId
);
1971 pFmt
->mbIsSet
= false;
1973 ScDocument
* pSrcDoc
= getInMemorySrcDocument(nFileId
);
1976 // source document already loaded in memory. Re-use this instance.
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
));
1988 ScExternalRefCache::TokenRef pToken
=
1989 getSingleRefTokenFromSrcDoc(
1990 nFileId
, *pSrcDoc
, ScAddress(rCell
.Col(),rCell
.Row(),nTab
), pFmt
);
1992 putCellDataIntoCache(maRefCache
, pToken
, nFileId
, rTabName
, rCell
, pFmt
);
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
);
2003 fillCellFormat(nFmtIndex
, pFmt
);
2007 // reference not cached. read from the source document.
2008 pSrcDoc
= getSrcDocument(nFileId
);
2011 // Source document not reachable.
2012 if (!isLinkUpdateAllowedInDoc(mrDoc
))
2014 // Indicate with specific error.
2015 pToken
.reset(new FormulaErrorToken(FormulaError::LinkFormulaNeedingCheck
));
2019 // Throw a reference error.
2020 pToken
.reset(new FormulaErrorToken(FormulaError::NoRef
));
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
));
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);
2047 pCacheTab
->setCachedCell(rCell
.Col(), rCell
.Row());
2049 pToken
.reset(new ScEmptyCellToken(false, false));
2053 pToken
= getSingleRefTokenFromSrcDoc(
2054 nFileId
, *pSrcDoc
, ScAddress(rCell
.Col(),rCell
.Row(),nTab
), pFmt
);
2056 putCellDataIntoCache(maRefCache
, pToken
, nFileId
, rTabName
, rCell
, pFmt
);
2060 ScExternalRefCache::TokenArrayRef
ScExternalRefManager::getDoubleRefTokens(
2061 sal_uInt16 nFileId
, const OUString
& rTabName
, const ScRange
& rRange
, const ScAddress
* pCurPos
)
2064 insertRefCell(nFileId
, *pCurPos
);
2066 maybeLinkExternalFile(nFileId
);
2068 ScRange
aDataRange(rRange
);
2069 ScDocument
* pSrcDoc
= getInMemorySrcDocument(nFileId
);
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
);
2082 // Check if the given table name and the cell position is cached.
2083 ScExternalRefCache::TokenArrayRef pArray
=
2084 maRefCache
.getCellRangeData(nFileId
, rTabName
, rRange
);
2089 pSrcDoc
= getSrcDocument(nFileId
);
2092 // Source document is not reachable. Throw a reference error.
2093 pArray
= std::make_shared
<ScTokenArray
>(maRefCache
.getDoc());
2094 pArray
->AddToken(FormulaErrorToken(FormulaError::NoRef
));
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
);
2106 ScExternalRefCache::TokenArrayRef
ScExternalRefManager::getRangeNameTokens(
2107 sal_uInt16 nFileId
, const OUString
& rName
, const ScAddress
* 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
);
2118 // Document already loaded in memory.
2119 ScExternalRefCache::TokenArrayRef pArray
=
2120 getRangeNameTokensFromSrcDoc(nFileId
, *pSrcDoc
, aName
);
2123 // Cache this range name array.
2124 maRefCache
.setRangeNameTokens(nFileId
, aName
, pArray
);
2129 ScExternalRefCache::TokenArrayRef pArray
= maRefCache
.getRangeNameTokens(nFileId
, rName
);
2131 // This range name is cached.
2134 pSrcDoc
= getSrcDocument(nFileId
);
2136 // failed to load document from disk.
2137 return ScExternalRefCache::TokenArrayRef();
2139 pArray
= getRangeNameTokensFromSrcDoc(nFileId
, *pSrcDoc
, aName
);
2142 // Cache this range name array.
2143 maRefCache
.setRangeNameTokens(nFileId
, aName
, pArray
);
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
);
2166 // Only check the presence of the name.
2167 if (hasRangeName(*pSrcDoc
, rName
))
2169 maRefCache
.setRangeName(nFileId
, rName
);
2175 if (maRefCache
.isValidRangeName(nFileId
, rName
))
2176 // Range name is cached.
2179 pSrcDoc
= getSrcDocument(nFileId
);
2181 // failed to load document from disk.
2184 if (hasRangeName(*pSrcDoc
, rName
))
2186 maRefCache
.setRangeName(nFileId
, rName
);
2193 void ScExternalRefManager::refreshAllRefCells(sal_uInt16 nFileId
)
2195 RefCellMap::iterator itrFile
= maRefCells
.find(nFileId
);
2196 if (itrFile
== maRefCells
.end())
2199 RefCellSet
& rRefCells
= itrFile
->second
;
2200 for_each(rRefCells
.begin(), rRefCells
.end(), UpdateFormulaCell());
2202 ScViewData
* pViewData
= ScDocShell::GetViewData();
2206 ScTabViewShell
* pVShell
= pViewData
->GetViewShell();
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();
2218 void insertRefCellByIterator(
2219 const ScExternalRefManager::RefCellMap::iterator
& itr
, ScFormulaCell
* 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
);
2239 // insertion failed.
2245 insertRefCellByIterator(itr
, mrDoc
.GetFormulaCell(rCell
));
2248 void ScExternalRefManager::insertRefCellFromTemplate( ScFormulaCell
* pTemplateCell
, ScFormulaCell
* pCell
)
2250 if (!pTemplateCell
|| !pCell
)
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
);
2265 return std::any_of(maRefCells
.begin(), maRefCells
.end(),
2266 [&pCell
](const RefCellMap::value_type
& rEntry
) { return rEntry
.second
.find(pCell
) != rEntry
.second
.end(); });
2271 void ScExternalRefManager::enableDocTimer( bool bEnable
)
2273 if (mbDocTimerEnabled
== bEnable
)
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();
2288 maSrcDocTimer
.Stop();
2291 void ScExternalRefManager::fillCellFormat(sal_uLong nFmtIndex
, ScExternalRefCache::CellFormat
* pFmt
) const
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
));
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
);
2326 ScExternalRefCache::TokenArrayRef
ScExternalRefManager::getDoubleRefTokensFromSrcDoc(
2327 const ScDocument
& rSrcDoc
, const OUString
& rTabName
, ScRange
& rRange
,
2328 vector
<ScExternalRefCache::SingleRangeData
>& rCacheData
)
2330 ScExternalRefCache::TokenArrayRef pArray
;
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
));
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
)
2353 if (!rSrcDoc
.GetName(nTab1
+ 1, aTabName
))
2354 // source document doesn't have any table by the specified name.
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
);
2366 rCacheData
.swap(aCacheData
);
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
);
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
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())
2395 const ScSingleRefData
& rRef
= *pToken
->GetSingleRef();
2397 rSrcDoc
.GetName(rRef
.Tab(), aTabName
);
2398 ScExternalSingleRefToken
aNewToken(nFileId
, svl::SharedString( aTabName
), // string not interned
2399 *pToken
->GetSingleRef());
2400 pNew
->AddToken(aNewToken
);
2406 const ScSingleRefData
& rRef
= *pToken
->GetSingleRef();
2408 rSrcDoc
.GetName(rRef
.Tab(), aTabName
);
2409 ScExternalDoubleRefToken
aNewToken(nFileId
, svl::SharedString( aTabName
), // string not interned
2410 *pToken
->GetDoubleRef());
2411 pNew
->AddToken(aNewToken
);
2420 pNew
->AddToken(*pToken
);
2423 rName
= pRangeData
->GetName(); // Get the correctly-cased name.
2427 ScDocument
* ScExternalRefManager::getInMemorySrcDocument(sal_uInt16 nFileId
)
2429 const OUString
* pFileName
= getExternalFileName(nFileId
);
2433 // Do not load document until it was allowed.
2434 if (!isLinkUpdateAllowedInDoc(mrDoc
))
2437 ScDocument
* pSrcDoc
= nullptr;
2438 ScDocShell
* pShell
= static_cast<ScDocShell
*>(SfxObjectShell::GetFirst(checkSfxObjectShell
<ScDocShell
>, false));
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()))
2448 pSrcDoc
= &pShell
->GetDocument();
2454 // handle unsaved documents here
2455 OUString aName
= pShell
->GetName();
2456 if (pFileName
->equalsIgnoreAsciiCase(aName
))
2460 aSrcDoc
.maShell
= pShell
;
2461 maUnsavedDocShells
.emplace(nFileId
, aSrcDoc
);
2462 StartListening(*pShell
);
2463 pSrcDoc
= &pShell
->GetDocument();
2467 pShell
= static_cast<ScDocShell
*>(SfxObjectShell::GetNext(*pShell
, checkSfxObjectShell
<ScDocShell
>, false));
2470 initDocInCache(maRefCache
, pSrcDoc
, nFileId
);
2474 ScDocument
* ScExternalRefManager::getSrcDocument(sal_uInt16 nFileId
)
2476 if (!mrDoc
.IsExecuteLinkEnabled())
2479 DocShellMap::iterator itrEnd
= maDocShells
.end();
2480 DocShellMap::iterator itr
= maDocShells
.find(nFileId
);
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
);
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
);
2504 // no file name associated with this ID.
2511 aSrcDoc
.maShell
= loadSrcDocument(nFileId
, aFilter
);
2513 catch (const css::uno::Exception
&)
2516 if (!aSrcDoc
.maShell
.is())
2518 // source document could not be loaded.
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
))
2531 const SrcFileData
* pFileData
= getExternalFileData(nFileId
);
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
2539 OUString aFile
= pFileData
->maFileName
;
2540 maybeCreateRealFileName(nFileId
);
2541 if (!pFileData
->maRealFileName
.isEmpty())
2542 aFile
= pFileData
->maRealFileName
;
2544 if (!isFileLoadable(aFile
))
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
<< "\"");
2555 OUString aOptions
= pFileData
->maFilterOptions
;
2556 if ( !pFileData
->maFilterName
.isEmpty() )
2557 rFilter
= pFileData
->maFilterName
; // don't overwrite stored filter with guessed filter
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
2588 ScDocShell
* pShell
= mrDoc
.GetDocumentShell();
2591 SfxMedium
* pMedium
= pShell
->GetMedium();
2594 const SfxUInt16Item
* pItem
= pMedium
->GetItemSet().GetItemIfSet( SID_MACROEXECMODE
, false );
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
)
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();
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();
2637 // with UseInteractionHandler, options may be set by dialog during DoLoad
2638 OUString aNew
= ScDocumentLoader::GetOptions(*pNewShell
->GetMedium());
2639 if (!aNew
.isEmpty() && aNew
!= aOptions
)
2641 setFilterData(nFileId
, rFilter
, aOptions
); // update the filter data, including the new options
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
);
2659 bool ScExternalRefManager::isFileLoadable(const OUString
& rFile
) const
2661 if (rFile
.isEmpty())
2664 if (isOwnDocument(rFile
))
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
))
2675 return utl::UCBContentHelper::Exists(rFile
);
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.
2687 // Source document not linked yet. Link it now.
2688 const OUString
* pFileName
= getExternalFileName(nFileId
);
2692 OUString aFilter
, aOptions
;
2693 const SrcFileData
* pFileData
= getExternalFileData(nFileId
);
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();
2713 SAL_WARN( "sc.ui", "ScExternalRefManager::maybeLinkExternalFile: pLinkMgr==NULL");
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);
2723 pLink
->SetDoRefresh(true);
2725 maLinkedDocs
.emplace(nFileId
, true);
2728 void ScExternalRefManager::addFilesToLinkManager()
2730 if (maSrcFiles
.empty())
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.
2746 if (!maRealFileName
.isEmpty())
2747 // Real file name already created. Nothing to do.
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())
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();
2773 // This should not happen!
2776 SfxMedium
* pMed
= pShell
->GetMedium();
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));
2794 if (rFile
== pShell
->GetName())
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
));
2810 size_t nId
= distance(itrBeg
, itr
);
2811 return static_cast<sal_uInt16
>(nId
);
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())
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
)
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);
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
);
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())
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();
2913 void ScExternalRefManager::clearCache(sal_uInt16 nFileId
)
2915 maRefCache
.clearCache(nFileId
);
2920 class RefCacheFiller
: public sc::ColumnSpanSet::ColumnAction
2922 svl::SharedStringPool
& mrStrPool
;
2924 ScExternalRefCache
& mrRefCache
;
2925 ScExternalRefCache::TableTypeRef mpRefTab
;
2926 sal_uInt16 mnFileId
;
2928 sc::ColumnBlockConstPosition maBlockPos
;
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
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
)
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
:
2961 OUString aStr
= aCell
.getString(&mpCurCol
->GetDoc());
2962 svl::SharedString aSS
= mrStrPool
.intern(aStr
);
2963 pTok
.reset(new formula::FormulaStringToken(std::move(aSS
)));
2966 case CELLTYPE_VALUE
:
2967 pTok
.reset(new formula::FormulaDoubleToken(aCell
.getDouble()));
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
));
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
)));
2984 case sc::FormulaResultValue::Error
:
2985 case sc::FormulaResultValue::Invalid
:
2987 pTok
.reset(new FormulaErrorToken(FormulaError::NoValue
));
2992 pTok
.reset(new FormulaErrorToken(FormulaError::NoValue
));
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
;
3013 xDocShell
= loadSrcDocument(nFileId
, aFilter
);
3015 catch ( const css::uno::Exception
& ) {}
3017 if (!xDocShell
.is())
3018 // Failed to load the document. Bail out.
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
);
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
);
3056 void ScExternalRefManager::breakLink(sal_uInt16 nFileId
)
3058 // Turn all formula cells referencing this external document into static
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();
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
);
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())
3116 maSrcFiles
[nFileId
].maRelativeName
= rRelUrl
;
3119 void ScExternalRefManager::setFilterData(sal_uInt16 nFileId
, const OUString
& rFilterName
, const OUString
& rOptions
)
3121 if (nFileId
>= maSrcFiles
.size())
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());
3192 OSL_FAIL("insertion of new link listener list failed");
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.
3210 LinkListeners
& rList
= itr
->second
;
3211 rList
.erase(pListener
);
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.
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());
3265 // insertion failed.
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
;
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
);
3300 void ScExternalRefManager::Notify( SfxBroadcaster
&, const SfxHint
& rHint
)
3302 if (rHint
.GetId() != SfxHintId::ThisIsAnSfxEventHint
)
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
)));
3315 case SfxEventHintId::SaveDocDone
:
3316 case SfxEventHintId::SaveAsDocDone
:
3318 rtl::Reference
<SfxObjectShell
> pObjShell
= static_cast<const SfxEventHint
&>( rHint
).GetObjShell();
3319 transformUnsavedRefToSavedRef(pObjShell
.get());
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: */