/* -*- 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using ::rtl::math::approxEqual; using ::std::vector; using ::std::set; // iterators have very high frequency use -> custom debug. // #define debugiter(...) fprintf(stderr, __VA_ARGS__) #define debugiter(...) namespace { template void incBlock(std::pair& rPos) { // Move to the next block. ++rPos.first; rPos.second = 0; } template void decBlock(std::pair& rPos) { // Move to the last element of the previous block. --rPos.first; rPos.second = rPos.first->size - 1; } } static void ScAttrArray_IterGetNumberFormat( sal_uInt32& nFormat, const ScAttrArray*& rpArr, SCROW& nAttrEndRow, const ScAttrArray* pNewArr, SCROW nRow, const ScDocument& rDoc, const ScInterpreterContext* pContext = nullptr ) { if ( rpArr == pNewArr && nAttrEndRow >= nRow ) return; SCROW nRowStart = 0; SCROW nRowEnd = rDoc.MaxRow(); const ScPatternAttr* pPattern = pNewArr->GetPatternRange( nRowStart, nRowEnd, nRow ); if( !pPattern ) { pPattern = rDoc.GetDefPattern(); nRowEnd = rDoc.MaxRow(); } nFormat = pPattern->GetNumberFormat( pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable() ); rpArr = pNewArr; nAttrEndRow = nRowEnd; } ScValueIterator::ScValueIterator( ScDocument& rDocument, const ScRange& rRange, SubtotalFlags nSubTotalFlags, bool bTextZero ) : mrDoc(rDocument) , pContext(nullptr) , pAttrArray(nullptr) , nNumFormat(0) // Initialized in GetNumberFormat , nNumFmtIndex(0) , maStartPos(rRange.aStart) , maEndPos(rRange.aEnd) , mnCol(0) , mnTab(0) , nAttrEndRow(0) , mnSubTotalFlags(nSubTotalFlags) , nNumFmtType(SvNumFormatType::UNDEFINED) , bNumValid(false) , bCalcAsShown(rDocument.GetDocOptions().IsCalcAsShown()) , bTextAsZero(bTextZero) , mpCells(nullptr) { SCTAB nDocMaxTab = rDocument.GetTableCount() - 1; if (!rDocument.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol()); if (!rDocument.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol()); if (!rDocument.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow()); if (!rDocument.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow()); if (!ValidTab(maStartPos.Tab()) || maStartPos.Tab() > nDocMaxTab) maStartPos.SetTab(nDocMaxTab); if (!ValidTab(maEndPos.Tab()) || maEndPos.Tab() > nDocMaxTab) maEndPos.SetTab(nDocMaxTab); } SCROW ScValueIterator::GetRow() const { // Position of the head of the current block + offset within the block // equals the logical element position. return maCurPos.first->position + maCurPos.second; } void ScValueIterator::IncBlock() { ++maCurPos.first; maCurPos.second = 0; } void ScValueIterator::IncPos() { if (maCurPos.second + 1 < maCurPos.first->size) // Move within the same block. ++maCurPos.second; else // Move to the next block. IncBlock(); } bool ScValueIterator::GetThis(double& rValue, FormulaError& rErr) { while (true) { bool bNextColumn = !mpCells || maCurPos.first == mpCells->end(); if (!bNextColumn) { if (GetRow() > maEndPos.Row()) bNextColumn = true; } ScColumn* pCol; if (!bNextColumn) pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol]; else { // Find the next available column. do { ++mnCol; if (mnCol > maEndPos.Col() || mnCol >= mrDoc.maTabs[mnTab]->GetAllocatedColumnsCount()) { mnCol = maStartPos.Col(); ++mnTab; if (mnTab > maEndPos.Tab()) { rErr = FormulaError::NONE; return false; // Over and out } } pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol]; } while (pCol->IsEmptyData()); mpCells = &pCol->maCells; maCurPos = mpCells->position(maStartPos.Row()); } SCROW nCurRow = GetRow(); SCROW nLastRow; // Skip all filtered or hidden rows, depending on mnSubTotalFlags if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) && mrDoc.RowFiltered( nCurRow, mnTab, nullptr, &nLastRow ) ) || ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) && mrDoc.RowHidden( nCurRow, mnTab, nullptr, &nLastRow ) ) ) { maCurPos = mpCells->position(maCurPos.first, nLastRow+1); continue; } switch (maCurPos.first->type) { case sc::element_type_numeric: { bNumValid = false; rValue = sc::numeric_block::at(*maCurPos.first->data, maCurPos.second); rErr = FormulaError::NONE; if (bCalcAsShown) { ScAttrArray_IterGetNumberFormat(nNumFormat, pAttrArray, nAttrEndRow, pCol->pAttrArray.get(), nCurRow, mrDoc, pContext); rValue = mrDoc.RoundValueAsShown(rValue, nNumFormat, pContext); } return true; // Found it! } break; case sc::element_type_formula: { ScFormulaCell& rCell = *sc::formula_block::at(*maCurPos.first->data, maCurPos.second); if ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && rCell.IsSubTotal() ) { // Skip subtotal formula cells. IncPos(); break; } if (rCell.GetErrorOrValue(rErr, rValue)) { if ( rErr != FormulaError::NONE && ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) { IncPos(); break; } bNumValid = false; return true; // Found it! } else if (bTextAsZero) { rValue = 0.0; bNumValid = false; return true; } IncPos(); } break; case sc::element_type_string : case sc::element_type_edittext : { if (bTextAsZero) { rErr = FormulaError::NONE; rValue = 0.0; nNumFmtType = SvNumFormatType::NUMBER; nNumFmtIndex = 0; bNumValid = true; return true; } IncBlock(); } break; case sc::element_type_empty: default: // Skip the whole block. IncBlock(); } } } void ScValueIterator::GetCurNumFmtInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex ) { if (!bNumValid && mnTab < mrDoc.GetTableCount()) { SCROW nCurRow = GetRow(); const ScColumn* pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol]; nNumFmtIndex = pCol->GetNumberFormat(rContext, nCurRow); nNumFmtType = rContext.GetNumberFormatType( nNumFmtIndex ); bNumValid = true; } nType = nNumFmtType; nIndex = nNumFmtIndex; } bool ScValueIterator::GetFirst(double& rValue, FormulaError& rErr) { mnCol = maStartPos.Col(); mnTab = maStartPos.Tab(); ScTable* pTab = mrDoc.FetchTable(mnTab); if (!pTab) return false; nNumFormat = 0; // Initialized in GetNumberFormat pAttrArray = nullptr; nAttrEndRow = 0; auto nCol = maStartPos.Col(); if (nCol < pTab->GetAllocatedColumnsCount()) { mpCells = &pTab->aCol[nCol].maCells; maCurPos = mpCells->position(maStartPos.Row()); } else mpCells = nullptr; return GetThis(rValue, rErr); } bool ScValueIterator::GetNext(double& rValue, FormulaError& rErr) { IncPos(); return GetThis(rValue, rErr); } ScDBQueryDataIterator::DataAccess::DataAccess() { } ScDBQueryDataIterator::DataAccess::~DataAccess() { } const sc::CellStoreType* ScDBQueryDataIterator::GetColumnCellStore(ScDocument& rDoc, SCTAB nTab, SCCOL nCol) { ScTable* pTab = rDoc.FetchTable(nTab); if (!pTab) return nullptr; if (nCol >= pTab->GetAllocatedColumnsCount()) return nullptr; return &pTab->aCol[nCol].maCells; } const ScAttrArray* ScDBQueryDataIterator::GetAttrArrayByCol(ScDocument& rDoc, SCTAB nTab, SCCOL nCol) { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); ScColumn* pCol = &rDoc.maTabs[nTab]->aCol[nCol]; return pCol->pAttrArray.get(); } bool ScDBQueryDataIterator::IsQueryValid( ScDocument& rDoc, const ScQueryParam& rParam, SCTAB nTab, SCROW nRow, const ScRefCellValue* pCell) { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); return rDoc.maTabs[nTab]->ValidQuery(nRow, rParam, pCell); } ScDBQueryDataIterator::DataAccessInternal::DataAccessInternal(ScDBQueryParamInternal* pParam, ScDocument& rDoc, const ScInterpreterContext& rContext) : DataAccess() , mpCells(nullptr) , mpParam(pParam) , mrDoc(rDoc) , mrContext(rContext) , pAttrArray(nullptr) , nNumFormat(0) // Initialized in GetNumberFormat , nNumFmtIndex(0) , nCol(mpParam->mnField) , nRow(mpParam->nRow1) , nAttrEndRow(0) , nTab(mpParam->nTab) , nNumFmtType(SvNumFormatType::ALL) , bCalcAsShown(rDoc.GetDocOptions().IsCalcAsShown()) { SCSIZE i; SCSIZE nCount = mpParam->GetEntryCount(); for (i=0; (iGetEntry(i).bDoQuery); i++) { ScQueryEntry& rEntry = mpParam->GetEntry(i); ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); rItems.resize(1); ScQueryEntry::Item& rItem = rItems.front(); sal_uInt32 nIndex = 0; bool bNumber = mrDoc.GetFormatTable()->IsNumberFormat( rItem.maString.getString(), nIndex, rItem.mfVal); rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; } } ScDBQueryDataIterator::DataAccessInternal::~DataAccessInternal() { } bool ScDBQueryDataIterator::DataAccessInternal::getCurrent(Value& rValue) { // Start with the current row position, and find the first row position // that satisfies the query. // If the query starts in the same column as the result vector we can // prefetch the cell which saves us one fetch in the success case. SCCOLROW nFirstQueryField = mpParam->GetEntry(0).nField; ScRefCellValue aCell; while (true) { if (maCurPos.first == mpCells->end() || nRow > mpParam->nRow2) { // Bottom of the range reached. Bail out. rValue.mnError = FormulaError::NONE; return false; } if (maCurPos.first->type == sc::element_type_empty) { // Skip the whole empty block. incBlock(); continue; } ScRefCellValue* pCell = nullptr; if (nCol == static_cast(nFirstQueryField)) { aCell = sc::toRefCell(maCurPos.first, maCurPos.second); pCell = &aCell; } if (ScDBQueryDataIterator::IsQueryValid(mrDoc, *mpParam, nTab, nRow, pCell)) { if (!pCell) aCell = sc::toRefCell(maCurPos.first, maCurPos.second); switch (aCell.meType) { case CELLTYPE_VALUE: { rValue.mfValue = aCell.mfValue; rValue.mbIsNumber = true; if ( bCalcAsShown ) { const ScAttrArray* pNewAttrArray = ScDBQueryDataIterator::GetAttrArrayByCol(mrDoc, nTab, nCol); ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray, nAttrEndRow, pNewAttrArray, nRow, mrDoc ); rValue.mfValue = mrDoc.RoundValueAsShown( rValue.mfValue, nNumFormat ); } nNumFmtType = SvNumFormatType::NUMBER; nNumFmtIndex = 0; rValue.mnError = FormulaError::NONE; return true; // Found it! } case CELLTYPE_FORMULA: { if (aCell.mpFormula->IsValue()) { rValue.mfValue = aCell.mpFormula->GetValue(); rValue.mbIsNumber = true; mrDoc.GetNumberFormatInfo( mrContext, nNumFmtType, nNumFmtIndex, ScAddress(nCol, nRow, nTab)); rValue.mnError = aCell.mpFormula->GetErrCode(); return true; // Found it! } else if(mpParam->mbSkipString) incPos(); else { rValue.maString = aCell.mpFormula->GetString().getString(); rValue.mfValue = 0.0; rValue.mnError = aCell.mpFormula->GetErrCode(); rValue.mbIsNumber = false; return true; } } break; case CELLTYPE_STRING: case CELLTYPE_EDIT: if (mpParam->mbSkipString) incPos(); else { rValue.maString = aCell.getString(&mrDoc); rValue.mfValue = 0.0; rValue.mnError = FormulaError::NONE; rValue.mbIsNumber = false; return true; } break; default: incPos(); } } else incPos(); } // statement unreachable } bool ScDBQueryDataIterator::DataAccessInternal::getFirst(Value& rValue) { if (mpParam->bHasHeader) ++nRow; mpCells = ScDBQueryDataIterator::GetColumnCellStore(mrDoc, nTab, nCol); if (!mpCells) return false; maCurPos = mpCells->position(nRow); return getCurrent(rValue); } bool ScDBQueryDataIterator::DataAccessInternal::getNext(Value& rValue) { if (!mpCells || maCurPos.first == mpCells->end()) return false; incPos(); return getCurrent(rValue); } void ScDBQueryDataIterator::DataAccessInternal::incBlock() { ++maCurPos.first; maCurPos.second = 0; nRow = maCurPos.first->position; } void ScDBQueryDataIterator::DataAccessInternal::incPos() { if (maCurPos.second + 1 < maCurPos.first->size) { // Move within the same block. ++maCurPos.second; ++nRow; } else // Move to the next block. incBlock(); } ScDBQueryDataIterator::DataAccessMatrix::DataAccessMatrix(ScDBQueryParamMatrix* pParam) : DataAccess() , mpParam(pParam) , mnCurRow(0) { SCSIZE nC, nR; mpParam->mpMatrix->GetDimensions(nC, nR); mnRows = static_cast(nR); } ScDBQueryDataIterator::DataAccessMatrix::~DataAccessMatrix() { } bool ScDBQueryDataIterator::DataAccessMatrix::getCurrent(Value& rValue) { // Starting from row == mnCurRow, get the first row that satisfies all the // query parameters. for ( ;mnCurRow < mnRows; ++mnCurRow) { const ScMatrix& rMat = *mpParam->mpMatrix; if (rMat.IsEmpty(mpParam->mnField, mnCurRow)) // Don't take empty values into account. continue; bool bIsStrVal = rMat.IsStringOrEmpty(mpParam->mnField, mnCurRow); if (bIsStrVal && mpParam->mbSkipString) continue; if (isValidQuery(mnCurRow, rMat)) { rValue.maString = rMat.GetString(mpParam->mnField, mnCurRow).getString(); rValue.mfValue = rMat.GetDouble(mpParam->mnField, mnCurRow); rValue.mbIsNumber = !bIsStrVal; rValue.mnError = FormulaError::NONE; return true; } } return false; } bool ScDBQueryDataIterator::DataAccessMatrix::getFirst(Value& rValue) { mnCurRow = mpParam->bHasHeader ? 1 : 0; return getCurrent(rValue); } bool ScDBQueryDataIterator::DataAccessMatrix::getNext(Value& rValue) { ++mnCurRow; return getCurrent(rValue); } namespace { bool isQueryByValue(const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow) { if (rItem.meType == ScQueryEntry::ByString) return false; if (!rMat.IsValueOrEmpty(nCol, nRow)) return false; return true; } bool isQueryByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow) { switch (rEntry.eOp) { case SC_EQUAL: case SC_NOT_EQUAL: case SC_CONTAINS: case SC_DOES_NOT_CONTAIN: case SC_BEGINS_WITH: case SC_ENDS_WITH: case SC_DOES_NOT_BEGIN_WITH: case SC_DOES_NOT_END_WITH: return true; default: ; } return rItem.meType == ScQueryEntry::ByString && rMat.IsStringOrEmpty(nCol, nRow); } } bool ScDBQueryDataIterator::DataAccessMatrix::isValidQuery(SCROW nRow, const ScMatrix& rMat) const { SCSIZE nEntryCount = mpParam->GetEntryCount(); vector aResults; aResults.reserve(nEntryCount); const CollatorWrapper& rCollator = mpParam->bCaseSens ? *ScGlobal::GetCaseCollator() : *ScGlobal::GetCollator(); for (SCSIZE i = 0; i < nEntryCount; ++i) { const ScQueryEntry& rEntry = mpParam->GetEntry(i); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); if (!rEntry.bDoQuery) continue; switch (rEntry.eOp) { case SC_EQUAL: case SC_LESS: case SC_GREATER: case SC_LESS_EQUAL: case SC_GREATER_EQUAL: case SC_NOT_EQUAL: break; default: // Only the above operators are supported. continue; } bool bValid = false; SCSIZE nField = static_cast(rEntry.nField); if (isQueryByValue(rItem, rMat, nField, nRow)) { // By value double fMatVal = rMat.GetDouble(nField, nRow); bool bEqual = approxEqual(fMatVal, rItem.mfVal); switch (rEntry.eOp) { case SC_EQUAL: bValid = bEqual; break; case SC_LESS: bValid = (fMatVal < rItem.mfVal) && !bEqual; break; case SC_GREATER: bValid = (fMatVal > rItem.mfVal) && !bEqual; break; case SC_LESS_EQUAL: bValid = (fMatVal < rItem.mfVal) || bEqual; break; case SC_GREATER_EQUAL: bValid = (fMatVal > rItem.mfVal) || bEqual; break; case SC_NOT_EQUAL: bValid = !bEqual; break; default: ; } } else if (isQueryByString(rEntry, rItem, rMat, nField, nRow)) { // By string do { // Equality check first. svl::SharedString aMatStr = rMat.GetString(nField, nRow); svl::SharedString aQueryStr = rEntry.GetQueryItem().maString; bool bDone = false; rtl_uString* p1 = mpParam->bCaseSens ? aMatStr.getData() : aMatStr.getDataIgnoreCase(); rtl_uString* p2 = mpParam->bCaseSens ? aQueryStr.getData() : aQueryStr.getDataIgnoreCase(); switch (rEntry.eOp) { case SC_EQUAL: bValid = (p1 == p2); bDone = true; break; case SC_NOT_EQUAL: bValid = (p1 != p2); bDone = true; break; default: ; } if (bDone) break; // Unequality check using collator. sal_Int32 nCompare = rCollator.compareString(aMatStr.getString(), aQueryStr.getString()); switch (rEntry.eOp) { case SC_LESS : bValid = (nCompare < 0); break; case SC_GREATER : bValid = (nCompare > 0); break; case SC_LESS_EQUAL : bValid = (nCompare <= 0); break; case SC_GREATER_EQUAL : bValid = (nCompare >= 0); break; default: ; } } while (false); } if (aResults.empty()) // First query entry. aResults.push_back(bValid); else if (rEntry.eConnect == SC_AND) { // For AND op, tuck the result into the last result value. size_t n = aResults.size(); aResults[n-1] = aResults[n-1] && bValid; } else // For OR op, store its own result. aResults.push_back(bValid); } // Row is valid as long as there is at least one result being true. return std::find(aResults.begin(), aResults.end(), true) != aResults.end(); } ScDBQueryDataIterator::Value::Value() : mnError(FormulaError::NONE), mbIsNumber(true) { ::rtl::math::setNan(&mfValue); } ScDBQueryDataIterator::ScDBQueryDataIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, std::unique_ptr pParam) : mpParam (std::move(pParam)) { switch (mpParam->GetType()) { case ScDBQueryParamBase::INTERNAL: { ScDBQueryParamInternal* p = static_cast(mpParam.get()); mpData.reset(new DataAccessInternal(p, rDocument, rContext)); } break; case ScDBQueryParamBase::MATRIX: { ScDBQueryParamMatrix* p = static_cast(mpParam.get()); mpData.reset(new DataAccessMatrix(p)); } } } bool ScDBQueryDataIterator::GetFirst(Value& rValue) { return mpData->getFirst(rValue); } bool ScDBQueryDataIterator::GetNext(Value& rValue) { return mpData->getNext(rValue); } ScFormulaGroupIterator::ScFormulaGroupIterator( ScDocument& rDoc ) : mrDoc(rDoc), mnTab(0), mnCol(0), mnIndex(0) { ScTable *pTab = mrDoc.FetchTable(mnTab); ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr; if (pCol) { mbNullCol = false; maEntries = pCol->GetFormulaGroupEntries(); } else mbNullCol = true; } sc::FormulaGroupEntry* ScFormulaGroupIterator::first() { return next(); } sc::FormulaGroupEntry* ScFormulaGroupIterator::next() { if (mnIndex >= maEntries.size() || mbNullCol) { while (mnIndex >= maEntries.size() || mbNullCol) { mnIndex = 0; mnCol++; if (mnCol > mrDoc.MaxCol()) { mnCol = 0; mnTab++; if (mnTab >= mrDoc.GetTableCount()) return nullptr; } ScTable *pTab = mrDoc.FetchTable(mnTab); ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr; if (pCol) { mbNullCol = false; maEntries = pCol->GetFormulaGroupEntries(); } else mbNullCol = true; } } return &maEntries[mnIndex++]; } ScCellIterator::ScCellIterator( ScDocument& rDoc, const ScRange& rRange, SubtotalFlags nSubTotalFlags ) : mrDoc(rDoc), maStartPos(rRange.aStart), maEndPos(rRange.aEnd), mnSubTotalFlags(nSubTotalFlags) { init(); } void ScCellIterator::incBlock() { ++maCurColPos.first; maCurColPos.second = 0; maCurPos.SetRow(maCurColPos.first->position); } void ScCellIterator::incPos() { if (maCurColPos.second + 1 < maCurColPos.first->size) { // Move within the same block. ++maCurColPos.second; maCurPos.IncRow(); } else // Move to the next block. incBlock(); } void ScCellIterator::setPos(size_t nPos) { maCurColPos = getColumn()->maCells.position(maCurColPos.first, nPos); maCurPos.SetRow(nPos); } const ScColumn* ScCellIterator::getColumn() const { return &mrDoc.maTabs[maCurPos.Tab()]->aCol[maCurPos.Col()]; } void ScCellIterator::init() { SCTAB nDocMaxTab = mrDoc.GetTableCount() - 1; PutInOrder(maStartPos, maEndPos); if (!mrDoc.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol()); if (!mrDoc.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol()); if (!mrDoc.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow()); if (!mrDoc.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow()); if (!ValidTab(maStartPos.Tab(), nDocMaxTab)) maStartPos.SetTab(nDocMaxTab); if (!ValidTab(maEndPos.Tab(), nDocMaxTab)) maEndPos.SetTab(nDocMaxTab); while (maEndPos.Tab() > 0 && !mrDoc.maTabs[maEndPos.Tab()]) maEndPos.IncTab(-1); // Only the tables in use if (maStartPos.Tab() > maEndPos.Tab()) maStartPos.SetTab(maEndPos.Tab()); if (!mrDoc.maTabs[maStartPos.Tab()]) { assert(!"Table not found"); maStartPos = ScAddress(mrDoc.MaxCol()+1, mrDoc.MaxRow()+1, MAXTAB+1); // -> Abort on GetFirst. } else { maStartPos.SetCol(mrDoc.maTabs[maStartPos.Tab()]->ClampToAllocatedColumns(maStartPos.Col())); } maCurPos = maStartPos; } bool ScCellIterator::getCurrent() { const ScColumn* pCol = getColumn(); while (true) { bool bNextColumn = maCurColPos.first == pCol->maCells.end(); if (!bNextColumn) { if (maCurPos.Row() > maEndPos.Row()) bNextColumn = true; } if (bNextColumn) { // Move to the next column. maCurPos.SetRow(maStartPos.Row()); do { maCurPos.IncCol(); if (maCurPos.Col() >= mrDoc.GetAllocatedColumnsCount(maCurPos.Tab()) || maCurPos.Col() > maEndPos.Col()) { maCurPos.SetCol(maStartPos.Col()); maCurPos.IncTab(); if (maCurPos.Tab() > maEndPos.Tab()) { maCurCell.clear(); return false; // Over and out } } pCol = getColumn(); } while (pCol->IsEmptyData()); maCurColPos = pCol->maCells.position(maCurPos.Row()); } if (maCurColPos.first->type == sc::element_type_empty) { incBlock(); continue; } SCROW nLastRow; // Skip all filtered or hidden rows, depending on mSubTotalFlags if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) && pCol->GetDoc().RowFiltered(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) || ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) && pCol->GetDoc().RowHidden(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) ) { setPos(nLastRow+1); continue; } if (maCurColPos.first->type == sc::element_type_formula) { if ( mnSubTotalFlags != SubtotalFlags::NONE ) { ScFormulaCell* pCell = sc::formula_block::at(*maCurColPos.first->data, maCurColPos.second); // Skip formula cells with Subtotal formulae or errors, depending on mnSubTotalFlags if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && pCell->IsSubTotal() ) || ( ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) && pCell->GetErrCode() != FormulaError::NONE ) ) { incPos(); continue; } } } maCurCell = sc::toRefCell(maCurColPos.first, maCurColPos.second); return true; } return false; } OUString ScCellIterator::getString() const { return maCurCell.getString(&mrDoc); } ScCellValue ScCellIterator::getCellValue() const { ScCellValue aRet; aRet.meType = maCurCell.meType; switch (maCurCell.meType) { case CELLTYPE_STRING: aRet.mpString = new svl::SharedString(*maCurCell.mpString); break; case CELLTYPE_EDIT: aRet.mpEditText = maCurCell.mpEditText->Clone().release(); break; case CELLTYPE_VALUE: aRet.mfValue = maCurCell.mfValue; break; case CELLTYPE_FORMULA: aRet.mpFormula = maCurCell.mpFormula->Clone(); break; default: ; } return aRet; } bool ScCellIterator::hasString() const { return maCurCell.hasString(); } bool ScCellIterator::isEmpty() const { return maCurCell.isEmpty(); } bool ScCellIterator::equalsWithoutFormat( const ScAddress& rPos ) const { ScRefCellValue aOther(mrDoc, rPos); return maCurCell.equalsWithoutFormat(aOther); } bool ScCellIterator::first() { if (!ValidTab(maCurPos.Tab())) return false; maCurPos = maStartPos; const ScColumn* pCol = getColumn(); maCurColPos = pCol->maCells.position(maCurPos.Row()); return getCurrent(); } bool ScCellIterator::next() { incPos(); return getCurrent(); } ScQueryCellIterator::ScQueryCellIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod ) : maParam(rParam), rDoc( rDocument ), mrContext( rContext ), nTab( nTable), nStopOnMismatch( nStopOnMismatchDisabled ), nTestEqualCondition( nTestEqualConditionDisabled ), bAdvanceQuery( false ), bIgnoreMismatchOnLeadingStrings( false ) { nCol = maParam.nCol1; nRow = maParam.nRow1; SCSIZE i; if (!bMod) // Or else it's already inserted return; SCSIZE nCount = maParam.GetEntryCount(); for (i = 0; (i < nCount) && (maParam.GetEntry(i).bDoQuery); ++i) { ScQueryEntry& rEntry = maParam.GetEntry(i); ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); sal_uInt32 nIndex = 0; bool bNumber = mrContext.GetFormatTable()->IsNumberFormat( rItem.maString.getString(), nIndex, rItem.mfVal); rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; } } void ScQueryCellIterator::InitPos() { nRow = maParam.nRow1; if (maParam.bHasHeader && maParam.bByRow) ++nRow; const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol); maCurPos = rCol.maCells.position(nRow); } void ScQueryCellIterator::IncPos() { if (maCurPos.second + 1 < maCurPos.first->size) { // Move within the same block. ++maCurPos.second; ++nRow; } else // Move to the next block. IncBlock(); } void ScQueryCellIterator::IncBlock() { ++maCurPos.first; maCurPos.second = 0; nRow = maCurPos.first->position; } bool ScQueryCellIterator::GetThis() { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); const ScQueryEntry& rEntry = maParam.GetEntry(0); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); SCCOLROW nFirstQueryField = rEntry.nField; bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && rItem.meType != ScQueryEntry::ByString; bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString && ((maParam.bByRow && nRow == maParam.nRow1) || (!maParam.bByRow && nCol == maParam.nCol1)); ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; while (true) { bool bNextColumn = maCurPos.first == pCol->maCells.end(); if (!bNextColumn) { if (nRow > maParam.nRow2) bNextColumn = true; } if (bNextColumn) { do { ++nCol; if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount()) return false; // Over and out if ( bAdvanceQuery ) { AdvanceQueryParamEntryField(); nFirstQueryField = rEntry.nField; } pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; } while (!rItem.mbMatchEmpty && pCol->IsEmptyData()); InitPos(); bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString && maParam.bByRow; } if (maCurPos.first->type == sc::element_type_empty) { if (rItem.mbMatchEmpty && rEntry.GetQueryItems().size() == 1) { // This shortcut, instead of determining if any SC_OR query // exists or this query is SC_AND'ed (which wouldn't make // sense, but..) and evaluating them in ValidQuery(), is // possible only because the interpreter is the only caller // that sets mbMatchEmpty and there is only one item in those // cases. // XXX this would have to be reworked if other filters used it // in different manners and evaluation would have to be done in // ValidQuery(). return true; } else { IncBlock(); continue; } } ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second); if (bAllStringIgnore && aCell.hasString()) IncPos(); else { bool bTestEqualCondition = false; if ( rDoc.maTabs[nTab]->ValidQuery( nRow, maParam, (nCol == static_cast(nFirstQueryField) ? &aCell : nullptr), (nTestEqualCondition ? &bTestEqualCondition : nullptr), &mrContext) ) { if ( nTestEqualCondition && bTestEqualCondition ) nTestEqualCondition |= nTestEqualConditionMatched; return !aCell.isEmpty(); // Found it! } else if ( nStopOnMismatch ) { // Yes, even a mismatch may have a fulfilled equal // condition if regular expressions were involved and // SC_LESS_EQUAL or SC_GREATER_EQUAL were queried. if ( nTestEqualCondition && bTestEqualCondition ) { nTestEqualCondition |= nTestEqualConditionMatched; nStopOnMismatch |= nStopOnMismatchOccurred; return false; } bool bStop; if (bFirstStringIgnore) { if (aCell.hasString()) { IncPos(); bStop = false; } else bStop = true; } else bStop = true; if (bStop) { nStopOnMismatch |= nStopOnMismatchOccurred; return false; } } else IncPos(); } bFirstStringIgnore = false; } } bool ScQueryCellIterator::GetFirst() { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); nCol = maParam.nCol1; InitPos(); return GetThis(); } bool ScQueryCellIterator::GetNext() { IncPos(); if ( nStopOnMismatch ) nStopOnMismatch = nStopOnMismatchEnabled; if ( nTestEqualCondition ) nTestEqualCondition = nTestEqualConditionEnabled; return GetThis(); } void ScQueryCellIterator::AdvanceQueryParamEntryField() { SCSIZE nEntries = maParam.GetEntryCount(); for ( SCSIZE j = 0; j < nEntries; j++ ) { ScQueryEntry& rEntry = maParam.GetEntry( j ); if ( rEntry.bDoQuery ) { if ( rEntry.nField < rDoc.MaxCol() ) rEntry.nField++; else { assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL"); } } else break; // for } } bool ScQueryCellIterator::FindEqualOrSortedLastInRange( SCCOL& nFoundCol, SCROW& nFoundRow ) { // Set and automatically reset mpParam->mbRangeLookup when returning. We // could use comphelper::FlagRestorationGuard, but really, that one is // overengineered for this simple purpose here. struct BoolResetter { bool& mr; bool mb; BoolResetter( bool& r, bool b ) : mr(r), mb(r) { r = b; } ~BoolResetter() { mr = mb; } } aRangeLookupResetter( maParam.mbRangeLookup, true); nFoundCol = rDoc.MaxCol()+1; nFoundRow = rDoc.MaxRow()+1; SetStopOnMismatch( true ); // assume sorted keys SetTestEqualCondition( true ); bIgnoreMismatchOnLeadingStrings = true; bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal && maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString; bool bBinary = maParam.bByRow && (bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) && (maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL); bool bFound = false; if (bBinary) { if (BinarySearch()) { // BinarySearch() already positions correctly and only needs real // query comparisons afterwards, skip the verification check below. maParam.mbRangeLookup = false; bFound = GetThis(); } } else { bFound = GetFirst(); } if (bFound) { // First equal entry or last smaller than (greater than) entry. PositionType aPosSave; bool bNext = false; do { nFoundCol = GetCol(); nFoundRow = GetRow(); aPosSave = maCurPos; if (IsEqualConditionFulfilled()) break; bNext = GetNext(); } while (bNext); // There may be no pNext but equal condition fulfilled if regular // expressions are involved. Keep the found entry and proceed. if (!bNext && !IsEqualConditionFulfilled()) { // Step back to last in range and adjust position markers for // GetNumberFormat() or similar. SCCOL nColDiff = nCol - nFoundCol; nCol = nFoundCol; nRow = nFoundRow; maCurPos = aPosSave; if (maParam.mbRangeLookup) { // Verify that the found entry does not only fulfill the range // lookup but also the real query, i.e. not numeric was found // if query is ByString and vice versa. maParam.mbRangeLookup = false; // Step back the last field advance if GetNext() did one. if (bAdvanceQuery && nColDiff) { SCSIZE nEntries = maParam.GetEntryCount(); for (SCSIZE j=0; j < nEntries; ++j) { ScQueryEntry& rEntry = maParam.GetEntry( j ); if (rEntry.bDoQuery) { if (rEntry.nField - nColDiff >= 0) rEntry.nField -= nColDiff; else { assert(!"FindEqualOrSortedLastInRange: rEntry.nField -= nColDiff < 0"); } } else break; // for } } // Check it. if (!GetThis()) { nFoundCol = rDoc.MaxCol()+1; nFoundRow = rDoc.MaxRow()+1; } } } } if ( IsEqualConditionFulfilled() ) { // Position on last equal entry. SCSIZE nEntries = maParam.GetEntryCount(); for ( SCSIZE j = 0; j < nEntries; j++ ) { ScQueryEntry& rEntry = maParam.GetEntry( j ); if ( rEntry.bDoQuery ) { switch ( rEntry.eOp ) { case SC_LESS_EQUAL : case SC_GREATER_EQUAL : rEntry.eOp = SC_EQUAL; break; default: { // added to avoid warnings } } } else break; // for } PositionType aPosSave; bIgnoreMismatchOnLeadingStrings = false; SetTestEqualCondition( false ); do { nFoundCol = GetCol(); nFoundRow = GetRow(); aPosSave = maCurPos; } while (GetNext()); // Step back conditions are the same as above nCol = nFoundCol; nRow = nFoundRow; maCurPos = aPosSave; return true; } if ( (maParam.eSearchType != utl::SearchParam::SearchType::Normal) && StoppedOnMismatch() ) { // Assume found entry to be the last value less than respectively // greater than the query. But keep on searching for an equal match. SCSIZE nEntries = maParam.GetEntryCount(); for ( SCSIZE j = 0; j < nEntries; j++ ) { ScQueryEntry& rEntry = maParam.GetEntry( j ); if ( rEntry.bDoQuery ) { switch ( rEntry.eOp ) { case SC_LESS_EQUAL : case SC_GREATER_EQUAL : rEntry.eOp = SC_EQUAL; break; default: { // added to avoid warnings } } } else break; // for } SetStopOnMismatch( false ); SetTestEqualCondition( false ); if (GetNext()) { // Last of a consecutive area, avoid searching the entire parameter // range as it is a real performance bottleneck in case of regular // expressions. PositionType aPosSave; do { nFoundCol = GetCol(); nFoundRow = GetRow(); aPosSave = maCurPos; SetStopOnMismatch( true ); } while (GetNext()); nCol = nFoundCol; nRow = nFoundRow; maCurPos = aPosSave; } } return (nFoundCol <= rDoc.MaxCol()) && (nFoundRow <= rDoc.MaxRow()); } ScCountIfCellIterator::ScCountIfCellIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam ) : maParam(rParam), rDoc( rDocument ), mrContext( rContext ), nTab( nTable) { maParam.nCol1 = rDoc.maTabs[nTable]->ClampToAllocatedColumns(maParam.nCol1); maParam.nCol2 = rDoc.maTabs[nTable]->ClampToAllocatedColumns(maParam.nCol2); nCol = maParam.nCol1; nRow = maParam.nRow1; } void ScCountIfCellIterator::InitPos() { nRow = maParam.nRow1; if (maParam.bHasHeader && maParam.bByRow) ++nRow; ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; maCurPos = pCol->maCells.position(nRow); } void ScCountIfCellIterator::IncPos() { if (maCurPos.second + 1 < maCurPos.first->size) { // Move within the same block. ++maCurPos.second; ++nRow; } else // Move to the next block. IncBlock(); } void ScCountIfCellIterator::IncBlock() { ++maCurPos.first; maCurPos.second = 0; nRow = maCurPos.first->position; } int ScCountIfCellIterator::GetCount() { assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT"); nCol = maParam.nCol1; InitPos(); const ScQueryEntry& rEntry = maParam.GetEntry(0); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1; int count = 0; ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; while (true) { bool bNextColumn = maCurPos.first == pCol->maCells.end(); if (!bNextColumn) { if (nRow > maParam.nRow2) bNextColumn = true; } if (bNextColumn) { do { ++nCol; if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount()) return count; // Over and out AdvanceQueryParamEntryField(); pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; } while (!rItem.mbMatchEmpty && pCol->IsEmptyData()); InitPos(); } if (maCurPos.first->type == sc::element_type_empty) { if (rItem.mbMatchEmpty && bSingleQueryItem) { // This shortcut, instead of determining if any SC_OR query // exists or this query is SC_AND'ed (which wouldn't make // sense, but..) and evaluating them in ValidQuery(), is // possible only because the interpreter is the only caller // that sets mbMatchEmpty and there is only one item in those // cases. // XXX this would have to be reworked if other filters used it // in different manners and evaluation would have to be done in // ValidQuery(). count++; IncPos(); continue; } else { IncBlock(); continue; } } ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second); if ( rDoc.maTabs[nTab]->ValidQuery( nRow, maParam, (nCol == static_cast(rEntry.nField) ? &aCell : nullptr), nullptr, &mrContext) ) { if (aCell.isEmpty()) return count; count++; IncPos(); continue; } else IncPos(); } return count; } void ScCountIfCellIterator::AdvanceQueryParamEntryField() { SCSIZE nEntries = maParam.GetEntryCount(); for ( SCSIZE j = 0; j < nEntries; j++ ) { ScQueryEntry& rEntry = maParam.GetEntry( j ); if ( rEntry.bDoQuery ) { if ( rEntry.nField < rDoc.MaxCol() ) rEntry.nField++; else { OSL_FAIL( "AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL" ); } } else break; // for } } namespace { /** * This class sequentially indexes non-empty cells in order, from the top of * the block where the start row position is, to the bottom of the block * where the end row position is. It skips all empty blocks that may be * present in between. * * The index value is an offset from the first element of the first block * disregarding all empty cell blocks. */ class NonEmptyCellIndexer { typedef std::map BlockMapType; BlockMapType maBlockMap; const sc::CellStoreType& mrCells; size_t mnLowIndex; size_t mnHighIndex; bool mbValid; public: typedef std::pair CellType; /** * @param rCells cell storage container * @param nStartRow logical start row position * @param nEndRow logical end row position, inclusive. * @param bSkipTopStrBlock when true, skip all leading string cells. */ NonEmptyCellIndexer( const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow, bool bSkipTopStrBlock ) : mrCells(rCells), mnLowIndex(0), mnHighIndex(0), mbValid(true) { if (nEndRow < nStartRow) { mbValid = false; return; } // Find the low position. sc::CellStoreType::const_position_type aLoPos = mrCells.position(nStartRow); if (aLoPos.first->type == sc::element_type_empty) incBlock(aLoPos); if (aLoPos.first == rCells.end()) { mbValid = false; return; } if (bSkipTopStrBlock) { // Skip all leading string or empty blocks. while (aLoPos.first->type == sc::element_type_string || aLoPos.first->type == sc::element_type_edittext || aLoPos.first->type == sc::element_type_empty) { incBlock(aLoPos); if (aLoPos.first == rCells.end()) { mbValid = false; return; } } } SCROW nFirstRow = aLoPos.first->position; SCROW nLastRow = aLoPos.first->position + aLoPos.first->size - 1; if (nFirstRow > nEndRow) { // Both start and end row positions are within the leading skipped // blocks. mbValid = false; return; } // Calculate the index of the low position. if (nFirstRow < nStartRow) mnLowIndex = nStartRow - nFirstRow; else { // Start row is within the skipped block(s). Set it to the first // element of the low block. mnLowIndex = 0; } if (nEndRow < nLastRow) { assert(nEndRow >= nFirstRow); mnHighIndex = nEndRow - nFirstRow; maBlockMap.emplace(aLoPos.first->size, aLoPos.first); return; } // Find the high position. sc::CellStoreType::const_position_type aHiPos = mrCells.position(aLoPos.first, nEndRow); if (aHiPos.first->type == sc::element_type_empty) { // Move to the last position of the previous block. decBlock(aHiPos); // Check the row position of the end of the previous block, and make sure it's valid. SCROW nBlockEndRow = aHiPos.first->position + aHiPos.first->size - 1; if (nBlockEndRow < nStartRow) { mbValid = false; return; } } // Tag the start and end blocks, and all blocks in between in order // but skip all empty blocks. size_t nPos = 0; sc::CellStoreType::const_iterator itBlk = aLoPos.first; while (itBlk != aHiPos.first) { if (itBlk->type == sc::element_type_empty) { ++itBlk; continue; } nPos += itBlk->size; maBlockMap.emplace(nPos, itBlk); ++itBlk; if (itBlk->type == sc::element_type_empty) ++itBlk; assert(itBlk != mrCells.end()); } assert(itBlk == aHiPos.first); nPos += itBlk->size; maBlockMap.emplace(nPos, itBlk); // Calculate the high index. BlockMapType::const_reverse_iterator ri = maBlockMap.rbegin(); mnHighIndex = ri->first; mnHighIndex -= ri->second->size; mnHighIndex += aHiPos.second; } sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const { assert(mbValid); assert(mnLowIndex <= nIndex); assert(nIndex <= mnHighIndex); sc::CellStoreType::const_position_type aRet(mrCells.end(), 0); BlockMapType::const_iterator it = maBlockMap.upper_bound(nIndex); if (it == maBlockMap.end()) return aRet; sc::CellStoreType::const_iterator itBlk = it->second; size_t nBlkIndex = it->first - itBlk->size; // index of the first element of the block. assert(nBlkIndex <= nIndex); assert(nIndex < it->first); size_t nOffset = nIndex - nBlkIndex; aRet.first = itBlk; aRet.second = nOffset; return aRet; } CellType getCell( size_t nIndex ) const { std::pair aRet; aRet.second = -1; sc::CellStoreType::const_position_type aPos = getPosition(nIndex); if (aPos.first == mrCells.end()) return aRet; aRet.first = sc::toRefCell(aPos.first, aPos.second); aRet.second = aPos.first->position + aPos.second; return aRet; } size_t getLowIndex() const { return mnLowIndex; } size_t getHighIndex() const { return mnHighIndex; } bool isValid() const { return mbValid; } }; } bool ScQueryCellIterator::BinarySearch() { // TODO: This will be extremely slow with mdds::multi_type_vector. assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); nCol = maParam.nCol1; if (nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount()) return false; ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol]; if (pCol->IsEmptyData()) return false; CollatorWrapper* pCollator = (maParam.bCaseSens ? ScGlobal::GetCaseCollator() : ScGlobal::GetCollator()); SvNumberFormatter& rFormatter = *(mrContext.GetFormatTable()); const ScQueryEntry& rEntry = maParam.GetEntry(0); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); bool bLessEqual = rEntry.eOp == SC_LESS_EQUAL; bool bByString = rItem.meType == ScQueryEntry::ByString; bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString; bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && !maParam.bHasHeader && bByString; nRow = maParam.nRow1; if (maParam.bHasHeader) ++nRow; ScRefCellValue aCell; if (bFirstStringIgnore) { sc::CellStoreType::const_position_type aPos = pCol->maCells.position(nRow); if (aPos.first->type == sc::element_type_string || aPos.first->type == sc::element_type_edittext) { aCell = sc::toRefCell(aPos.first, aPos.second); sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, nRow); OUString aCellStr; ScCellFormat::GetInputString(aCell, nFormat, aCellStr, rFormatter, rDoc); sal_Int32 nTmp = pCollator->compareString(aCellStr, rEntry.GetQueryItem().maString.getString()); if ((rEntry.eOp == SC_LESS_EQUAL && nTmp > 0) || (rEntry.eOp == SC_GREATER_EQUAL && nTmp < 0) || (rEntry.eOp == SC_EQUAL && nTmp != 0)) ++nRow; } } NonEmptyCellIndexer aIndexer(pCol->maCells, nRow, maParam.nRow2, bAllStringIgnore); if (!aIndexer.isValid()) return false; size_t nLo = aIndexer.getLowIndex(); size_t nHi = aIndexer.getHighIndex(); NonEmptyCellIndexer::CellType aCellData; // Bookkeeping values for breaking up the binary search in case the data // range isn't strictly sorted. size_t nLastInRange = nLo; size_t nFirstLastInRange = nLastInRange; double fLastInRangeValue = bLessEqual ? -(::std::numeric_limits::max()) : ::std::numeric_limits::max(); OUString aLastInRangeString; if (!bLessEqual) aLastInRangeString = OUString(u'\xFFFF'); aCellData = aIndexer.getCell(nLastInRange); aCell = aCellData.first; if (aCell.hasString()) { sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second); OUString aStr; ScCellFormat::GetInputString(aCell, nFormat, aStr, rFormatter, rDoc); aLastInRangeString = aStr; } else { switch (aCell.meType) { case CELLTYPE_VALUE : fLastInRangeValue = aCell.mfValue; break; case CELLTYPE_FORMULA : fLastInRangeValue = aCell.mpFormula->GetValue(); break; default: { // added to avoid warnings } } } sal_Int32 nRes = 0; bool bFound = false; bool bDone = false; while (nLo <= nHi && !bDone) { size_t nMid = (nLo+nHi)/2; size_t i = nMid; aCellData = aIndexer.getCell(i); aCell = aCellData.first; bool bStr = aCell.hasString(); nRes = 0; // compares are contentquery:1 // Cell value comparison similar to ScTable::ValidQuery() if (!bStr && !bByString) { double nCellVal; switch (aCell.meType) { case CELLTYPE_VALUE : case CELLTYPE_FORMULA : nCellVal = aCell.getValue(); break; default: nCellVal = 0.0; } if ((nCellVal < rItem.mfVal) && !::rtl::math::approxEqual( nCellVal, rItem.mfVal)) { nRes = -1; if (bLessEqual) { if (fLastInRangeValue < nCellVal) { fLastInRangeValue = nCellVal; nLastInRange = i; } else if (fLastInRangeValue > nCellVal) { // not strictly sorted, continue with GetThis() nLastInRange = nFirstLastInRange; bDone = true; } } } else if ((nCellVal > rItem.mfVal) && !::rtl::math::approxEqual( nCellVal, rItem.mfVal)) { nRes = 1; if (!bLessEqual) { if (fLastInRangeValue > nCellVal) { fLastInRangeValue = nCellVal; nLastInRange = i; } else if (fLastInRangeValue < nCellVal) { // not strictly sorted, continue with GetThis() nLastInRange = nFirstLastInRange; bDone = true; } } } } else if (bStr && bByString) { OUString aCellStr; sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second); ScCellFormat::GetInputString(aCell, nFormat, aCellStr, rFormatter, rDoc); nRes = pCollator->compareString(aCellStr, rEntry.GetQueryItem().maString.getString()); if (nRes < 0 && bLessEqual) { sal_Int32 nTmp = pCollator->compareString( aLastInRangeString, aCellStr); if (nTmp < 0) { aLastInRangeString = aCellStr; nLastInRange = i; } else if (nTmp > 0) { // not strictly sorted, continue with GetThis() nLastInRange = nFirstLastInRange; bDone = true; } } else if (nRes > 0 && !bLessEqual) { sal_Int32 nTmp = pCollator->compareString( aLastInRangeString, aCellStr); if (nTmp > 0) { aLastInRangeString = aCellStr; nLastInRange = i; } else if (nTmp < 0) { // not strictly sorted, continue with GetThis() nLastInRange = nFirstLastInRange; bDone = true; } } } else if (!bStr && bByString) { nRes = -1; // numeric < string if (bLessEqual) nLastInRange = i; } else // if (bStr && !bByString) { nRes = 1; // string > numeric if (!bLessEqual) nLastInRange = i; } if (nRes < 0) { if (bLessEqual) nLo = nMid + 1; else // assumed to be SC_GREATER_EQUAL { if (nMid > 0) nHi = nMid - 1; else bDone = true; } } else if (nRes > 0) { if (bLessEqual) { if (nMid > 0) nHi = nMid - 1; else bDone = true; } else // assumed to be SC_GREATER_EQUAL nLo = nMid + 1; } else { nLo = i; bDone = bFound = true; } } if (!bFound) { // If all hits didn't result in a moving limit there's something // strange, e.g. data range not properly sorted, or only identical // values encountered, which doesn't mean there aren't any others in // between... leave it to GetThis(). The condition for this would be // if (nLastInRange == nFirstLastInRange) nLo = nFirstLastInRange; // Else, in case no exact match was found, we step back for a // subsequent GetThis() to find the last in range. Effectively this is // --nLo with nLastInRange == nLo-1. Both conditions combined yield: nLo = nLastInRange; } aCellData = aIndexer.getCell(nLo); if (nLo <= nHi && aCellData.second <= maParam.nRow2) { nRow = aCellData.second; maCurPos = aIndexer.getPosition(nLo); return true; } else { nRow = maParam.nRow2 + 1; // Set current position to the last possible row. maCurPos.first = pCol->maCells.end(); --maCurPos.first; maCurPos.second = maCurPos.first->size - 1; return false; } } ScHorizontalCellIterator::ScHorizontalCellIterator(ScDocument& rDocument, SCTAB nTable, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) : rDoc( rDocument ), mnTab( nTable ), nStartCol( nCol1 ), nEndCol( nCol2 ), nStartRow( nRow1 ), nEndRow( nRow2 ), mnCol( nCol1 ), mnRow( nRow1 ), mbMore( false ) { assert(mnTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); nEndCol = rDoc.maTabs[mnTab]->ClampToAllocatedColumns(nEndCol); if (nEndCol < nStartCol) // E.g., somewhere completely outside allocated area nEndCol = nStartCol - 1; // Empty maColPositions.reserve( nEndCol-nStartCol+1 ); SetTab( mnTab ); } ScHorizontalCellIterator::~ScHorizontalCellIterator() { } void ScHorizontalCellIterator::SetTab( SCTAB nTabP ) { mbMore = false; mnTab = nTabP; mnRow = nStartRow; mnCol = nStartCol; maColPositions.resize(0); // Set the start position in each column. for (SCCOL i = nStartCol; i <= nEndCol; ++i) { ScColumn* pCol = &rDoc.maTabs[mnTab]->aCol[i]; ColParam aParam; aParam.maPos = pCol->maCells.position(nStartRow).first; aParam.maEnd = pCol->maCells.end(); aParam.mnCol = i; // find first non-empty element. while (aParam.maPos != aParam.maEnd) { if (aParam.maPos->type == sc::element_type_empty) ++aParam.maPos; else { maColPositions.push_back( aParam ); break; } } } if (maColPositions.empty()) return; maColPos = maColPositions.begin(); mbMore = true; SkipInvalid(); } ScRefCellValue* ScHorizontalCellIterator::GetNext( SCCOL& rCol, SCROW& rRow ) { if (!mbMore) { debugiter("no more !\n"); return nullptr; } // Return the current non-empty cell, and move the cursor to the next one. ColParam& r = *maColPos; rCol = mnCol = r.mnCol; rRow = mnRow; debugiter("return col %d row %d\n", (int)rCol, (int)rRow); size_t nOffset = static_cast(mnRow) - r.maPos->position; maCurCell = sc::toRefCell(r.maPos, nOffset); Advance(); debugiter("advance to: col %d row %d\n", (int)maColPos->mnCol, (int)mnRow); return &maCurCell; } bool ScHorizontalCellIterator::GetPos( SCCOL& rCol, SCROW& rRow ) { rCol = mnCol; rRow = mnRow; return mbMore; } // Skip any invalid / empty cells across the current row, // we only advance the cursor if the current entry is invalid. // if we return true we have a valid cursor (or hit the end) bool ScHorizontalCellIterator::SkipInvalidInRow() { assert (mbMore); assert (maColPos != maColPositions.end()); // Find the next non-empty cell in the current row. while( maColPos != maColPositions.end() ) { ColParam& r = *maColPos; assert (r.maPos != r.maEnd); size_t nRow = static_cast(mnRow); if (nRow >= r.maPos->position) { if (nRow < r.maPos->position + r.maPos->size) { mnCol = maColPos->mnCol; debugiter("found valid cell at column %d, row %d\n", (int)mnCol, (int)mnRow); assert(r.maPos->type != sc::element_type_empty); return true; } else { bool bMoreBlocksInColumn = false; // This block is behind the current row position. Advance the block. for (++r.maPos; r.maPos != r.maEnd; ++r.maPos) { if (nRow < r.maPos->position + r.maPos->size && r.maPos->type != sc::element_type_empty) { bMoreBlocksInColumn = true; break; } } if (!bMoreBlocksInColumn) { debugiter("remove column %d at row %d\n", (int)maColPos->mnCol, (int)nRow); maColPos = maColPositions.erase(maColPos); if (maColPositions.empty()) { debugiter("no more columns\n"); mbMore = false; } } else { debugiter("advanced column %d to block starting row %d, retrying\n", (int)maColPos->mnCol, r.maPos->position); } } } else { debugiter("skip empty cells at column %d, row %d\n", (int)maColPos->mnCol, (int)nRow); ++maColPos; } } // No more columns with anything interesting in them ? if (maColPositions.empty()) { debugiter("no more live columns left - done\n"); mbMore = false; return true; } return false; } /// Find the next row that has some real content in one of its columns. SCROW ScHorizontalCellIterator::FindNextNonEmptyRow() { size_t nNextRow = rDoc.MaxRow()+1; for (const ColParam& r : maColPositions) { assert(o3tl::make_unsigned(mnRow) <= r.maPos->position); nNextRow = std::min (nNextRow, static_cast(r.maPos->position)); } SCROW nRow = std::max(static_cast(nNextRow), mnRow); debugiter("Next non empty row is %d\n", (int) nRow); return nRow; } void ScHorizontalCellIterator::Advance() { assert (mbMore); assert (maColPos != maColPositions.end()); ++maColPos; SkipInvalid(); } void ScHorizontalCellIterator::SkipInvalid() { if (maColPos == maColPositions.end() || !SkipInvalidInRow()) { mnRow++; if (mnRow > nEndRow) { mbMore = false; return; } maColPos = maColPositions.begin(); debugiter("moving to next row\n"); if (SkipInvalidInRow()) { debugiter("moved to valid cell in next row (or end)\n"); return; } mnRow = FindNextNonEmptyRow(); maColPos = maColPositions.begin(); bool bCorrect = SkipInvalidInRow(); assert (bCorrect); (void) bCorrect; } if (mnRow > nEndRow) mbMore = false; } ScHorizontalValueIterator::ScHorizontalValueIterator( ScDocument& rDocument, const ScRange& rRange ) : rDoc( rDocument ), nEndTab( rRange.aEnd.Tab() ), bCalcAsShown( rDocument.GetDocOptions().IsCalcAsShown() ) { SCCOL nStartCol = rRange.aStart.Col(); SCROW nStartRow = rRange.aStart.Row(); SCTAB nStartTab = rRange.aStart.Tab(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nEndRow = rRange.aEnd.Row(); PutInOrder( nStartCol, nEndCol); PutInOrder( nStartRow, nEndRow); PutInOrder( nStartTab, nEndTab ); if (!rDoc.ValidCol(nStartCol)) nStartCol = rDoc.MaxCol(); if (!rDoc.ValidCol(nEndCol)) nEndCol = rDoc.MaxCol(); if (!rDoc.ValidRow(nStartRow)) nStartRow = rDoc.MaxRow(); if (!rDoc.ValidRow(nEndRow)) nEndRow = rDoc.MaxRow(); if (!ValidTab(nStartTab)) nStartTab = MAXTAB; if (!ValidTab(nEndTab)) nEndTab = MAXTAB; nCurCol = nStartCol; nCurRow = nStartRow; nCurTab = nStartTab; nNumFormat = 0; // Will be initialized in GetNumberFormat() pAttrArray = nullptr; nAttrEndRow = 0; pCellIter.reset( new ScHorizontalCellIterator( rDoc, nStartTab, nStartCol, nStartRow, nEndCol, nEndRow ) ); } ScHorizontalValueIterator::~ScHorizontalValueIterator() { } bool ScHorizontalValueIterator::GetNext( double& rValue, FormulaError& rErr ) { bool bFound = false; while ( !bFound ) { ScRefCellValue* pCell = pCellIter->GetNext( nCurCol, nCurRow ); while ( !pCell ) { if ( nCurTab < nEndTab ) { pCellIter->SetTab( ++nCurTab); pCell = pCellIter->GetNext( nCurCol, nCurRow ); } else return false; } switch (pCell->meType) { case CELLTYPE_VALUE: { rValue = pCell->mfValue; rErr = FormulaError::NONE; if ( bCalcAsShown ) { ScColumn* pCol = &rDoc.maTabs[nCurTab]->aCol[nCurCol]; ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray, nAttrEndRow, pCol->pAttrArray.get(), nCurRow, rDoc ); rValue = rDoc.RoundValueAsShown( rValue, nNumFormat ); } bFound = true; } break; case CELLTYPE_FORMULA: { rErr = pCell->mpFormula->GetErrCode(); if (rErr != FormulaError::NONE || pCell->mpFormula->IsValue()) { rValue = pCell->mpFormula->GetValue(); bFound = true; } } break; case CELLTYPE_STRING : case CELLTYPE_EDIT : break; default: ; // nothing } } return bFound; } ScHorizontalAttrIterator::ScHorizontalAttrIterator( ScDocument& rDocument, SCTAB nTable, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) : rDoc( rDocument ), nTab( nTable ), nStartCol( nCol1 ), nStartRow( nRow1 ), nEndCol( nCol2 ), nEndRow( nRow2 ) { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); assert(rDoc.maTabs[nTab]); nEndCol = rDoc.maTabs[nTab]->ClampToAllocatedColumns(nEndCol); nRow = nStartRow; nCol = nStartCol; bRowEmpty = false; pIndices.reset( new SCSIZE[nEndCol-nStartCol+1] ); pNextEnd.reset( new SCROW[nEndCol-nStartCol+1] ); pHorizEnd.reset( new SCCOL[nEndCol-nStartCol+1] ); ppPatterns.reset( new const ScPatternAttr*[nEndCol-nStartCol+1] ); InitForNextRow(true); } ScHorizontalAttrIterator::~ScHorizontalAttrIterator() { } void ScHorizontalAttrIterator::InitForNextRow(bool bInitialization) { bool bEmpty = true; nMinNextEnd = rDoc.MaxRow(); SCCOL nThisHead = 0; for (SCCOL i=nStartCol; i<=nEndCol; i++) { SCCOL nPos = i - nStartCol; if ( bInitialization || pNextEnd[nPos] < nRow ) { const ScAttrArray* pArray = rDoc.maTabs[nTab]->aCol[i].pAttrArray.get(); assert(pArray); SCSIZE nIndex; if (bInitialization) { if ( pArray->Count() ) pArray->Search( nStartRow, nIndex ); else nIndex = 0; pIndices[nPos] = nIndex; pHorizEnd[nPos] = rDoc.MaxCol()+1; // only for assert() } else nIndex = ++pIndices[nPos]; if ( !nIndex && !pArray->Count() ) { pNextEnd[nPos] = rDoc.MaxRow(); assert( pNextEnd[nPos] >= nRow && "Sequence out of order" ); ppPatterns[nPos] = nullptr; } else if ( nIndex < pArray->Count() ) { const ScPatternAttr* pPattern = pArray->mvData[nIndex].pPattern; SCROW nThisEnd = pArray->mvData[nIndex].nEndRow; if ( IsDefaultItem( pPattern ) ) pPattern = nullptr; else bEmpty = false; // Found attributes pNextEnd[nPos] = nThisEnd; assert( pNextEnd[nPos] >= nRow && "Sequence out of order" ); ppPatterns[nPos] = pPattern; } else { assert(!"AttrArray does not range to MAXROW"); pNextEnd[nPos] = rDoc.MaxRow(); ppPatterns[nPos] = nullptr; } } else if ( ppPatterns[nPos] ) bEmpty = false; // Area not at the end yet if ( nMinNextEnd > pNextEnd[nPos] ) nMinNextEnd = pNextEnd[nPos]; // store positions of ScHorizontalAttrIterator elements (minimizing expensive ScPatternAttr comparisons) if (i > nStartCol && ppPatterns[nThisHead] != ppPatterns[nPos]) { pHorizEnd[nThisHead] = i - 1; nThisHead = nPos; // start position of the next horizontal group } } if (bEmpty) nRow = nMinNextEnd; // Skip until end of next section else pHorizEnd[nThisHead] = nEndCol; // set the end position of the last horizontal group, too bRowEmpty = bEmpty; } bool ScHorizontalAttrIterator::InitForNextAttr() { if ( !ppPatterns[nCol-nStartCol] ) // Skip default items { assert( pHorizEnd[nCol-nStartCol] < rDoc.MaxCol()+1 && "missing stored data" ); nCol = pHorizEnd[nCol-nStartCol] + 1; if ( nCol > nEndCol ) return false; } return true; } const ScPatternAttr* ScHorizontalAttrIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, SCROW& rRow ) { assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); for (;;) { if ( !bRowEmpty && nCol <= nEndCol && InitForNextAttr() ) { const ScPatternAttr* pPat = ppPatterns[nCol-nStartCol]; rRow = nRow; rCol1 = nCol; assert( pHorizEnd[nCol-nStartCol] < rDoc.MaxCol()+1 && "missing stored data" ); nCol = pHorizEnd[nCol-nStartCol]; rCol2 = nCol; ++nCol; // Count up for next call return pPat; // Found it! } // Next row ++nRow; if ( nRow > nEndRow ) // Already at the end? return nullptr; // Found nothing nCol = nStartCol; // Start at the left again if ( bRowEmpty || nRow > nMinNextEnd ) InitForNextRow(false); } } static bool IsGreater( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) { return ( nRow1 > nRow2 ) || ( nRow1 == nRow2 && nCol1 > nCol2 ); } ScUsedAreaIterator::ScUsedAreaIterator( ScDocument& rDocument, SCTAB nTable, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) : aCellIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 ) , aAttrIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 ) , nNextCol( nCol1 ) , nNextRow( nRow1 ) , nCellCol( 0 ) , nCellRow( 0 ) , nAttrCol1( 0 ) , nAttrCol2( 0 ) , nAttrRow( 0 ) , nFoundStartCol( 0 ) , nFoundEndCol( 0 ) , nFoundRow( 0 ) , pFoundPattern( nullptr ) { pCell = aCellIter.GetNext( nCellCol, nCellRow ); pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow ); } ScUsedAreaIterator::~ScUsedAreaIterator() { } bool ScUsedAreaIterator::GetNext() { // Forward iterators if ( pCell && IsGreater( nNextCol, nNextRow, nCellCol, nCellRow ) ) pCell = aCellIter.GetNext( nCellCol, nCellRow ); while (pCell && pCell->isEmpty()) pCell = aCellIter.GetNext( nCellCol, nCellRow ); if ( pPattern && IsGreater( nNextCol, nNextRow, nAttrCol2, nAttrRow ) ) pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow ); if ( pPattern && nAttrRow == nNextRow && nAttrCol1 < nNextCol ) nAttrCol1 = nNextCol; // Find next area bool bFound = true; bool bUseCell = false; if ( pCell && pPattern ) { if ( IsGreater( nCellCol, nCellRow, nAttrCol1, nAttrRow ) ) // Only attributes at the beginning? { maFoundCell.clear(); pFoundPattern = pPattern; nFoundRow = nAttrRow; nFoundStartCol = nAttrCol1; if ( nCellRow == nAttrRow && nCellCol <= nAttrCol2 ) // Area also contains cell? nFoundEndCol = nCellCol - 1; // Only until right before the cell else nFoundEndCol = nAttrCol2; // Everything } else { bUseCell = true; if ( nAttrRow == nCellRow && nAttrCol1 == nCellCol ) // Attributes on the cell? pFoundPattern = pPattern; else pFoundPattern = nullptr; } } else if ( pCell ) // Just a cell -> take over right away { pFoundPattern = nullptr; bUseCell = true; // Cell position } else if ( pPattern ) // Just attributes -> take over right away { maFoundCell.clear(); pFoundPattern = pPattern; nFoundRow = nAttrRow; nFoundStartCol = nAttrCol1; nFoundEndCol = nAttrCol2; } else // Nothing bFound = false; if ( bUseCell ) // Cell position { if (pCell) maFoundCell = *pCell; else maFoundCell.clear(); nFoundRow = nCellRow; nFoundStartCol = nFoundEndCol = nCellCol; } if (bFound) { nNextRow = nFoundRow; nNextCol = nFoundEndCol + 1; } return bFound; } ScDocAttrIterator::ScDocAttrIterator(ScDocument& rDocument, SCTAB nTable, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) : rDoc( rDocument ), nTab( nTable ), nEndCol( nCol2 ), nStartRow( nRow1 ), nEndRow( nRow2 ), nCol( nCol1 ) { if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] && nCol < rDoc.maTabs[nTab]->GetAllocatedColumnsCount()) { nEndCol = rDoc.maTabs[nTab]->ClampToAllocatedColumns(nEndCol); pColIter = rDoc.maTabs[nTab]->aCol[nCol].CreateAttrIterator( nStartRow, nEndRow ); } } ScDocAttrIterator::~ScDocAttrIterator() { } const ScPatternAttr* ScDocAttrIterator::GetNext( SCCOL& rCol, SCROW& rRow1, SCROW& rRow2 ) { while ( pColIter ) { const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 ); if ( pPattern ) { rCol = nCol; return pPattern; } ++nCol; if ( nCol <= nEndCol ) pColIter = rDoc.maTabs[nTab]->aCol[nCol].CreateAttrIterator( nStartRow, nEndRow ); else pColIter.reset(); } return nullptr; // Nothing anymore } ScDocRowHeightUpdater::TabRanges::TabRanges(SCTAB nTab, SCROW nMaxRow) : mnTab(nTab), maRanges(nMaxRow) { } ScDocRowHeightUpdater::ScDocRowHeightUpdater(ScDocument& rDoc, OutputDevice* pOutDev, double fPPTX, double fPPTY, const vector* pTabRangesArray) : mrDoc(rDoc), mpOutDev(pOutDev), mfPPTX(fPPTX), mfPPTY(fPPTY), mpTabRangesArray(pTabRangesArray) { } void ScDocRowHeightUpdater::update() { if (!mpTabRangesArray || mpTabRangesArray->empty()) { // No ranges defined. Update all rows in all tables. updateAll(); return; } sal_uLong nCellCount = 0; for (const auto& rTabRanges : *mpTabRangesArray) { const SCTAB nTab = rTabRanges.mnTab; if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab]) continue; ScFlatBoolRowSegments::RangeData aData; ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges); for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData)) { if (!aData.mbValue) continue; nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2); } } ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true); Fraction aZoom(1, 1); sal_uLong nProgressStart = 0; for (const auto& rTabRanges : *mpTabRangesArray) { const SCTAB nTab = rTabRanges.mnTab; if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab]) continue; sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev); ScFlatBoolRowSegments::RangeData aData; ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges); for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData)) { if (!aData.mbValue) continue; mrDoc.maTabs[nTab]->SetOptimalHeight( aCxt, aData.mnRow1, aData.mnRow2, true, &aProgress, nProgressStart); nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2); } } } void ScDocRowHeightUpdater::updateAll() { sal_uInt32 nCellCount = 0; for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab) { if (!ValidTab(nTab) || !mrDoc.maTabs[nTab]) continue; nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(); } ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true); Fraction aZoom(1, 1); sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev); sal_uLong nProgressStart = 0; for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab) { if (!ValidTab(nTab) || !mrDoc.maTabs[nTab]) continue; mrDoc.maTabs[nTab]->SetOptimalHeight(aCxt, 0, mrDoc.MaxRow(), true, &aProgress, nProgressStart); nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(); } } ScAttrRectIterator::ScAttrRectIterator(ScDocument& rDocument, SCTAB nTable, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) : rDoc( rDocument ), nTab( nTable ), nEndCol( nCol2 ), nStartRow( nRow1 ), nEndRow( nRow2 ), nIterStartCol( nCol1 ), nIterEndCol( nCol1 ) { if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] && nCol1 < rDoc.maTabs[nTab]->GetAllocatedColumnsCount()) { nEndCol = rDoc.maTabs[nTab]->ClampToAllocatedColumns(nEndCol); pColIter = rDoc.maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nStartRow, nEndRow ); while ( nIterEndCol < nEndCol && rDoc.maTabs[nTab]->aCol[nIterEndCol].IsAllAttrEqual( rDoc.maTabs[nTab]->aCol[nIterEndCol+1], nStartRow, nEndRow ) ) ++nIterEndCol; } else pColIter = nullptr; } ScAttrRectIterator::~ScAttrRectIterator() { } void ScAttrRectIterator::DataChanged() { if (pColIter) { SCROW nNextRow = pColIter->GetNextRow(); pColIter = rDoc.maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nNextRow, nEndRow ); } } const ScPatternAttr* ScAttrRectIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, SCROW& rRow1, SCROW& rRow2 ) { while ( pColIter ) { const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 ); if ( pPattern ) { rCol1 = nIterStartCol; rCol2 = nIterEndCol; return pPattern; } nIterStartCol = nIterEndCol+1; if ( nIterStartCol <= nEndCol ) { nIterEndCol = nIterStartCol; pColIter = rDoc.maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nStartRow, nEndRow ); while ( nIterEndCol < nEndCol && rDoc.maTabs[nTab]->aCol[nIterEndCol].IsAllAttrEqual( rDoc.maTabs[nTab]->aCol[nIterEndCol+1], nStartRow, nEndRow ) ) ++nIterEndCol; } else pColIter.reset(); } return nullptr; // Nothing anymore } ScRowBreakIterator::ScRowBreakIterator(set& rBreaks) : mrBreaks(rBreaks), maItr(rBreaks.begin()), maEnd(rBreaks.end()) { } SCROW ScRowBreakIterator::first() { maItr = mrBreaks.begin(); return maItr == maEnd ? NOT_FOUND : *maItr; } SCROW ScRowBreakIterator::next() { ++maItr; return maItr == maEnd ? NOT_FOUND : *maItr; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */