Line data Source code
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 "formulaparser.hxx"
12 : #include <externallinkbuffer.hxx>
13 : #include <com/sun/star/sheet/XFormulaTokens.hpp>
14 : #include <com/sun/star/sheet/XArrayFormulaTokens.hpp>
15 : #include <com/sun/star/container/XIndexAccess.hpp>
16 : #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
17 : #include <com/sun/star/table/XCell2.hpp>
18 : #include "formulacell.hxx"
19 : #include "document.hxx"
20 : #include "documentimport.hxx"
21 : #include "convuno.hxx"
22 :
23 : #include "rangelst.hxx"
24 : #include "autonamecache.hxx"
25 : #include "tokenuno.hxx"
26 : #include "tokenarray.hxx"
27 : #include "sharedformulagroups.hxx"
28 : #include "externalrefmgr.hxx"
29 : #include "tokenstringcontext.hxx"
30 : #include <oox/token/tokens.hxx>
31 : #include <svl/sharedstringpool.hxx>
32 :
33 : using namespace com::sun::star;
34 : using namespace ::com::sun::star::uno;
35 : using namespace ::com::sun::star::table;
36 : using namespace ::com::sun::star::sheet;
37 : using namespace ::com::sun::star::container;
38 :
39 : #include <boost/scoped_ptr.hpp>
40 : #include <boost/noncopyable.hpp>
41 :
42 : namespace oox { namespace xls {
43 :
44 : namespace {
45 :
46 : /**
47 : * Cache the token array for the last cell position in each column. We use
48 : * one cache per sheet.
49 : */
50 : class CachedTokenArray : boost::noncopyable
51 : {
52 : public:
53 :
54 : struct Item : boost::noncopyable
55 : {
56 : SCROW mnRow;
57 : ScFormulaCell* mpCell;
58 :
59 202 : Item() : mnRow(-1), mpCell(NULL) {}
60 : };
61 :
62 50 : CachedTokenArray( ScDocument& rDoc ) :
63 50 : maCxt(&rDoc, formula::FormulaGrammar::GRAM_OOXML) {}
64 :
65 50 : ~CachedTokenArray()
66 50 : {
67 50 : ColCacheType::const_iterator it = maCache.begin(), itEnd = maCache.end();
68 252 : for (; it != itEnd; ++it)
69 202 : delete it->second;
70 50 : }
71 :
72 2130 : Item* get( const ScAddress& rPos, const OUString& rFormula )
73 : {
74 : // Check if a token array is cached for this column.
75 2130 : ColCacheType::iterator it = maCache.find(rPos.Col());
76 2130 : if (it == maCache.end())
77 202 : return NULL;
78 :
79 1928 : Item& rCached = *it->second;
80 1928 : const ScTokenArray& rCode = *rCached.mpCell->GetCode();
81 1928 : OUString aPredicted = rCode.CreateString(maCxt, rPos);
82 1928 : if (rFormula == aPredicted)
83 810 : return &rCached;
84 :
85 1118 : return NULL;
86 : }
87 :
88 1320 : void store( const ScAddress& rPos, ScFormulaCell* pCell )
89 : {
90 1320 : ColCacheType::iterator it = maCache.find(rPos.Col());
91 1320 : if (it == maCache.end())
92 : {
93 : // Create an entry for this column.
94 : std::pair<ColCacheType::iterator,bool> r =
95 202 : maCache.insert(ColCacheType::value_type(rPos.Col(), new Item));
96 202 : if (!r.second)
97 : // Insertion failed.
98 1320 : return;
99 :
100 202 : it = r.first;
101 : }
102 :
103 1320 : Item& rItem = *it->second;
104 1320 : rItem.mnRow = rPos.Row();
105 1320 : rItem.mpCell = pCell;
106 : }
107 :
108 : private:
109 : typedef boost::unordered_map<SCCOL, Item*> ColCacheType;
110 : ColCacheType maCache;
111 : sc::TokenStringContext maCxt;
112 : };
113 :
114 18 : void applySharedFormulas(
115 : ScDocumentImport& rDoc,
116 : SvNumberFormatter& rFormatter,
117 : std::vector<FormulaBuffer::SharedFormulaEntry>& rSharedFormulas,
118 : std::vector<FormulaBuffer::SharedFormulaDesc>& rCells )
119 : {
120 18 : sc::SharedFormulaGroups aGroups;
121 : {
122 : // Process shared formulas first.
123 18 : std::vector<FormulaBuffer::SharedFormulaEntry>::const_iterator it = rSharedFormulas.begin(), itEnd = rSharedFormulas.end();
124 144 : for (; it != itEnd; ++it)
125 : {
126 126 : const table::CellAddress& rAddr = it->maAddress;
127 126 : sal_Int32 nId = it->mnSharedId;
128 126 : const OUString& rTokenStr = it->maTokenStr;
129 :
130 126 : ScAddress aPos;
131 126 : ScUnoConversion::FillScAddress(aPos, rAddr);
132 126 : ScCompiler aComp(&rDoc.getDoc(), aPos);
133 126 : aComp.SetNumberFormatter(&rFormatter);
134 126 : aComp.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
135 126 : ScTokenArray* pArray = aComp.CompileString(rTokenStr);
136 126 : if (pArray)
137 : {
138 126 : aComp.CompileTokenArray(); // Generate RPN tokens.
139 126 : aGroups.set(nId, pArray);
140 : }
141 126 : }
142 : }
143 :
144 : {
145 : // Process formulas that use shared formulas.
146 18 : std::vector<FormulaBuffer::SharedFormulaDesc>::const_iterator it = rCells.begin(), itEnd = rCells.end();
147 2262 : for (; it != itEnd; ++it)
148 : {
149 2244 : const table::CellAddress& rAddr = it->maAddress;
150 2244 : const ScTokenArray* pArray = aGroups.get(it->mnSharedId);
151 2244 : if (!pArray)
152 0 : continue;
153 :
154 2244 : ScAddress aPos;
155 2244 : ScUnoConversion::FillScAddress(aPos, rAddr);
156 2244 : ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, *pArray);
157 2244 : rDoc.setFormulaCell(aPos, pCell);
158 2244 : if (it->maCellValue.isEmpty())
159 : {
160 : // No cached cell value. Mark it for re-calculation.
161 0 : pCell->SetDirty(true);
162 0 : continue;
163 : }
164 :
165 : // Set cached formula results. For now, we only use numeric
166 : // results. Find out how to utilize cached results of other types.
167 2244 : switch (it->mnValueType)
168 : {
169 : case XML_n:
170 : // numeric value.
171 728 : pCell->SetResultDouble(it->maCellValue.toDouble());
172 728 : break;
173 : default:
174 : // Mark it for re-calculation.
175 1516 : pCell->SetDirty(true);
176 : }
177 : }
178 18 : }
179 18 : }
180 :
181 50 : void applyCellFormulas(
182 : ScDocumentImport& rDoc, CachedTokenArray& rCache, SvNumberFormatter& rFormatter,
183 : const uno::Sequence<sheet::ExternalLinkInfo>& rExternalLinks,
184 : const std::vector<FormulaBuffer::TokenAddressItem>& rCells )
185 : {
186 50 : std::vector<FormulaBuffer::TokenAddressItem>::const_iterator it = rCells.begin(), itEnd = rCells.end();
187 2180 : for (; it != itEnd; ++it)
188 : {
189 2130 : ScAddress aPos;
190 2130 : ScUnoConversion::FillScAddress(aPos, it->maCellAddress);
191 2130 : CachedTokenArray::Item* p = rCache.get(aPos, it->maTokenStr);
192 2130 : if (p)
193 : {
194 : // Use the cached version to avoid re-compilation.
195 :
196 810 : ScFormulaCell* pCell = NULL;
197 810 : if (p->mnRow + 1 == aPos.Row())
198 : {
199 : // Put them in the same formula group.
200 804 : ScFormulaCell& rPrev = *p->mpCell;
201 804 : ScFormulaCellGroupRef xGroup = rPrev.GetCellGroup();
202 804 : if (!xGroup)
203 : {
204 : // Last cell is not grouped yet. Start a new group.
205 : assert(rPrev.aPos.Row() == p->mnRow);
206 226 : xGroup = rPrev.CreateCellGroup(1, false);
207 : }
208 804 : ++xGroup->mnLength;
209 :
210 804 : pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, xGroup);
211 : }
212 : else
213 6 : pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, p->mpCell->GetCode()->Clone());
214 :
215 810 : rDoc.setFormulaCell(aPos, pCell);
216 :
217 : // Update the cache.
218 810 : p->mnRow = aPos.Row();
219 810 : p->mpCell = pCell;
220 1620 : continue;
221 : }
222 :
223 1320 : ScCompiler aCompiler(&rDoc.getDoc(), aPos);
224 1320 : aCompiler.SetNumberFormatter(&rFormatter);
225 1320 : aCompiler.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
226 1320 : aCompiler.SetExternalLinks(rExternalLinks);
227 1320 : ScTokenArray* pCode = aCompiler.CompileString(it->maTokenStr);
228 1320 : if (!pCode)
229 0 : continue;
230 :
231 1320 : aCompiler.CompileTokenArray(); // Generate RPN tokens.
232 1320 : ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, pCode);
233 1320 : rDoc.setFormulaCell(aPos, pCell);
234 1320 : rCache.store(aPos, pCell);
235 1320 : }
236 50 : }
237 :
238 0 : void applyArrayFormulas(
239 : ScDocumentImport& rDoc, SvNumberFormatter& rFormatter,
240 : const std::vector<FormulaBuffer::TokenRangeAddressItem>& rArrays )
241 : {
242 0 : std::vector<FormulaBuffer::TokenRangeAddressItem>::const_iterator it = rArrays.begin(), itEnd = rArrays.end();
243 0 : for (; it != itEnd; ++it)
244 : {
245 0 : ScAddress aPos;
246 0 : ScUnoConversion::FillScAddress(aPos, it->maTokenAndAddress.maCellAddress);
247 0 : ScRange aRange;
248 0 : ScUnoConversion::FillScRange(aRange, it->maCellRangeAddress);
249 :
250 0 : ScCompiler aComp(&rDoc.getDoc(), aPos);
251 0 : aComp.SetNumberFormatter(&rFormatter);
252 0 : aComp.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
253 0 : boost::scoped_ptr<ScTokenArray> pArray(aComp.CompileString(it->maTokenAndAddress.maTokenStr));
254 0 : if (pArray)
255 0 : rDoc.setMatrixCells(aRange, *pArray, formula::FormulaGrammar::GRAM_OOXML);
256 0 : }
257 0 : }
258 :
259 50 : void applyCellFormulaValues(
260 : ScDocumentImport& rDoc, const std::vector<FormulaBuffer::FormulaValue>& rVector )
261 : {
262 50 : svl::SharedStringPool& rStrPool = rDoc.getDoc().GetSharedStringPool();
263 :
264 50 : std::vector<FormulaBuffer::FormulaValue>::const_iterator it = rVector.begin(), itEnd = rVector.end();
265 2178 : for (; it != itEnd; ++it)
266 : {
267 2128 : ScAddress aCellPos;
268 2128 : ScUnoConversion::FillScAddress(aCellPos, it->maCellAddress);
269 2128 : ScFormulaCell* pCell = rDoc.getDoc().GetFormulaCell(aCellPos);
270 2128 : const OUString& rValueStr = it->maValueStr;
271 2128 : if (!pCell)
272 0 : continue;
273 :
274 2128 : switch (it->mnCellType)
275 : {
276 : case XML_n:
277 : {
278 2058 : pCell->SetResultDouble(rValueStr.toDouble());
279 2058 : pCell->ResetDirty();
280 2058 : pCell->SetChanged(false);
281 : }
282 2058 : break;
283 : case XML_str:
284 : {
285 46 : svl::SharedString aSS = rStrPool.intern(rValueStr);
286 46 : pCell->SetResultToken(new formula::FormulaStringToken(aSS));
287 46 : pCell->ResetDirty();
288 46 : pCell->SetChanged(false);
289 : }
290 46 : break;
291 : default:
292 : ;
293 : }
294 : }
295 50 : }
296 :
297 260 : void processSheetFormulaCells(
298 : ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter& rFormatter,
299 : const uno::Sequence<sheet::ExternalLinkInfo>& rExternalLinks )
300 : {
301 260 : if (rItem.mpSharedFormulaEntries && rItem.mpSharedFormulaIDs)
302 18 : applySharedFormulas(rDoc, rFormatter, *rItem.mpSharedFormulaEntries, *rItem.mpSharedFormulaIDs);
303 :
304 260 : if (rItem.mpCellFormulas)
305 : {
306 50 : CachedTokenArray aCache(rDoc.getDoc());
307 50 : applyCellFormulas(rDoc, aCache, rFormatter, rExternalLinks, *rItem.mpCellFormulas);
308 : }
309 :
310 260 : if (rItem.mpArrayFormulas)
311 0 : applyArrayFormulas(rDoc, rFormatter, *rItem.mpArrayFormulas);
312 :
313 260 : if (rItem.mpCellFormulaValues)
314 50 : applyCellFormulaValues(rDoc, *rItem.mpCellFormulaValues);
315 260 : }
316 :
317 : class WorkerThread: public salhelper::Thread, private boost::noncopyable
318 : {
319 : ScDocumentImport& mrDoc;
320 : FormulaBuffer::SheetItem& mrItem;
321 : boost::scoped_ptr<SvNumberFormatter> mpFormatter;
322 : const uno::Sequence<sheet::ExternalLinkInfo>& mrExternalLinks;
323 :
324 : public:
325 : WorkerThread(
326 : ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter* pFormatter,
327 : const uno::Sequence<sheet::ExternalLinkInfo>& rExternalLinks ) :
328 : salhelper::Thread("xlsx-import-formula-buffer-worker-thread"),
329 : mrDoc(rDoc), mrItem(rItem), mpFormatter(pFormatter), mrExternalLinks(rExternalLinks) {}
330 :
331 : virtual ~WorkerThread() {}
332 :
333 : protected:
334 : virtual void execute() SAL_OVERRIDE
335 : {
336 : processSheetFormulaCells(mrDoc, mrItem, *mpFormatter, mrExternalLinks);
337 : }
338 : };
339 :
340 : }
341 :
342 126 : FormulaBuffer::SharedFormulaEntry::SharedFormulaEntry(
343 : const table::CellAddress& rAddr, const table::CellRangeAddress& rRange,
344 : const OUString& rTokenStr, sal_Int32 nSharedId ) :
345 126 : maAddress(rAddr), maRange(rRange), maTokenStr(rTokenStr), mnSharedId(nSharedId) {}
346 :
347 2244 : FormulaBuffer::SharedFormulaDesc::SharedFormulaDesc(
348 : const com::sun::star::table::CellAddress& rAddr, sal_Int32 nSharedId,
349 : const OUString& rCellValue, sal_Int32 nValueType ) :
350 2244 : maAddress(rAddr), mnSharedId(nSharedId), maCellValue(rCellValue), mnValueType(nValueType) {}
351 :
352 260 : FormulaBuffer::SheetItem::SheetItem() :
353 : mpCellFormulas(NULL),
354 : mpArrayFormulas(NULL),
355 : mpCellFormulaValues(NULL),
356 : mpSharedFormulaEntries(NULL),
357 260 : mpSharedFormulaIDs(NULL) {}
358 :
359 128 : FormulaBuffer::FormulaBuffer( const WorkbookHelper& rHelper ) : WorkbookHelper( rHelper )
360 : {
361 128 : }
362 :
363 128 : void FormulaBuffer::SetSheetCount( SCTAB nSheets )
364 : {
365 128 : maCellFormulas.resize( nSheets );
366 128 : maCellArrayFormulas.resize( nSheets );
367 128 : maSharedFormulas.resize( nSheets );
368 128 : maSharedFormulaIds.resize( nSheets );
369 128 : maCellFormulaValues.resize( nSheets );
370 128 : }
371 :
372 128 : void FormulaBuffer::finalizeImport()
373 : {
374 128 : ISegmentProgressBarRef xFormulaBar = getProgressBar().createSegment( getProgressBar().getFreeLength() );
375 :
376 128 : const size_t nThreadCount = 1;
377 128 : ScDocumentImport& rDoc = getDocImport();
378 128 : rDoc.getDoc().SetAutoNameCache(new ScAutoNameCache(&rDoc.getDoc()));
379 256 : ScExternalRefManager::ApiGuard aExtRefGuard(&rDoc.getDoc());
380 :
381 128 : SCTAB nTabCount = rDoc.getDoc().GetTableCount();
382 :
383 : // Fetch all the formulas to process first.
384 256 : std::vector<SheetItem> aSheetItems;
385 128 : aSheetItems.reserve(nTabCount);
386 388 : for (SCTAB nTab = 0; nTab < nTabCount; ++nTab)
387 260 : aSheetItems.push_back(getSheetItem(nTab));
388 :
389 128 : std::vector<SheetItem>::iterator it = aSheetItems.begin(), itEnd = aSheetItems.end();
390 :
391 : if (nThreadCount == 1)
392 : {
393 388 : for (; it != itEnd; ++it)
394 260 : processSheetFormulaCells(rDoc, *it, *rDoc.getDoc().GetFormatTable(), getExternalLinks().getLinkInfos());
395 : }
396 : else
397 : {
398 : typedef rtl::Reference<WorkerThread> WorkerThreadRef;
399 : std::vector<WorkerThreadRef> aThreads;
400 : aThreads.reserve(nThreadCount);
401 : // TODO: Right now we are spawning multiple threads all at once and block
402 : // on them all at once. Any more clever thread management would require
403 : // use of condition variables which our own osl thread framework seems to
404 : // lack.
405 : while (it != itEnd)
406 : {
407 : for (size_t i = 0; i < nThreadCount; ++i)
408 : {
409 : if (it == itEnd)
410 : break;
411 :
412 : WorkerThreadRef xThread(new WorkerThread(rDoc, *it, rDoc.getDoc().CreateFormatTable(), getExternalLinks().getLinkInfos()));
413 : ++it;
414 : aThreads.push_back(xThread);
415 : xThread->launch();
416 : }
417 :
418 : for (size_t i = 0, n = aThreads.size(); i < n; ++i)
419 : {
420 : if (aThreads[i].is())
421 : aThreads[i]->join();
422 : }
423 :
424 : aThreads.clear();
425 : }
426 : }
427 :
428 128 : rDoc.getDoc().SetAutoNameCache(NULL);
429 :
430 256 : xFormulaBar->setPosition( 1.0 );
431 128 : }
432 :
433 260 : FormulaBuffer::SheetItem FormulaBuffer::getSheetItem( SCTAB nTab )
434 : {
435 260 : osl::MutexGuard aGuard(&maMtxData);
436 :
437 260 : SheetItem aItem;
438 :
439 260 : if( (size_t) nTab >= maCellFormulas.size() )
440 : {
441 : SAL_WARN( "sc", "Tab " << nTab << " out of bounds " << maCellFormulas.size() );
442 0 : return aItem;
443 : }
444 :
445 260 : if( maCellFormulas[ nTab ].size() > 0 )
446 50 : aItem.mpCellFormulas = &maCellFormulas[ nTab ];
447 260 : if( maCellArrayFormulas[ nTab ].size() > 0 )
448 0 : aItem.mpArrayFormulas = &maCellArrayFormulas[ nTab ];
449 260 : if( maCellFormulaValues[ nTab ].size() > 0 )
450 50 : aItem.mpCellFormulaValues = &maCellFormulaValues[ nTab ];
451 260 : if( maSharedFormulas[ nTab ].size() > 0 )
452 18 : aItem.mpSharedFormulaEntries = &maSharedFormulas[ nTab ];
453 260 : if( maSharedFormulaIds[ nTab ].size() > 0 )
454 18 : aItem.mpSharedFormulaIDs = &maSharedFormulaIds[ nTab ];
455 :
456 260 : return aItem;
457 : }
458 :
459 126 : void FormulaBuffer::createSharedFormulaMapEntry(
460 : const table::CellAddress& rAddress, const table::CellRangeAddress& rRange,
461 : sal_Int32 nSharedId, const OUString& rTokens )
462 : {
463 : assert( rAddress.Sheet >= 0 && (size_t)rAddress.Sheet < maSharedFormulas.size() );
464 126 : std::vector<SharedFormulaEntry>& rSharedFormulas = maSharedFormulas[ rAddress.Sheet ];
465 126 : SharedFormulaEntry aEntry(rAddress, rRange, rTokens, nSharedId);
466 126 : rSharedFormulas.push_back( aEntry );
467 126 : }
468 :
469 2130 : void FormulaBuffer::setCellFormula( const ::com::sun::star::table::CellAddress& rAddress, const OUString& rTokenStr )
470 : {
471 : assert( rAddress.Sheet >= 0 && (size_t)rAddress.Sheet < maCellFormulas.size() );
472 2130 : maCellFormulas[ rAddress.Sheet ].push_back( TokenAddressItem( rTokenStr, rAddress ) );
473 2130 : }
474 :
475 2244 : void FormulaBuffer::setCellFormula(
476 : const table::CellAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType )
477 : {
478 : assert( rAddress.Sheet >= 0 && (size_t)rAddress.Sheet < maSharedFormulaIds.size() );
479 2244 : maSharedFormulaIds[rAddress.Sheet].push_back(
480 4488 : SharedFormulaDesc(rAddress, nSharedId, rCellValue, nValueType));
481 2244 : }
482 :
483 0 : void FormulaBuffer::setCellArrayFormula( const ::com::sun::star::table::CellRangeAddress& rRangeAddress, const ::com::sun::star::table::CellAddress& rTokenAddress, const OUString& rTokenStr )
484 : {
485 :
486 0 : TokenAddressItem tokenPair( rTokenStr, rTokenAddress );
487 : assert( rRangeAddress.Sheet >= 0 && (size_t)rRangeAddress.Sheet < maCellArrayFormulas.size() );
488 0 : maCellArrayFormulas[ rRangeAddress.Sheet ].push_back( TokenRangeAddressItem( tokenPair, rRangeAddress ) );
489 0 : }
490 :
491 2128 : void FormulaBuffer::setCellFormulaValue(
492 : const css::table::CellAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType )
493 : {
494 : assert( rAddress.Sheet >= 0 && (size_t)rAddress.Sheet < maCellFormulaValues.size() );
495 2128 : FormulaValue aVal;
496 2128 : aVal.maCellAddress = rAddress;
497 2128 : aVal.maValueStr = rValueStr;
498 2128 : aVal.mnCellType = nCellType;
499 2128 : maCellFormulaValues[rAddress.Sheet].push_back(aVal);
500 2128 : }
501 :
502 48 : }}
503 :
504 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|