/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star::uno; using namespace ::com::sun::star::sheet; #include namespace oox { namespace xls { namespace { /** * Cache the token array for the last cell position in each column. We use * one cache per sheet. */ class CachedTokenArray { public: CachedTokenArray(const CachedTokenArray&) = delete; const CachedTokenArray& operator=(const CachedTokenArray&) = delete; struct Item { SCROW mnRow; ScFormulaCell* mpCell; Item(const Item&) = delete; const Item& operator=(const Item&) = delete; Item() : mnRow(-1), mpCell(nullptr) {} }; explicit CachedTokenArray( ScDocument& rDoc ) : maCxt(&rDoc, formula::FormulaGrammar::GRAM_OOXML) {} Item* get( const ScAddress& rPos, const OUString& rFormula ) { // Check if a token array is cached for this column. ColCacheType::iterator it = maCache.find(rPos.Col()); if (it == maCache.end()) return nullptr; Item& rCached = *it->second; const ScTokenArray& rCode = *rCached.mpCell->GetCode(); OUString aPredicted = rCode.CreateString(maCxt, rPos); if (rFormula == aPredicted) return &rCached; return nullptr; } void store( const ScAddress& rPos, ScFormulaCell* pCell ) { ColCacheType::iterator it = maCache.find(rPos.Col()); if (it == maCache.end()) { // Create an entry for this column. std::pair r = maCache.emplace(rPos.Col(), std::make_unique()); if (!r.second) // Insertion failed. return; it = r.first; } Item& rItem = *it->second; rItem.mnRow = rPos.Row(); rItem.mpCell = pCell; } private: typedef std::unordered_map> ColCacheType; ColCacheType maCache; sc::TokenStringContext maCxt; }; void applySharedFormulas( ScDocumentImport& rDoc, SvNumberFormatter& rFormatter, std::vector& rSharedFormulas, std::vector& rCells, bool bGeneratorKnownGood) { sc::SharedFormulaGroups aGroups; { // Process shared formulas first. for (const FormulaBuffer::SharedFormulaEntry& rEntry : rSharedFormulas) { const ScAddress& aPos = rEntry.maAddress; sal_Int32 nId = rEntry.mnSharedId; const OUString& rTokenStr = rEntry.maTokenStr; ScCompiler aComp(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false); aComp.SetNumberFormatter(&rFormatter); std::unique_ptr pArray = aComp.CompileString(rTokenStr); if (pArray) { aComp.CompileTokenArray(); // Generate RPN tokens. aGroups.set(nId, std::move(pArray)); } } } { svl::SharedStringPool& rStrPool = rDoc.getDoc().GetSharedStringPool(); // Process formulas that use shared formulas. for (const FormulaBuffer::SharedFormulaDesc& rDesc : rCells) { const ScAddress& aPos = rDesc.maAddress; const ScTokenArray* pArray = aGroups.get(rDesc.mnSharedId); if (!pArray) continue; ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, *pArray); rDoc.setFormulaCell(aPos, pCell); if (rDesc.maCellValue.isEmpty()) { // No cached cell value. Mark it for re-calculation. pCell->SetDirty(); continue; } // Set cached formula results. For now, we only use numeric and string-formula // results. Find out how to utilize cached results of other types. switch (rDesc.mnValueType) { case XML_n: // numeric value. pCell->SetResultDouble(rDesc.maCellValue.toDouble()); /* TODO: is it on purpose that we never reset dirty here * and thus recalculate anyway if cell was dirty? Or is it * never dirty and therefore set dirty below otherwise? This * is different from the non-shared case in * applyCellFormulaValues(). */ break; case XML_str: if (bGeneratorKnownGood) { // See applyCellFormulaValues svl::SharedString aSS = rStrPool.intern(rDesc.maCellValue); pCell->SetResultToken(new formula::FormulaStringToken(aSS)); // If we don't reset dirty, then e.g. disabling macros makes all cells // that use macro functions to show #VALUE! pCell->ResetDirty(); pCell->SetChanged(false); break; } [[fallthrough]]; default: // Mark it for re-calculation. pCell->SetDirty(); } } } } void applyCellFormulas( ScDocumentImport& rDoc, CachedTokenArray& rCache, SvNumberFormatter& rFormatter, const Sequence& rExternalLinks, const std::vector& rCells ) { for (const FormulaBuffer::TokenAddressItem& rItem : rCells) { const ScAddress& aPos = rItem.maAddress; CachedTokenArray::Item* p = rCache.get(aPos, rItem.maTokenStr); if (p) { // Use the cached version to avoid re-compilation. ScFormulaCell* pCell = nullptr; if (p->mnRow + 1 == aPos.Row()) { // Put them in the same formula group. ScFormulaCell& rPrev = *p->mpCell; ScFormulaCellGroupRef xGroup = rPrev.GetCellGroup(); if (!xGroup) { // Last cell is not grouped yet. Start a new group. assert(rPrev.aPos.Row() == p->mnRow); xGroup = rPrev.CreateCellGroup(1, false); } ++xGroup->mnLength; pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, xGroup); } else pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, p->mpCell->GetCode()->Clone()); rDoc.setFormulaCell(aPos, pCell); // Update the cache. p->mnRow = aPos.Row(); p->mpCell = pCell; continue; } ScCompiler aCompiler(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false); aCompiler.SetNumberFormatter(&rFormatter); aCompiler.SetExternalLinks(rExternalLinks); std::unique_ptr pCode = aCompiler.CompileString(rItem.maTokenStr); if (!pCode) continue; aCompiler.CompileTokenArray(); // Generate RPN tokens. ScFormulaCell* pCell = new ScFormulaCell(&rDoc.getDoc(), aPos, std::move(pCode)); rDoc.setFormulaCell(aPos, pCell); rCache.store(aPos, pCell); } } void applyArrayFormulas( ScDocumentImport& rDoc, SvNumberFormatter& rFormatter, const std::vector& rArrays ) { for (const FormulaBuffer::TokenRangeAddressItem& rAddressItem : rArrays) { const ScAddress& aPos = rAddressItem.maTokenAndAddress.maAddress; ScCompiler aComp(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML); aComp.SetNumberFormatter(&rFormatter); std::unique_ptr pArray(aComp.CompileString(rAddressItem.maTokenAndAddress.maTokenStr)); if (pArray) rDoc.setMatrixCells(rAddressItem.maRange, *pArray, formula::FormulaGrammar::GRAM_OOXML); } } void applyCellFormulaValues( ScDocumentImport& rDoc, const std::vector& rVector, bool bGeneratorKnownGood ) { svl::SharedStringPool& rStrPool = rDoc.getDoc().GetSharedStringPool(); for (const FormulaBuffer::FormulaValue& rValue : rVector) { const ScAddress& aCellPos = rValue.maAddress; ScFormulaCell* pCell = rDoc.getDoc().GetFormulaCell(aCellPos); const OUString& rValueStr = rValue.maValueStr; if (!pCell) continue; switch (rValue.mnCellType) { case XML_n: { pCell->SetResultDouble(rValueStr.toDouble()); pCell->ResetDirty(); pCell->SetChanged(false); } break; case XML_str: // Excel uses t="str" for string results (per definition // ECMA-376 18.18.11 ST_CellType (Cell Type) "Cell containing a // formula string.", but that 't' Cell Data Type attribute, "an // enumeration representing the cell's data type", is meant for // the content of the element). We follow that. Other // applications might not and instead use t="str" for the cell // content if formula. Setting an otherwise numeric result as // string result fouls things up, set result strings only for // documents claiming to be generated by a known good // generator. See tdf#98481 if (bGeneratorKnownGood) { svl::SharedString aSS = rStrPool.intern(rValueStr); pCell->SetResultToken(new formula::FormulaStringToken(aSS)); pCell->ResetDirty(); pCell->SetChanged(false); } break; default: ; } } } void processSheetFormulaCells( ScDocumentImport& rDoc, FormulaBuffer::SheetItem& rItem, SvNumberFormatter& rFormatter, const Sequence& rExternalLinks, bool bGeneratorKnownGood ) { if (rItem.mpSharedFormulaEntries && rItem.mpSharedFormulaIDs) applySharedFormulas(rDoc, rFormatter, *rItem.mpSharedFormulaEntries, *rItem.mpSharedFormulaIDs, bGeneratorKnownGood); if (rItem.mpCellFormulas) { CachedTokenArray aCache(rDoc.getDoc()); applyCellFormulas(rDoc, aCache, rFormatter, rExternalLinks, *rItem.mpCellFormulas); } if (rItem.mpArrayFormulas) applyArrayFormulas(rDoc, rFormatter, *rItem.mpArrayFormulas); if (rItem.mpCellFormulaValues) applyCellFormulaValues(rDoc, *rItem.mpCellFormulaValues, bGeneratorKnownGood); } } FormulaBuffer::SharedFormulaEntry::SharedFormulaEntry( const ScAddress& rAddr, const OUString& rTokenStr, sal_Int32 nSharedId ) : maAddress(rAddr), maTokenStr(rTokenStr), mnSharedId(nSharedId) {} FormulaBuffer::SharedFormulaDesc::SharedFormulaDesc( const ScAddress& rAddr, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType ) : maAddress(rAddr), mnSharedId(nSharedId), maCellValue(rCellValue), mnValueType(nValueType) {} FormulaBuffer::SheetItem::SheetItem() : mpCellFormulas(nullptr), mpArrayFormulas(nullptr), mpCellFormulaValues(nullptr), mpSharedFormulaEntries(nullptr), mpSharedFormulaIDs(nullptr) {} FormulaBuffer::FormulaBuffer( const WorkbookHelper& rHelper ) : WorkbookHelper( rHelper ) { } void FormulaBuffer::SetSheetCount( SCTAB nSheets ) { maCellFormulas.resize( nSheets ); maCellArrayFormulas.resize( nSheets ); maSharedFormulas.resize( nSheets ); maSharedFormulaIds.resize( nSheets ); maCellFormulaValues.resize( nSheets ); } void FormulaBuffer::finalizeImport() { ISegmentProgressBarRef xFormulaBar = getProgressBar().createSegment( getProgressBar().getFreeLength() ); ScDocumentImport& rDoc = getDocImport(); rDoc.getDoc().SetAutoNameCache(std::make_unique(&rDoc.getDoc())); ScExternalRefManager::ApiGuard aExtRefGuard(&rDoc.getDoc()); SCTAB nTabCount = rDoc.getDoc().GetTableCount(); // Fetch all the formulas to process first. std::vector aSheetItems; aSheetItems.reserve(nTabCount); for (SCTAB nTab = 0; nTab < nTabCount; ++nTab) aSheetItems.push_back(getSheetItem(nTab)); for (SheetItem& rItem : aSheetItems) processSheetFormulaCells(rDoc, rItem, *rDoc.getDoc().GetFormatTable(), getExternalLinks().getLinkInfos(), isGeneratorKnownGood()); // With formula results being set and not recalculated we need to // force-trigger adding all linked external files to the LinkManager. rDoc.getDoc().GetExternalRefManager()->addFilesToLinkManager(); rDoc.getDoc().SetAutoNameCache(nullptr); xFormulaBar->setPosition( 1.0 ); } FormulaBuffer::SheetItem FormulaBuffer::getSheetItem( SCTAB nTab ) { osl::MutexGuard aGuard(&maMtxData); SheetItem aItem; if( static_cast(nTab) >= maCellFormulas.size() ) { SAL_WARN( "sc", "Tab " << nTab << " out of bounds " << maCellFormulas.size() ); return aItem; } if( !maCellFormulas[ nTab ].empty() ) aItem.mpCellFormulas = &maCellFormulas[ nTab ]; if( !maCellArrayFormulas[ nTab ].empty() ) aItem.mpArrayFormulas = &maCellArrayFormulas[ nTab ]; if( !maCellFormulaValues[ nTab ].empty() ) aItem.mpCellFormulaValues = &maCellFormulaValues[ nTab ]; if( !maSharedFormulas[ nTab ].empty() ) aItem.mpSharedFormulaEntries = &maSharedFormulas[ nTab ]; if( !maSharedFormulaIds[ nTab ].empty() ) aItem.mpSharedFormulaIDs = &maSharedFormulaIds[ nTab ]; return aItem; } void FormulaBuffer::createSharedFormulaMapEntry( const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rTokens ) { assert( rAddress.Tab() >= 0 && static_cast(rAddress.Tab()) < maSharedFormulas.size() ); std::vector& rSharedFormulas = maSharedFormulas[ rAddress.Tab() ]; SharedFormulaEntry aEntry(rAddress, rTokens, nSharedId); rSharedFormulas.push_back( aEntry ); } void FormulaBuffer::setCellFormula( const ScAddress& rAddress, const OUString& rTokenStr ) { assert( rAddress.Tab() >= 0 && static_cast(rAddress.Tab()) < maCellFormulas.size() ); maCellFormulas[ rAddress.Tab() ].emplace_back( rTokenStr, rAddress ); } void FormulaBuffer::setCellFormula( const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType ) { assert( rAddress.Tab() >= 0 && static_cast(rAddress.Tab()) < maSharedFormulaIds.size() ); maSharedFormulaIds[rAddress.Tab()].emplace_back(rAddress, nSharedId, rCellValue, nValueType); } void FormulaBuffer::setCellArrayFormula( const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr ) { TokenAddressItem tokenPair( rTokenStr, rTokenAddress ); assert( rRangeAddress.aStart.Tab() >= 0 && static_cast(rRangeAddress.aStart.Tab()) < maCellArrayFormulas.size() ); maCellArrayFormulas[ rRangeAddress.aStart.Tab() ].emplace_back( tokenPair, rRangeAddress ); } void FormulaBuffer::setCellFormulaValue( const ScAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType ) { assert( rAddress.Tab() >= 0 && static_cast(rAddress.Tab()) < maCellFormulaValues.size() ); FormulaValue aVal; aVal.maAddress = rAddress; aVal.maValueStr = rValueStr; aVal.mnCellType = nCellType; maCellFormulaValues[rAddress.Tab()].push_back(aVal); } }} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */