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( const 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 Sequence<ExternalLinkInfo>& rExternalLinks,
255  const std::vector<FormulaBuffer::TokenRangeAddressItem>& rArrays )
256 {
257  for (const FormulaBuffer::TokenRangeAddressItem& rAddressItem : rArrays)
258  {
259  const ScAddress& aPos = rAddressItem.maTokenAndAddress.maAddress;
260 
262  aComp.SetNumberFormatter(&rFormatter);
263  aComp.SetExternalLinks(rExternalLinks);
264  std::unique_ptr<ScTokenArray> pArray(aComp.CompileString(rAddressItem.maTokenAndAddress.maTokenStr));
265  if (pArray)
266  rDoc.setMatrixCells(rAddressItem.maRange, *pArray, formula::FormulaGrammar::GRAM_OOXML);
267  }
268 }
269 
270 void applyCellFormulaValues(
271  ScDocumentImport& rDoc, const std::vector<FormulaBuffer::FormulaValue>& rVector, bool bGeneratorKnownGood )
272 {
273  svl::SharedStringPool& rStrPool = rDoc.getDoc().GetSharedStringPool();
274 
275  for (const FormulaBuffer::FormulaValue& rValue : rVector)
276  {
277  const ScAddress& aCellPos = rValue.maAddress;
278  ScFormulaCell* pCell = rDoc.getDoc().GetFormulaCell(aCellPos);
279  const OUString& rValueStr = rValue.maValueStr;
280  if (!pCell)
281  continue;
282 
283  switch (rValue.mnCellType)
284  {
285  case XML_n:
286  {
287  pCell->SetResultDouble(rValueStr.toDouble());
288  pCell->ResetDirty();
289  pCell->SetChanged(false);
290  }
291  break;
292  case XML_str:
293  // Excel uses t="str" for string results (per definition
294  // ECMA-376 18.18.11 ST_CellType (Cell Type) "Cell containing a
295  // formula string.", but that 't' Cell Data Type attribute, "an
296  // enumeration representing the cell's data type", is meant for
297  // the content of the <v> element). We follow that. Other
298  // applications might not and instead use t="str" for the cell
299  // content if formula. Setting an otherwise numeric result as
300  // string result fouls things up, set result strings only for
301  // documents claiming to be generated by a known good
302  // generator. See tdf#98481
303  if (bGeneratorKnownGood)
304  {
305  svl::SharedString aSS = rStrPool.intern(rValueStr);
307  pCell->ResetDirty();
308  pCell->SetChanged(false);
309  }
310  break;
311  default:
312  ;
313  }
314  }
315 }
316 
317 void processSheetFormulaCells(
318  ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter& rFormatter,
319  const Sequence<ExternalLinkInfo>& rExternalLinks, bool bGeneratorKnownGood )
320 {
321  if (rItem.mpSharedFormulaEntries && rItem.mpSharedFormulaIDs)
322  applySharedFormulas(rDoc, rFormatter, *rItem.mpSharedFormulaEntries,
323  *rItem.mpSharedFormulaIDs, bGeneratorKnownGood);
324 
325  if (rItem.mpCellFormulas)
326  {
327  CachedTokenArray aCache(rDoc.getDoc());
328  applyCellFormulas(rDoc, aCache, rFormatter, rExternalLinks, *rItem.mpCellFormulas);
329  }
330 
331  if (rItem.mpArrayFormulas)
332  applyArrayFormulas(rDoc, rFormatter, rExternalLinks, *rItem.mpArrayFormulas);
333 
334  if (rItem.mpCellFormulaValues)
335  applyCellFormulaValues(rDoc, *rItem.mpCellFormulaValues, bGeneratorKnownGood);
336 }
337 
338 }
339 
341  const ScAddress& rAddr,
342  const OUString& rTokenStr, sal_Int32 nSharedId ) :
343  maAddress(rAddr), maTokenStr(rTokenStr), mnSharedId(nSharedId) {}
344 
346  const ScAddress& rAddr, sal_Int32 nSharedId,
347  const OUString& rCellValue, sal_Int32 nValueType ) :
348  maAddress(rAddr), mnSharedId(nSharedId), maCellValue(rCellValue), mnValueType(nValueType) {}
349 
351  mpCellFormulas(nullptr),
352  mpArrayFormulas(nullptr),
353  mpCellFormulaValues(nullptr),
354  mpSharedFormulaEntries(nullptr),
355  mpSharedFormulaIDs(nullptr) {}
356 
358 {
359 }
360 
362 {
363  maCellFormulas.resize( nSheets );
364  maCellArrayFormulas.resize( nSheets );
365  maSharedFormulas.resize( nSheets );
366  maSharedFormulaIds.resize( nSheets );
367  maCellFormulaValues.resize( nSheets );
368 }
369 
371 {
372  ISegmentProgressBarRef xFormulaBar = getProgressBar().createSegment( getProgressBar().getFreeLength() );
373 
374  ScDocumentImport& rDoc = getDocImport();
375  rDoc.getDoc().SetAutoNameCache(std::make_unique<ScAutoNameCache>(rDoc.getDoc()));
376  ScExternalRefManager::ApiGuard aExtRefGuard(rDoc.getDoc());
377 
378  SCTAB nTabCount = rDoc.getDoc().GetTableCount();
379 
380  // Fetch all the formulas to process first.
381  std::vector<SheetItem> aSheetItems;
382  aSheetItems.reserve(nTabCount);
383  for (SCTAB nTab = 0; nTab < nTabCount; ++nTab)
384  aSheetItems.push_back(getSheetItem(nTab));
385 
386  for (SheetItem& rItem : aSheetItems)
387  processSheetFormulaCells(rDoc, rItem, *rDoc.getDoc().GetFormatTable(), getExternalLinks().getLinkInfos(),
389 
390  // With formula results being set and not recalculated we need to
391  // force-trigger adding all linked external files to the LinkManager.
393 
394  rDoc.getDoc().SetAutoNameCache(nullptr);
395 
396  xFormulaBar->setPosition( 1.0 );
397 }
398 
400 {
401  osl::MutexGuard aGuard(&maMtxData);
402 
403  SheetItem aItem;
404 
405  if( o3tl::make_unsigned(nTab) >= maCellFormulas.size() )
406  {
407  SAL_WARN( "sc", "Tab " << nTab << " out of bounds " << maCellFormulas.size() );
408  return aItem;
409  }
410 
411  if( !maCellFormulas[ nTab ].empty() )
412  aItem.mpCellFormulas = &maCellFormulas[ nTab ];
413  if( !maCellArrayFormulas[ nTab ].empty() )
414  aItem.mpArrayFormulas = &maCellArrayFormulas[ nTab ];
415  if( !maCellFormulaValues[ nTab ].empty() )
416  aItem.mpCellFormulaValues = &maCellFormulaValues[ nTab ];
417  if( !maSharedFormulas[ nTab ].empty() )
418  aItem.mpSharedFormulaEntries = &maSharedFormulas[ nTab ];
419  if( !maSharedFormulaIds[ nTab ].empty() )
420  aItem.mpSharedFormulaIDs = &maSharedFormulaIds[ nTab ];
421 
422  return aItem;
423 }
424 
426  const ScAddress& rAddress,
427  sal_Int32 nSharedId, const OUString& rTokens )
428 {
429  assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maSharedFormulas.size() );
430  std::vector<SharedFormulaEntry>& rSharedFormulas = maSharedFormulas[ rAddress.Tab() ];
431  SharedFormulaEntry aEntry(rAddress, rTokens, nSharedId);
432  rSharedFormulas.push_back( aEntry );
433 }
434 
435 void FormulaBuffer::setCellFormula( const ScAddress& rAddress, const OUString& rTokenStr )
436 {
437  assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maCellFormulas.size() );
438  maCellFormulas[ rAddress.Tab() ].emplace_back( rTokenStr, rAddress );
439 }
440 
442  const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType )
443 {
444  assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maSharedFormulaIds.size() );
445  maSharedFormulaIds[rAddress.Tab()].emplace_back(rAddress, nSharedId, rCellValue, nValueType);
446 }
447 
448 void FormulaBuffer::setCellArrayFormula( const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr )
449 {
450 
451  TokenAddressItem tokenPair( rTokenStr, rTokenAddress );
452  assert( rRangeAddress.aStart.Tab() >= 0 && o3tl::make_unsigned(rRangeAddress.aStart.Tab()) < maCellArrayFormulas.size() );
453  maCellArrayFormulas[ rRangeAddress.aStart.Tab() ].emplace_back( tokenPair, rRangeAddress );
454 }
455 
457  const ScAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType )
458 {
459  assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maCellFormulaValues.size() );
460  FormulaValue aVal;
461  aVal.maAddress = rAddress;
462  aVal.maValueStr = rValueStr;
463  aVal.mnCellType = nCellType;
464  maCellFormulaValues[rAddress.Tab()].push_back(aVal);
465 }
466 
467 }
468 
469 /* 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:3723
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:314
ScFormulaCell * mpCell
void SetNumberFormatter(SvNumberFormatter *pFormatter)
Definition: compiler.cxx:282
sc::TokenStringContext maCxt
SC_DLLPUBLIC ScExternalRefManager * GetExternalRefManager() const
Definition: documen3.cxx:618
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:6799
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()