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