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