LibreOffice Module sc (master) 1
formulabuffer.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 */
9
10#include <formulabuffer.hxx>
12#include <formulacell.hxx>
13#include <document.hxx>
14#include <documentimport.hxx>
15
16#include <autonamecache.hxx>
17#include <tokenarray.hxx>
19#include <externalrefmgr.hxx>
21#include <o3tl/safeint.hxx>
22#include <oox/token/tokens.hxx>
25#include <sal/log.hxx>
26
27using namespace ::com::sun::star::uno;
28using namespace ::com::sun::star::sheet;
29
30#include <memory>
31#include <utility>
32
33namespace oox::xls {
34
35namespace {
36
41class CachedTokenArray
42{
43public:
44 CachedTokenArray(const CachedTokenArray&) = delete;
45 const CachedTokenArray& operator=(const CachedTokenArray&) = delete;
46
47 struct Item
48 {
51
52 Item(const Item&) = delete;
53 const Item& operator=(const Item&) = delete;
54
55 Item() : mnRow(-1), mpCell(nullptr) {}
56 };
57
58 explicit CachedTokenArray( const ScDocument& rDoc ) :
59 maCxt(rDoc, formula::FormulaGrammar::GRAM_OOXML) {}
60
61 Item* get( const ScAddress& rPos, std::u16string_view rFormula )
62 {
63 // Check if a token array is cached for this column.
64 ColCacheType::iterator it = maCache.find(rPos.Col());
65 if (it == maCache.end())
66 return nullptr;
67
68 Item& rCached = *it->second;
69 const ScTokenArray& rCode = *rCached.mpCell->GetCode();
70 OUString aPredicted = rCode.CreateString(maCxt, rPos);
71 if (rFormula == aPredicted)
72 return &rCached;
73
74 return nullptr;
75 }
76
77 void store( const ScAddress& rPos, ScFormulaCell* pCell )
78 {
79 ColCacheType::iterator it = maCache.find(rPos.Col());
80 if (it == maCache.end())
81 {
82 // Create an entry for this column.
83 std::pair<ColCacheType::iterator,bool> r =
84 maCache.emplace(rPos.Col(), std::make_unique<Item>());
85 if (!r.second)
86 // Insertion failed.
87 return;
88
89 it = r.first;
90 }
91
92 Item& rItem = *it->second;
93 rItem.mnRow = rPos.Row();
94 rItem.mpCell = pCell;
95 }
96
97private:
98 typedef std::unordered_map<SCCOL, std::unique_ptr<Item>> ColCacheType;
99 ColCacheType maCache;
101};
102
103void applySharedFormulas(
104 ScDocumentImport& rDoc,
105 SvNumberFormatter& rFormatter,
106 std::vector<FormulaBuffer::SharedFormulaEntry>& rSharedFormulas,
107 std::vector<FormulaBuffer::SharedFormulaDesc>& rCells,
108 bool bGeneratorKnownGood)
109{
111 {
112 // Process shared formulas first.
113 for (const FormulaBuffer::SharedFormulaEntry& rEntry : rSharedFormulas)
114 {
115 const ScAddress& aPos = rEntry.maAddress;
116 sal_Int32 nId = rEntry.mnSharedId;
117 const OUString& rTokenStr = rEntry.maTokenStr;
118
119 ScCompiler aComp(rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false);
120 aComp.SetNumberFormatter(&rFormatter);
121 std::unique_ptr<ScTokenArray> pArray = aComp.CompileString(rTokenStr);
122 if (pArray)
123 {
124 aComp.CompileTokenArray(); // Generate RPN tokens.
125 aGroups.set(nId, std::move(pArray), aPos);
126 }
127 }
128 }
129
130 {
132 // Process formulas that use shared formulas.
133 for (const FormulaBuffer::SharedFormulaDesc& rDesc : rCells)
134 {
135 const ScAddress& aPos = rDesc.maAddress;
136 const sc::SharedFormulaGroupEntry* pEntry = aGroups.getEntry(rDesc.mnSharedId);
137 if (!pEntry)
138 continue;
139
140 const ScTokenArray* pArray = pEntry->getTokenArray();
141 assert(pArray);
142 const ScAddress& rOrigin = pEntry->getOrigin();
143 assert(rOrigin.IsValid());
144
145 ScFormulaCell* pCell;
146 // In case of shared-formula along a row, do not let
147 // these cells share the same token objects.
148 // If we do, any reference-updates on these cells
149 // (while editing) will mess things up. Pass the cloned array as a
150 // pointer and not as reference to avoid any further allocation.
151 if (rOrigin.Col() != aPos.Col())
152 pCell = new ScFormulaCell(rDoc.getDoc(), aPos, pArray->Clone());
153 else
154 pCell = new ScFormulaCell(rDoc.getDoc(), aPos, *pArray);
155
156 rDoc.setFormulaCell(aPos, pCell);
157 if (rDoc.getDoc().GetNumberFormat(aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET == 0)
158 pCell->SetNeedNumberFormat(true);
159
160 if (rDesc.maCellValue.isEmpty())
161 {
162 // No cached cell value. Mark it for re-calculation.
163 pCell->SetDirty();
164 continue;
165 }
166
167 // Set cached formula results. For now, we only use numeric and string-formula
168 // results. Find out how to utilize cached results of other types.
169 switch (rDesc.mnValueType)
170 {
171 case XML_n:
172 // numeric value.
173 pCell->SetResultDouble(rDesc.maCellValue.toDouble());
174 /* TODO: is it on purpose that we never reset dirty here
175 * and thus recalculate anyway if cell was dirty? Or is it
176 * never dirty and therefore set dirty below otherwise? This
177 * is different from the non-shared case in
178 * applyCellFormulaValues(). */
179 break;
180 case XML_str:
181 if (bGeneratorKnownGood)
182 {
183 // See applyCellFormulaValues
184 svl::SharedString aSS = rStrPool.intern(rDesc.maCellValue);
185 pCell->SetResultToken(new formula::FormulaStringToken(std::move(aSS)));
186 // If we don't reset dirty, then e.g. disabling macros makes all cells
187 // that use macro functions to show #VALUE!
188 pCell->ResetDirty();
189 pCell->SetChanged(false);
190 break;
191 }
192 [[fallthrough]];
193 default:
194 // Mark it for re-calculation.
195 pCell->SetDirty();
196 }
197 }
198 }
199}
200
201void applyCellFormulas(
202 ScDocumentImport& rDoc, CachedTokenArray& rCache, SvNumberFormatter& rFormatter,
203 const Sequence<ExternalLinkInfo>& rExternalLinks,
204 const std::vector<FormulaBuffer::TokenAddressItem>& rCells )
205{
206 for (const FormulaBuffer::TokenAddressItem& rItem : rCells)
207 {
208 const ScAddress& aPos = rItem.maAddress;
209 CachedTokenArray::Item* p = rCache.get(aPos, rItem.maTokenStr);
210 if (p)
211 {
212 // Use the cached version to avoid re-compilation.
213
214 ScFormulaCell* pCell = nullptr;
215 if (p->mnRow + 1 == aPos.Row())
216 {
217 // Put them in the same formula group.
218 ScFormulaCell& rPrev = *p->mpCell;
219 ScFormulaCellGroupRef xGroup = rPrev.GetCellGroup();
220 if (!xGroup)
221 {
222 // Last cell is not grouped yet. Start a new group.
223 assert(rPrev.aPos.Row() == p->mnRow);
224 xGroup = rPrev.CreateCellGroup(1, false);
225 }
226 ++xGroup->mnLength;
227
228 pCell = new ScFormulaCell(rDoc.getDoc(), aPos, xGroup);
229 }
230 else
231 pCell = new ScFormulaCell(rDoc.getDoc(), aPos, p->mpCell->GetCode()->Clone());
232
233 rDoc.setFormulaCell(aPos, pCell);
234 if (rDoc.getDoc().GetNumberFormat(aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET == 0)
235 pCell->SetNeedNumberFormat(true);
236
237 // Update the cache.
238 p->mnRow = aPos.Row();
239 p->mpCell = pCell;
240 continue;
241 }
242
243 ScCompiler aCompiler(rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false);
244 aCompiler.SetNumberFormatter(&rFormatter);
245 aCompiler.SetExternalLinks(rExternalLinks);
246 std::unique_ptr<ScTokenArray> pCode = aCompiler.CompileString(rItem.maTokenStr);
247 if (!pCode)
248 continue;
249
250 aCompiler.CompileTokenArray(); // Generate RPN tokens.
251
252 ScFormulaCell* pCell = new ScFormulaCell(rDoc.getDoc(), aPos, std::move(pCode));
253 rDoc.setFormulaCell(aPos, pCell);
254 if (rDoc.getDoc().GetNumberFormat(aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET == 0)
255 pCell->SetNeedNumberFormat(true);
256 rCache.store(aPos, pCell);
257 }
258}
259
260void applyArrayFormulas(
261 ScDocumentImport& rDoc, SvNumberFormatter& rFormatter,
262 const Sequence<ExternalLinkInfo>& rExternalLinks,
263 const std::vector<FormulaBuffer::TokenRangeAddressItem>& rArrays )
264{
265 for (const FormulaBuffer::TokenRangeAddressItem& rAddressItem : rArrays)
266 {
267 const ScAddress& aPos = rAddressItem.maTokenAndAddress.maAddress;
268
270 aComp.SetNumberFormatter(&rFormatter);
271 aComp.SetExternalLinks(rExternalLinks);
272 std::unique_ptr<ScTokenArray> pArray(aComp.CompileString(rAddressItem.maTokenAndAddress.maTokenStr));
273 if (pArray)
274 rDoc.setMatrixCells(rAddressItem.maRange, *pArray, formula::FormulaGrammar::GRAM_OOXML);
275 }
276}
277
278void applyCellFormulaValues(
279 ScDocumentImport& rDoc, const std::vector<FormulaBuffer::FormulaValue>& rVector, bool bGeneratorKnownGood )
280{
282
283 for (const FormulaBuffer::FormulaValue& rValue : rVector)
284 {
285 const ScAddress& aCellPos = rValue.maAddress;
286 ScFormulaCell* pCell = rDoc.getDoc().GetFormulaCell(aCellPos);
287 const OUString& rValueStr = rValue.maValueStr;
288 if (!pCell)
289 continue;
290
291 switch (rValue.mnCellType)
292 {
293 case XML_n:
294 {
295 pCell->SetResultDouble(rValueStr.toDouble());
296 pCell->ResetDirty();
297 pCell->SetChanged(false);
298 }
299 break;
300 case XML_str:
301 // Excel uses t="str" for string results (per definition
302 // ECMA-376 18.18.11 ST_CellType (Cell Type) "Cell containing a
303 // formula string.", but that 't' Cell Data Type attribute, "an
304 // enumeration representing the cell's data type", is meant for
305 // the content of the <v> element). We follow that. Other
306 // applications might not and instead use t="str" for the cell
307 // content if formula. Setting an otherwise numeric result as
308 // string result fouls things up, set result strings only for
309 // documents claiming to be generated by a known good
310 // generator. See tdf#98481
311 if (bGeneratorKnownGood)
312 {
313 svl::SharedString aSS = rStrPool.intern(rValueStr);
314 pCell->SetResultToken(new formula::FormulaStringToken(std::move(aSS)));
315 pCell->ResetDirty();
316 pCell->SetChanged(false);
317 }
318 break;
319 default:
320 ;
321 }
322 }
323}
324
325void processSheetFormulaCells(
326 ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter& rFormatter,
327 const Sequence<ExternalLinkInfo>& rExternalLinks, bool bGeneratorKnownGood )
328{
329 if (rItem.mpSharedFormulaEntries && rItem.mpSharedFormulaIDs)
330 applySharedFormulas(rDoc, rFormatter, *rItem.mpSharedFormulaEntries,
331 *rItem.mpSharedFormulaIDs, bGeneratorKnownGood);
332
333 if (rItem.mpCellFormulas)
334 {
335 CachedTokenArray aCache(rDoc.getDoc());
336 applyCellFormulas(rDoc, aCache, rFormatter, rExternalLinks, *rItem.mpCellFormulas);
337 }
338
339 if (rItem.mpArrayFormulas)
340 applyArrayFormulas(rDoc, rFormatter, rExternalLinks, *rItem.mpArrayFormulas);
341
342 if (rItem.mpCellFormulaValues)
343 applyCellFormulaValues(rDoc, *rItem.mpCellFormulaValues, bGeneratorKnownGood);
344}
345
346}
347
349 const ScAddress& rAddr,
350 OUString aTokenStr, sal_Int32 nSharedId ) :
351 maAddress(rAddr), maTokenStr(std::move(aTokenStr)), mnSharedId(nSharedId) {}
352
354 const ScAddress& rAddr, sal_Int32 nSharedId,
355 OUString aCellValue, sal_Int32 nValueType ) :
356 maAddress(rAddr), maCellValue(std::move(aCellValue)), mnSharedId(nSharedId), mnValueType(nValueType) {}
357
359 mpCellFormulas(nullptr),
360 mpArrayFormulas(nullptr),
361 mpCellFormulaValues(nullptr),
362 mpSharedFormulaEntries(nullptr),
363 mpSharedFormulaIDs(nullptr) {}
364
366{
367}
368
370{
371 maCellFormulas.resize( nSheets );
372 maCellArrayFormulas.resize( nSheets );
373 maSharedFormulas.resize( nSheets );
374 maSharedFormulaIds.resize( nSheets );
375 maCellFormulaValues.resize( nSheets );
376}
377
379{
380 ISegmentProgressBarRef xFormulaBar = getProgressBar().createSegment( getProgressBar().getFreeLength() );
381
383 rDoc.getDoc().SetAutoNameCache(std::make_unique<ScAutoNameCache>(rDoc.getDoc()));
384 ScExternalRefManager::ApiGuard aExtRefGuard(rDoc.getDoc());
385
386 SCTAB nTabCount = rDoc.getDoc().GetTableCount();
387
388 // Fetch all the formulas to process first.
389 std::vector<SheetItem> aSheetItems;
390 aSheetItems.reserve(nTabCount);
391 for (SCTAB nTab = 0; nTab < nTabCount; ++nTab)
392 aSheetItems.push_back(getSheetItem(nTab));
393
394 for (SheetItem& rItem : aSheetItems)
395 processSheetFormulaCells(rDoc, rItem, *rDoc.getDoc().GetFormatTable(), getExternalLinks().getLinkInfos(),
397
398 // With formula results being set and not recalculated we need to
399 // force-trigger adding all linked external files to the LinkManager.
401
402 rDoc.getDoc().SetAutoNameCache(nullptr);
403
404 xFormulaBar->setPosition( 1.0 );
405}
406
408{
409 std::scoped_lock aGuard(maMtxData);
410
411 SheetItem aItem;
412
413 if( o3tl::make_unsigned(nTab) >= maCellFormulas.size() )
414 {
415 SAL_WARN( "sc", "Tab " << nTab << " out of bounds " << maCellFormulas.size() );
416 return aItem;
417 }
418
419 if( !maCellFormulas[ nTab ].empty() )
420 aItem.mpCellFormulas = &maCellFormulas[ nTab ];
421 if( !maCellArrayFormulas[ nTab ].empty() )
422 aItem.mpArrayFormulas = &maCellArrayFormulas[ nTab ];
423 if( !maCellFormulaValues[ nTab ].empty() )
425 if( !maSharedFormulas[ nTab ].empty() )
427 if( !maSharedFormulaIds[ nTab ].empty() )
428 aItem.mpSharedFormulaIDs = &maSharedFormulaIds[ nTab ];
429
430 return aItem;
431}
432
434 const ScAddress& rAddress,
435 sal_Int32 nSharedId, const OUString& rTokens )
436{
437 assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maSharedFormulas.size() );
438 std::vector<SharedFormulaEntry>& rSharedFormulas = maSharedFormulas[ rAddress.Tab() ];
439 SharedFormulaEntry aEntry(rAddress, rTokens, nSharedId);
440 rSharedFormulas.push_back( aEntry );
441}
442
443void FormulaBuffer::setCellFormula( const ScAddress& rAddress, const OUString& rTokenStr )
444{
445 assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maCellFormulas.size() );
446 maCellFormulas[ rAddress.Tab() ].emplace_back( rTokenStr, rAddress );
447}
448
450 const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType )
451{
452 assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maSharedFormulaIds.size() );
453 maSharedFormulaIds[rAddress.Tab()].emplace_back(rAddress, nSharedId, rCellValue, nValueType);
454}
455
456void FormulaBuffer::setCellArrayFormula( const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr )
457{
458
459 TokenAddressItem tokenPair( rTokenStr, rTokenAddress );
460 assert( rRangeAddress.aStart.Tab() >= 0 && o3tl::make_unsigned(rRangeAddress.aStart.Tab()) < maCellArrayFormulas.size() );
461 maCellArrayFormulas[ rRangeAddress.aStart.Tab() ].emplace_back( tokenPair, rRangeAddress );
462}
463
465 const ScAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType )
466{
467 assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maCellFormulaValues.size() );
468 FormulaValue aVal;
469 aVal.maAddress = rAddress;
470 aVal.maValueStr = rValueStr;
471 aVal.mnCellType = nCellType;
472 maCellFormulaValues[rAddress.Tab()].push_back(aVal);
473}
474
475}
476
477/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int16 mnValueType
SCTAB Tab() const
Definition: address.hxx:283
bool IsValid() const
Definition: address.hxx:305
SCROW Row() const
Definition: address.hxx:274
SCCOL Col() const
Definition: address.hxx:279
void SetNumberFormatter(SvNumberFormatter *pFormatter)
Definition: compiler.cxx:306
std::unique_ptr< ScTokenArray > CompileString(const OUString &rFormula)
Tokenize formula expression string into an array of tokens.
Definition: compiler.cxx:4687
Accessor class to ScDocument.
void setMatrixCells(const ScRange &rRange, const ScTokenArray &rArray, formula::FormulaGrammar::Grammar eGrammar)
void setFormulaCell(const ScAddress &rPos, const OUString &rFormula, formula::FormulaGrammar::Grammar eGrammar, const double *pResult=nullptr)
ScDocument & getDoc()
SC_DLLPUBLIC sal_uInt32 GetNumberFormat(SCCOL nCol, SCROW nRow, SCTAB nTab) const
Definition: document.cxx:3686
SC_DLLPUBLIC ScExternalRefManager * GetExternalRefManager() const
Definition: documen3.cxx:633
SC_DLLPUBLIC void SetAutoNameCache(std::unique_ptr< ScAutoNameCache > pCache)
Definition: document.cxx:7047
SC_DLLPUBLIC svl::SharedStringPool & GetSharedStringPool()
Definition: documen2.cxx:592
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:467
SC_DLLPUBLIC const ScFormulaCell * GetFormulaCell(const ScAddress &rPos) const
Definition: document.cxx:3766
SC_DLLPUBLIC SCTAB GetTableCount() const
Definition: document.cxx:317
Use this guard when performing something from the API that might query values from external reference...
void addFilesToLinkManager()
Add all known external files to the LinkManager.
void SetResultDouble(double n)
For import only: set a double result.
void SetNeedNumberFormat(bool bVal)
const ScFormulaCellGroupRef & GetCellGroup() const
void SetChanged(bool b)
void SetDirty(bool bDirtyFlag=true)
ScFormulaCellGroupRef CreateCellGroup(SCROW nLen, bool bInvariant)
Turn a non-grouped cell into the top of a grouped cell.
void SetResultToken(const formula::FormulaToken *pToken)
ScAddress aPos
ScAddress aStart
Definition: address.hxx:497
std::unique_ptr< ScTokenArray > Clone() const
Definition: token.cxx:1931
OUString CreateString(sc::TokenStringContext &rCxt, const ScAddress &rPos) const
Create a string representation of formula token array without modifying the internal state of the tok...
Definition: token.cxx:5217
virtual ISegmentProgressBarRef createSegment(double fLength) override
std::vector< std::vector< TokenRangeAddressItem > > maCellArrayFormulas
std::vector< std::vector< SharedFormulaDesc > > maSharedFormulaIds
SheetItem getSheetItem(SCTAB nTab)
std::vector< std::vector< TokenAddressItem > > maCellFormulas
void createSharedFormulaMapEntry(const ScAddress &rAddress, sal_Int32 nSharedId, const OUString &rTokens)
void SetSheetCount(SCTAB nSheets)
ensure sizes of vectors matches the number of sheets
void setCellFormulaValue(const ScAddress &rAddress, const OUString &rValueStr, sal_Int32 nCellType)
std::vector< std::vector< FormulaValue > > maCellFormulaValues
void setCellArrayFormula(const ScRange &rRangeAddress, const ScAddress &rTokenAddress, const OUString &)
void setCellFormula(const ScAddress &rAddress, const OUString &)
FormulaBuffer(const WorkbookHelper &rHelper)
std::vector< std::vector< SharedFormulaEntry > > maSharedFormulas
Helper class to provide access to global workbook data.
bool isGeneratorKnownGood() const
Returns true when reading a file generated by a known good generator.
ScDocumentImport & getDocImport()
SegmentProgressBar & getProgressBar() const
Returns the filter progress bar.
ExternalLinkBuffer & getExternalLinks() const
Returns the external links read from the external links substream.
const ScAddress & getOrigin() const
const ScTokenArray * getTokenArray() const
void set(size_t nSharedId, std::unique_ptr< ScTokenArray > pArray)
const SharedFormulaGroupEntry * getEntry(size_t nSharedId) const
SharedString intern(const OUString &rStr)
ScFormulaCell * mpCell
sc::TokenStringContext maCxt
SCROW mnRow
ColCacheType maCache
void * p
#define SAL_WARN(area, stream)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
std::shared_ptr< ISegmentProgressBar > ISegmentProgressBarRef
css::uno::Reference< css::linguistic2::XProofreadingIterator > get(css::uno::Reference< css::uno::XComponentContext > const &context)
sal_Int16 nId
SharedFormulaDesc(const ScAddress &rAddr, sal_Int32 nSharedId, OUString aCellValue, sal_Int32 nValueType)
Represents a shared formula definition.
SharedFormulaEntry(const ScAddress &rAddress, OUString aTokenStr, sal_Int32 nSharedId)
std::vector< FormulaValue > * mpCellFormulaValues
std::vector< TokenRangeAddressItem > * mpArrayFormulas
std::vector< SharedFormulaDesc > * mpSharedFormulaIDs
std::vector< SharedFormulaEntry > * mpSharedFormulaEntries
std::vector< TokenAddressItem > * mpCellFormulas
Context for creating string from an array of formula tokens, used in ScTokenArray::CreateString().
sal_Int16 SCTAB
Definition: types.hxx:22
::boost::intrusive_ptr< ScFormulaCellGroup > ScFormulaCellGroupRef
Definition: types.hxx:43
sal_Int32 SCROW
Definition: types.hxx:17