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 103 : Item() : mnRow(-1), mpCell(NULL) {}
60 : };
61 :
62 27 : CachedTokenArray( ScDocument& rDoc ) :
63 27 : maCxt(&rDoc, formula::FormulaGrammar::GRAM_OOXML) {}
64 :
65 27 : ~CachedTokenArray()
66 27 : {
67 27 : ColCacheType::const_iterator it = maCache.begin(), itEnd = maCache.end();
68 130 : for (; it != itEnd; ++it)
69 103 : delete it->second;
70 27 : }
71 :
72 1067 : Item* get( const ScAddress& rPos, const OUString& rFormula )
73 : {
74 : // Check if a token array is cached for this column.
75 1067 : ColCacheType::iterator it = maCache.find(rPos.Col());
76 1067 : if (it == maCache.end())
77 103 : return NULL;
78 :
79 964 : Item& rCached = *it->second;
80 964 : const ScTokenArray& rCode = *rCached.mpCell->GetCode();
81 964 : OUString aPredicted = rCode.CreateString(maCxt, rPos);
82 964 : if (rFormula == aPredicted)
83 405 : return &rCached;
84 :
85 559 : return NULL;
86 : }
87 :
88 662 : void store( const ScAddress& rPos, ScFormulaCell* pCell )
89 : {
90 662 : ColCacheType::iterator it = maCache.find(rPos.Col());
91 662 : if (it == maCache.end())
92 : {
93 : // Create an entry for this column.
94 : std::pair<ColCacheType::iterator,bool> r =
95 103 : maCache.insert(ColCacheType::value_type(rPos.Col(), new Item));
96 103 : if (!r.second)
97 : // Insertion failed.
98 662 : return;
99 :
100 103 : it = r.first;
101 : }
102 :
103 662 : Item& rItem = *it->second;
104 662 : rItem.mnRow = rPos.Row();
105 662 : rItem.mpCell = pCell;
106 : }
107 :
108 : private:
109 : typedef std::unordered_map<SCCOL, Item*> ColCacheType;
110 : ColCacheType maCache;
111 : sc::TokenStringContext maCxt;
112 : };
113 :
114 9 : void applySharedFormulas(
115 : ScDocumentImport& rDoc,
116 : SvNumberFormatter& rFormatter,
117 : std::vector<FormulaBuffer::SharedFormulaEntry>& rSharedFormulas,
118 : std::vector<FormulaBuffer::SharedFormulaDesc>& rCells )
119 : {
120 9 : sc::SharedFormulaGroups aGroups;
121 : {
122 : // Process shared formulas first.
123 9 : std::vector<FormulaBuffer::SharedFormulaEntry>::const_iterator it = rSharedFormulas.begin(), itEnd = rSharedFormulas.end();
124 72 : for (; it != itEnd; ++it)
125 : {
126 63 : const table::CellAddress& rAddr = it->maAddress;
127 63 : sal_Int32 nId = it->mnSharedId;
128 63 : const OUString& rTokenStr = it->maTokenStr;
129 :
130 63 : ScAddress aPos;
131 63 : ScUnoConversion::FillScAddress(aPos, rAddr);
132 63 : ScCompiler aComp(&rDoc.getDoc(), aPos);
133 63 : aComp.SetNumberFormatter(&rFormatter);
134 63 : aComp.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
135 63 : ScTokenArray* pArray = aComp.CompileString(rTokenStr);
136 63 : if (pArray)
137 : {
138 63 : aComp.CompileTokenArray(); // Generate RPN tokens.
139 63 : aGroups.set(nId, pArray);
140 : }
141 63 : }
142 : }
143 :
144 : {
145 : // Process formulas that use shared formulas.
146 9 : std::vector<FormulaBuffer::SharedFormulaDesc>::const_iterator it = rCells.begin(), itEnd = rCells.end();
147 1131 : for (; it != itEnd; ++it)
148 : {
149 1122 : const table::CellAddress& rAddr = it->maAddress;
150 1122 : const ScTokenArray* pArray = aGroups.get(it->mnSharedId);
151 1122 : if (!pArray)
152 0 : continue;
153 :
154 1122 : ScAddress aPos;
155 1122 : ScUnoConversion::FillScAddress(aPos, rAddr);
156 1122 : ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, *pArray);
157 1122 : rDoc.setFormulaCell(aPos, pCell);
158 1122 : 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 1122 : switch (it->mnValueType)
168 : {
169 : case XML_n:
170 : // numeric value.
171 364 : pCell->SetResultDouble(it->maCellValue.toDouble());
172 364 : break;
173 : default:
174 : // Mark it for re-calculation.
175 758 : pCell->SetDirty(true);
176 : }
177 : }
178 9 : }
179 9 : }
180 :
181 27 : 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 27 : std::vector<FormulaBuffer::TokenAddressItem>::const_iterator it = rCells.begin(), itEnd = rCells.end();
187 1094 : for (; it != itEnd; ++it)
188 : {
189 1067 : ScAddress aPos;
190 1067 : ScUnoConversion::FillScAddress(aPos, it->maCellAddress);
191 1067 : CachedTokenArray::Item* p = rCache.get(aPos, it->maTokenStr);
192 1067 : if (p)
193 : {
194 : // Use the cached version to avoid re-compilation.
195 :
196 405 : ScFormulaCell* pCell = NULL;
197 405 : if (p->mnRow + 1 == aPos.Row())
198 : {
199 : // Put them in the same formula group.
200 402 : ScFormulaCell& rPrev = *p->mpCell;
201 402 : ScFormulaCellGroupRef xGroup = rPrev.GetCellGroup();
202 402 : if (!xGroup)
203 : {
204 : // Last cell is not grouped yet. Start a new group.
205 : assert(rPrev.aPos.Row() == p->mnRow);
206 113 : xGroup = rPrev.CreateCellGroup(1, false);
207 : }
208 402 : ++xGroup->mnLength;
209 :
210 402 : pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, xGroup);
211 : }
212 : else
213 3 : pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, p->mpCell->GetCode()->Clone());
214 :
215 405 : rDoc.setFormulaCell(aPos, pCell);
216 :
217 : // Update the cache.
218 405 : p->mnRow = aPos.Row();
219 405 : p->mpCell = pCell;
220 810 : continue;
221 : }
222 :
223 662 : ScCompiler aCompiler(&rDoc.getDoc(), aPos);
224 662 : aCompiler.SetNumberFormatter(&rFormatter);
225 662 : aCompiler.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
226 662 : aCompiler.SetExternalLinks(rExternalLinks);
227 662 : ScTokenArray* pCode = aCompiler.CompileString(it->maTokenStr);
228 662 : if (!pCode)
229 0 : continue;
230 :
231 662 : aCompiler.CompileTokenArray(); // Generate RPN tokens.
232 662 : ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, pCode);
233 662 : rDoc.setFormulaCell(aPos, pCell);
234 662 : rCache.store(aPos, pCell);
235 662 : }
236 27 : }
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 26 : void applyCellFormulaValues(
260 : ScDocumentImport& rDoc, const std::vector<FormulaBuffer::FormulaValue>& rVector )
261 : {
262 26 : svl::SharedStringPool& rStrPool = rDoc.getDoc().GetSharedStringPool();
263 :
264 26 : std::vector<FormulaBuffer::FormulaValue>::const_iterator it = rVector.begin(), itEnd = rVector.end();
265 1091 : for (; it != itEnd; ++it)
266 : {
267 1065 : ScAddress aCellPos;
268 1065 : ScUnoConversion::FillScAddress(aCellPos, it->maCellAddress);
269 1065 : ScFormulaCell* pCell = rDoc.getDoc().GetFormulaCell(aCellPos);
270 1065 : const OUString& rValueStr = it->maValueStr;
271 1065 : if (!pCell)
272 0 : continue;
273 :
274 1065 : switch (it->mnCellType)
275 : {
276 : case XML_n:
277 : {
278 1030 : pCell->SetResultDouble(rValueStr.toDouble());
279 1030 : pCell->ResetDirty();
280 1030 : pCell->SetChanged(false);
281 : }
282 1030 : break;
283 : case XML_str:
284 : {
285 23 : svl::SharedString aSS = rStrPool.intern(rValueStr);
286 23 : pCell->SetResultToken(new formula::FormulaStringToken(aSS));
287 23 : pCell->ResetDirty();
288 23 : pCell->SetChanged(false);
289 : }
290 23 : break;
291 : default:
292 : ;
293 : }
294 : }
295 26 : }
296 :
297 269 : void processSheetFormulaCells(
298 : ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter& rFormatter,
299 : const uno::Sequence<sheet::ExternalLinkInfo>& rExternalLinks )
300 : {
301 269 : if (rItem.mpSharedFormulaEntries && rItem.mpSharedFormulaIDs)
302 9 : applySharedFormulas(rDoc, rFormatter, *rItem.mpSharedFormulaEntries, *rItem.mpSharedFormulaIDs);
303 :
304 269 : if (rItem.mpCellFormulas)
305 : {
306 27 : CachedTokenArray aCache(rDoc.getDoc());
307 27 : applyCellFormulas(rDoc, aCache, rFormatter, rExternalLinks, *rItem.mpCellFormulas);
308 : }
309 :
310 269 : if (rItem.mpArrayFormulas)
311 0 : applyArrayFormulas(rDoc, rFormatter, *rItem.mpArrayFormulas);
312 :
313 269 : if (rItem.mpCellFormulaValues)
314 26 : applyCellFormulaValues(rDoc, *rItem.mpCellFormulaValues);
315 269 : }
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 63 : FormulaBuffer::SharedFormulaEntry::SharedFormulaEntry(
343 : const table::CellAddress& rAddr, const table::CellRangeAddress& rRange,
344 : const OUString& rTokenStr, sal_Int32 nSharedId ) :
345 63 : maAddress(rAddr), maRange(rRange), maTokenStr(rTokenStr), mnSharedId(nSharedId) {}
346 :
347 1122 : FormulaBuffer::SharedFormulaDesc::SharedFormulaDesc(
348 : const com::sun::star::table::CellAddress& rAddr, sal_Int32 nSharedId,
349 : const OUString& rCellValue, sal_Int32 nValueType ) :
350 1122 : maAddress(rAddr), mnSharedId(nSharedId), maCellValue(rCellValue), mnValueType(nValueType) {}
351 :
352 269 : FormulaBuffer::SheetItem::SheetItem() :
353 : mpCellFormulas(NULL),
354 : mpArrayFormulas(NULL),
355 : mpCellFormulaValues(NULL),
356 : mpSharedFormulaEntries(NULL),
357 269 : mpSharedFormulaIDs(NULL) {}
358 :
359 145 : FormulaBuffer::FormulaBuffer( const WorkbookHelper& rHelper ) : WorkbookHelper( rHelper )
360 : {
361 145 : }
362 :
363 145 : void FormulaBuffer::SetSheetCount( SCTAB nSheets )
364 : {
365 145 : maCellFormulas.resize( nSheets );
366 145 : maCellArrayFormulas.resize( nSheets );
367 145 : maSharedFormulas.resize( nSheets );
368 145 : maSharedFormulaIds.resize( nSheets );
369 145 : maCellFormulaValues.resize( nSheets );
370 145 : }
371 :
372 145 : void FormulaBuffer::finalizeImport()
373 : {
374 145 : ISegmentProgressBarRef xFormulaBar = getProgressBar().createSegment( getProgressBar().getFreeLength() );
375 :
376 145 : const size_t nThreadCount = 1;
377 145 : ScDocumentImport& rDoc = getDocImport();
378 145 : rDoc.getDoc().SetAutoNameCache(new ScAutoNameCache(&rDoc.getDoc()));
379 290 : ScExternalRefManager::ApiGuard aExtRefGuard(&rDoc.getDoc());
380 :
381 145 : SCTAB nTabCount = rDoc.getDoc().GetTableCount();
382 :
383 : // Fetch all the formulas to process first.
384 290 : std::vector<SheetItem> aSheetItems;
385 145 : aSheetItems.reserve(nTabCount);
386 414 : for (SCTAB nTab = 0; nTab < nTabCount; ++nTab)
387 269 : aSheetItems.push_back(getSheetItem(nTab));
388 :
389 145 : std::vector<SheetItem>::iterator it = aSheetItems.begin(), itEnd = aSheetItems.end();
390 :
391 : if (nThreadCount == 1)
392 : {
393 414 : for (; it != itEnd; ++it)
394 269 : 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 145 : rDoc.getDoc().SetAutoNameCache(NULL);
429 :
430 290 : xFormulaBar->setPosition( 1.0 );
431 145 : }
432 :
433 269 : FormulaBuffer::SheetItem FormulaBuffer::getSheetItem( SCTAB nTab )
434 : {
435 269 : osl::MutexGuard aGuard(&maMtxData);
436 :
437 269 : SheetItem aItem;
438 :
439 269 : 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 269 : if( maCellFormulas[ nTab ].size() > 0 )
446 27 : aItem.mpCellFormulas = &maCellFormulas[ nTab ];
447 269 : if( maCellArrayFormulas[ nTab ].size() > 0 )
448 0 : aItem.mpArrayFormulas = &maCellArrayFormulas[ nTab ];
449 269 : if( maCellFormulaValues[ nTab ].size() > 0 )
450 26 : aItem.mpCellFormulaValues = &maCellFormulaValues[ nTab ];
451 269 : if( maSharedFormulas[ nTab ].size() > 0 )
452 9 : aItem.mpSharedFormulaEntries = &maSharedFormulas[ nTab ];
453 269 : if( maSharedFormulaIds[ nTab ].size() > 0 )
454 9 : aItem.mpSharedFormulaIDs = &maSharedFormulaIds[ nTab ];
455 :
456 269 : return aItem;
457 : }
458 :
459 63 : 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 63 : std::vector<SharedFormulaEntry>& rSharedFormulas = maSharedFormulas[ rAddress.Sheet ];
465 63 : SharedFormulaEntry aEntry(rAddress, rRange, rTokens, nSharedId);
466 63 : rSharedFormulas.push_back( aEntry );
467 63 : }
468 :
469 1067 : 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 1067 : maCellFormulas[ rAddress.Sheet ].push_back( TokenAddressItem( rTokenStr, rAddress ) );
473 1067 : }
474 :
475 1122 : 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 1122 : maSharedFormulaIds[rAddress.Sheet].push_back(
480 2244 : SharedFormulaDesc(rAddress, nSharedId, rCellValue, nValueType));
481 1122 : }
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 1065 : 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 1065 : FormulaValue aVal;
496 1065 : aVal.maCellAddress = rAddress;
497 1065 : aVal.maValueStr = rValueStr;
498 1065 : aVal.mnCellType = nCellType;
499 1065 : maCellFormulaValues[rAddress.Sheet].push_back(aVal);
500 1065 : }
501 :
502 30 : }}
503 :
504 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|