/* -*- 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 .
*/
bool ScQueryEvaluator::isPartialTextMatchOp(ScQueryOp eOp)
{ switch (eOp)
{ // these operators can only be used with textural comparisons. 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: returntrue; default:;
} returnfalse;
}
bool ScQueryEvaluator::isTextMatchOp(ScQueryOp eOp)
{ if (isPartialTextMatchOp(eOp)) returntrue;
switch (eOp)
{ // these operators can be used for either textural or value comparison. case SC_EQUAL: case SC_NOT_EQUAL: returntrue; default:;
} returnfalse;
}
bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
{ bool bMatchWholeCell = docMatchWholeCell; if (isPartialTextMatchOp(eOp)) // may have to do partial textural comparison.
bMatchWholeCell = false; return bMatchWholeCell;
}
sal_uInt32 ScQueryEvaluator::getNumFmt(SCCOL nCol, SCROW nRow)
{
sal_uInt32 nNumFmt
= (mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
: mrTab.GetNumberFormat(nCol, nRow)); if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) // Any General of any locale is irrelevant for rounding.
nNumFmt = 0; return nNumFmt;
}
std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& rCell, SCCOL nCol,
SCROW nRow, const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem)
{ bool bOk = false; bool bTestEqual = false; double nCellVal; double fQueryVal = rItem.mfVal; // Defer all number format detection to as late as possible as it's a // bottle neck, even if that complicates the code. Also do not // unnecessarily call ScDocument::RoundValueAsShown() for the same // reason.
sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
switch (rCell.getType())
{ case CELLTYPE_VALUE:
nCellVal = rCell.getDouble(); break; case CELLTYPE_FORMULA:
nCellVal = rCell.getFormula()->GetValue(); break; default:
nCellVal = 0.0;
} if (rItem.mbRoundForFilter && nCellVal != 0.0)
{
nNumFmt = getNumFmt(nCol, nRow); if (nNumFmt)
{ switch (rCell.getType())
{ case CELLTYPE_VALUE: case CELLTYPE_FORMULA:
nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, mpContext); break; default:
assert(!"can't be");
}
}
}
/* NOTE: lcl_PrepareQuery() prepares a filter query such that if a * date+time format was queried rEntry.bQueryByDate is not set. In * case other queries wanted to use this mechanism they should do * the same, in other words only if rEntry.nVal is an integer value * rEntry.bQueryByDate should be true and the time fraction be
* stripped here. */
if (rItem.meType == ScQueryEntry::ByDate)
{ if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
nNumFmt = getNumFmt(nCol, nRow); if (nNumFmt)
{ const SvNumberFormatter* pFormatter
= mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable(); const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt); if (pEntry)
{
SvNumFormatType nNumFmtType = pEntry->GetType(); /* NOTE: Omitting the check for absence of * css::util::NumberFormat::TIME would include also date+time formatted * values of the same day. That may be desired in some * cases, querying all time values of a day, but confusing * in other cases. A user can always setup a standard
* filter query for x >= date AND x < date+1 */ if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & SvNumFormatType::TIME))
{ // The format is of date type. Strip off the time // element.
nCellVal = ::rtl::math::approxFloor(nCellVal);
}
}
}
}
switch (rEntry.eOp)
{ case SC_EQUAL:
bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal); break; case SC_LESS:
bOk = (nCellVal < fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal); break; case SC_GREATER:
bOk = (nCellVal > fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal); break; case SC_LESS_EQUAL:
bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal); if (bOk && mpTestEqualCondition)
bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal); break; case SC_GREATER_EQUAL:
bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal); if (bOk && mpTestEqualCondition)
bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal); break; case SC_NOT_EQUAL:
bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal); break; default:
assert(false); break;
}
staticbool equalCellSharedString(const svl::SharedString& rValueSource, const svl::SharedString& rString, bool bCaseSens)
{ // Fast string equality check by comparing string identifiers. // This is the bFast path, all conditions should lead here on bFast == true. if (bCaseSens) return rValueSource.getData() == rString.getData(); return rValueSource.getDataIgnoreCase() == rString.getDataIgnoreCase();
}
bool ScQueryEvaluator::isFastCompareByString(const ScQueryEntry& rEntry) const
{ // If this is true, then there's a fast path in compareByString() which // can be selected using the template argument to get fast code // that will not check the same conditions every time. This makes a difference // in fast lookups that search for an exact value (case sensitive or not). constbool bRealWildOrRegExp = isRealWildOrRegExp(rEntry); constbool bTestWildOrRegExp = isTestWildOrRegExp(rEntry); // SC_EQUAL is part of isTextMatchOp(rEntry) return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
&& isMatchWholeCell(rEntry.eOp);
}
// The value is placed inside parameter rValueSource. // For the template argument see isFastCompareByString(). template <bool bFast>
std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem, const ScRefCellValue& rCell, SCROW nRow)
{ bool bOk = false; bool bTestEqual = false; bool bMatchWholeCell; if (bFast)
bMatchWholeCell = true; else
bMatchWholeCell = isMatchWholeCell(rEntry.eOp); constbool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry); constbool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
// from 614 on, nEnd is behind the found text bool bMatch = false; if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
{
nEnd = 0;
nStart = rValue.getLength();
bMatch
= rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
->SearchBackward(rValue, &nStart, &nEnd);
} else
{
bMatch
= rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
->SearchForward(rValue, &nStart, &nEnd);
} if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != rValue.getLength()))
bMatch = false; // RegExp must match entire cell string if (bRealWildOrRegExp)
{ switch (rEntry.eOp)
{ case SC_EQUAL: case SC_CONTAINS:
bOk = bMatch; break; case SC_NOT_EQUAL: case SC_DOES_NOT_CONTAIN:
bOk = !bMatch; break; case SC_BEGINS_WITH:
bOk = (bMatch && (nStart == 0)); break; case SC_DOES_NOT_BEGIN_WITH:
bOk = !(bMatch && (nStart == 0)); break; case SC_ENDS_WITH:
bOk = (bMatch && (nEnd == rValue.getLength())); break; case SC_DOES_NOT_END_WITH:
bOk = !(bMatch && (nEnd == rValue.getLength())); break; default:
assert(false); break;
}
} else
bTestEqual = bMatch;
} if (bFast || !bRealWildOrRegExp)
{ // Simple string matching i.e. no regexp match. if (bFast || isTextMatchOp(rEntry.eOp))
{ // Check this even with bFast. if (rItem.meType != ScQueryEntry::ByString && rItem.maString.isEmpty())
{ // #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup), // the query value is assigned directly, and the string is empty. In that case, // don't find any string (isEqual would find empty string results in formula cells).
bOk = false; if (rEntry.eOp == SC_NOT_EQUAL)
bOk = !bOk;
} else
{ if (bFast || bMatchWholeCell)
{
bOk = equalCellSharedString(rCell, nRow, rEntry.nField, mrParam.bCaseSens,
rItem.maString);
if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
bOk = !bOk;
} else
{
svl::SharedString rValueSource
= getCellSharedString(rCell, nRow, rEntry.nField);
// Where do we find a match (if at all)
sal_Int32 nStrPos;
if (!mbCaseSensitive)
{ // Common case for vlookup etc. const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase(); const rtl_uString* pCellStr = rValueSource.getDataIgnoreCase();
assert(pCellStr != nullptr); if (pQuer == nullptr)
pQuer = svl::SharedString::getEmptyString().getDataIgnoreCase();
// To be called only if both isQueryByValue() and isQueryByString() // returned false and range lookup is wanted! In range lookup comparison // numbers are less than strings. Nothing else is compared.
std::pair<bool, bool> ScQueryEvaluator::compareByRangeLookup(const ScRefCellValue& rCell, const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem)
{ bool bTestEqual = false;
std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, const ScRefCellValue& aCell, const ScQueryEntry& rEntry, size_t nEntryIndex)
{
std::pair<bool, bool> aRes(false, false); const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
{ if (rEntry.IsQueryByEmpty())
aRes.first = aCell.isEmpty(); else
{
assert(rEntry.IsQueryByNonEmpty());
aRes.first = !aCell.isEmpty();
} return aRes;
} if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
{ // If there are many items to query for (autofilter does this), then try to search // efficiently in those items. So first search all the items of the relevant type, // If that does not find anything, fall back to the generic code. double value = 0; bool valid = true; // For ScQueryEntry::ByValue check that the cell either is a value or is a formula // that has a value and is not an error (those are compared as strings). This // is basically simplified isQueryByValue(). if (aCell.getType() == CELLTYPE_VALUE)
value = aCell.getDouble(); elseif (aCell.getType() == CELLTYPE_FORMULA
&& aCell.getFormula()->GetErrCode() != FormulaError::NONE
&& aCell.getFormula()->IsValue())
{
value = aCell.getFormula()->GetValue();
} else
valid = false; if (valid)
{ if (rItems.size() >= 100)
{ // Sort, cache and binary search for the value in items. // Don't bother comparing approximately. if (mCachedSortedItemValues.size() <= nEntryIndex)
{
mCachedSortedItemValues.resize(nEntryIndex + 1); auto& values = mCachedSortedItemValues[nEntryIndex];
values.reserve(rItems.size()); for (constauto& rItem : rItems) if (rItem.meType == ScQueryEntry::ByValue)
values.push_back(rItem.mfVal);
std::sort(values.begin(), values.end());
} auto& values = mCachedSortedItemValues[nEntryIndex]; auto it = std::lower_bound(values.begin(), values.end(), value); if (it != values.end() && *it == value) return std::make_pair(true, true);
} else
{ for (constauto& rItem : rItems)
{ // For speed don't bother comparing approximately here, usually there either // will be an exact match or it wouldn't match anyway. if (rItem.meType == ScQueryEntry::ByValue && value == rItem.mfVal)
{ return std::make_pair(true, true);
}
}
}
}
} constbool bFastCompareByString = isFastCompareByString(rEntry); if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
{ // The same as above but for strings. Try to optimize the case when // it's a svl::SharedString comparison. That happens when SC_EQUAL is used // and simple matching is used, see compareByString()
svl::SharedString cellSharedString = getCellSharedString(aCell, nRow, rEntry.nField); // Allow also checking ScQueryEntry::ByValue if the cell is not numeric, // as in that case isQueryByNumeric() would be false and isQueryByString() would // be true because of SC_EQUAL making isTextMatchOp() true. bool compareByValue = !isQueryByValueForCell(aCell); // For ScQueryEntry::ByString check that the cell is represented by a shared string, // which means it's either a string cell or a formula error. This is not as // generous as isQueryByString() but it should be enough and better be safe. if (rItems.size() >= 100)
{ // Sort, cache and binary search for the string in items. // Since each SharedString is identified by pointer value, // sorting by pointer value is enough. if (mCachedSortedItemStrings.size() <= nEntryIndex)
{
mCachedSortedItemStrings.resize(nEntryIndex + 1); auto& values = mCachedSortedItemStrings[nEntryIndex];
values.reserve(rItems.size()); for (constauto& rItem : rItems)
{ if (rItem.meType == ScQueryEntry::ByString
|| (compareByValue && rItem.meType == ScQueryEntry::ByValue))
{
values.push_back(mrParam.bCaseSens ? rItem.maString.getData()
: rItem.maString.getDataIgnoreCase());
}
}
std::sort(values.begin(), values.end());
} auto& values = mCachedSortedItemStrings[nEntryIndex]; const rtl_uString* string = mrParam.bCaseSens ? cellSharedString.getData()
: cellSharedString.getDataIgnoreCase(); auto it = std::lower_bound(values.begin(), values.end(), string); if (it != values.end() && *it == string) return std::make_pair(true, true);
} else
{ for (constauto& rItem : rItems)
{ if ((rItem.meType == ScQueryEntry::ByString
|| (compareByValue && rItem.meType == ScQueryEntry::ByValue))
&& (mrParam.bCaseSens ? cellSharedString.getData() == rItem.maString.getData()
: cellSharedString.getDataIgnoreCase()
== rItem.maString.getDataIgnoreCase()))
{ return std::make_pair(true, true);
}
}
}
} // Generic handling. for (constauto& rItem : rItems)
{ if (rItem.meType == ScQueryEntry::ByTextColor)
{
std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
aRes.first |= aThisRes.first;
aRes.second |= aThisRes.second;
} elseif (rItem.meType == ScQueryEntry::ByBackgroundColor)
{
std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
aRes.first |= aThisRes.first;
aRes.second |= aThisRes.second;
} elseif (isQueryByValue(rEntry.eOp, rItem.meType, aCell))
{
std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
aRes.first |= aThisRes.first;
aRes.second |= aThisRes.second;
} elseif (isQueryByString(rEntry.eOp, rItem.meType, aCell))
{
std::pair<bool, bool> aThisRes; if (bFastCompareByString) // fast
aThisRes = compareByString<true>(rEntry, rItem, aCell, nRow); else
aThisRes = compareByString(rEntry, rItem, aCell, nRow);
aRes.first |= aThisRes.first;
aRes.second |= aThisRes.second;
} elseif (mrParam.mbRangeLookup)
{
std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
aRes.first |= aThisRes.first;
aRes.second |= aThisRes.second;
}
// Short-circuit the test at the end of the loop - if this is SC_AND // and the previous value is false, this value will not be needed. // Disable this if pbTestEqualCondition is present as that one may get set // even if the result is false (that also means pTest doesn't need to be // handled here). if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
&& !mpPasst[nPos])
{ continue;
}
SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
// We can only handle one single direct query passed as a known pCell, // subsequent queries have to obtain the cell.
ScRefCellValue aCell; if (pCell && it == itBeg)
aCell = *pCell; elseif (pBlockPos)
{ // hinted mdds access
aCell = const_cast<ScTable&>(mrTab).GetCellValue(
nCol, *pBlockPos->getBlockPosition(nCol), nRow);
} else
aCell = mrTab.GetCellValue(nCol, nRow);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.