summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/queryevaluator.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/queryevaluator.cxx')
-rw-r--r--sc/source/core/data/queryevaluator.cxx961
1 files changed, 961 insertions, 0 deletions
diff --git a/sc/source/core/data/queryevaluator.cxx b/sc/source/core/data/queryevaluator.cxx
new file mode 100644
index 000000000..0cf27a36e
--- /dev/null
+++ b/sc/source/core/data/queryevaluator.cxx
@@ -0,0 +1,961 @@
+/* -*- 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 <queryevaluator.hxx>
+
+#include <cellform.hxx>
+#include <cellvalue.hxx>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <queryparam.hxx>
+#include <scitems.hxx>
+#include <table.hxx>
+
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+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:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isTextMatchOp(ScQueryOp eOp)
+{
+ if (isPartialTextMatchOp(eOp))
+ return true;
+
+ switch (eOp)
+ {
+ // these operators can be used for either textural or value comparison.
+ case SC_EQUAL:
+ case SC_NOT_EQUAL:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
+{
+ bool bMatchWholeCell = docMatchWholeCell;
+ if (isPartialTextMatchOp(eOp))
+ // may have to do partial textural comparison.
+ bMatchWholeCell = false;
+ return bMatchWholeCell;
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(ScQueryOp eOp) const
+{
+ return isMatchWholeCellHelper(mbMatchWholeCell, eOp);
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(const ScDocument& rDoc, ScQueryOp eOp)
+{
+ return isMatchWholeCellHelper(rDoc.GetDocOptions().IsMatchWholeCell(), eOp);
+}
+
+void ScQueryEvaluator::setupTransliteratorIfNeeded()
+{
+ if (!mpTransliteration)
+ mpTransliteration = &ScGlobal::GetTransliteration(mrParam.bCaseSens);
+}
+
+void ScQueryEvaluator::setupCollatorIfNeeded()
+{
+ if (!mpCollator)
+ mpCollator = &ScGlobal::GetCollator(mrParam.bCaseSens);
+}
+
+ScQueryEvaluator::ScQueryEvaluator(ScDocument& rDoc, const ScTable& rTab,
+ const ScQueryParam& rParam, const ScInterpreterContext* pContext,
+ bool* pTestEqualCondition)
+ : mrDoc(rDoc)
+ , mrStrPool(rDoc.GetSharedStringPool())
+ , mrTab(rTab)
+ , mrParam(rParam)
+ , mpTestEqualCondition(pTestEqualCondition)
+ , mpTransliteration(nullptr)
+ , mpCollator(nullptr)
+ , mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell())
+ , mbCaseSensitive(rParam.bCaseSens)
+ , mpContext(pContext)
+ , mnEntryCount(mrParam.GetEntryCount())
+{
+ if (mnEntryCount <= nFixedBools)
+ {
+ mpPasst = &maBool[0];
+ mpTest = &maTest[0];
+ }
+ else
+ {
+ mpBoolDynamic.reset(new bool[mnEntryCount]);
+ mpTestDynamic.reset(new bool[mnEntryCount]);
+ mpPasst = mpBoolDynamic.get();
+ mpTest = mpTestDynamic.get();
+ }
+}
+
+bool ScQueryEvaluator::isRealWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return isTextMatchOp(rEntry.eOp);
+}
+
+bool ScQueryEvaluator::isTestWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (!mpTestEqualCondition)
+ return false;
+
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
+}
+
+bool ScQueryEvaluator::isQueryByValue(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (eType == ScQueryEntry::ByString || isPartialTextMatchOp(eOp))
+ return false;
+
+ return isQueryByValueForCell(rCell);
+}
+
+bool ScQueryEvaluator::isQueryByValueForCell(const ScRefCellValue& rCell)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return false;
+
+ return rCell.hasNumeric();
+}
+
+bool ScQueryEvaluator::isQueryByString(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (isTextMatchOp(eOp))
+ return true;
+
+ if (eType != ScQueryEntry::ByString)
+ return false;
+
+ return rCell.hasString();
+}
+
+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.meType)
+ {
+ case CELLTYPE_VALUE:
+ nCellVal = rCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nCellVal = rCell.mpFormula->GetValue();
+ break;
+ default:
+ nCellVal = 0.0;
+ }
+ if (rItem.mbRoundForFilter && nCellVal != 0.0)
+ {
+ nNumFmt = getNumFmt(nCol, nRow);
+ if (nNumFmt)
+ {
+ switch (rCell.meType)
+ {
+ 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)
+ {
+ 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;
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+OUString ScQueryEvaluator::getCellString(const ScRefCellValue& rCell, SCROW nRow, SCCOL nCol,
+ const svl::SharedString** sharedString)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ const FormulaError error = rCell.mpFormula->GetErrCode();
+ auto it = mCachedSharedErrorStrings.find(error);
+ if (it == mCachedSharedErrorStrings.end())
+ {
+ svl::SharedString str = mrStrPool.intern(ScGlobal::GetErrorString(error));
+ auto pos = mCachedSharedErrorStrings.insert({ error, str });
+ assert(pos.second); // inserted
+ it = pos.first;
+ }
+ *sharedString = &it->second;
+ return OUString();
+ }
+ else if (rCell.meType == CELLTYPE_STRING)
+ {
+ *sharedString = rCell.mpString;
+ return OUString();
+ }
+ else
+ {
+ sal_uInt32 nFormat
+ = mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
+ : mrTab.GetNumberFormat(nCol, nRow);
+ SvNumberFormatter* pFormatter
+ = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+ return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrDoc, sharedString, true);
+ }
+}
+
+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).
+ const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
+ const bool 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 one parameter: [pValueSource1] or [pValueSource2] but never in both.
+// For the template argument see isFastCompareByString().
+template <bool bFast>
+std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem,
+ const svl::SharedString* pValueSource1,
+ const OUString* pValueSource2)
+{
+ bool bOk = false;
+ bool bTestEqual = false;
+ bool bMatchWholeCell;
+ if (bFast)
+ bMatchWholeCell = true;
+ else
+ bMatchWholeCell = isMatchWholeCell(rEntry.eOp);
+ const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
+ const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
+
+ assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
+ // [pValueSource1] or [pValueSource2] but never both of them or none of them
+ assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
+
+ if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
+ {
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = rValue.getLength();
+
+ // 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)
+ {
+ if (bFast || pValueSource1)
+ {
+ // Fast string equality check by comparing string identifiers.
+ // This is the bFast path, all conditions should lead here on bFast == true.
+ if (mrParam.bCaseSens)
+ {
+ bOk = pValueSource1->getData() == rItem.maString.getData();
+ }
+ else
+ {
+ bOk = pValueSource1->getDataIgnoreCase()
+ == rItem.maString.getDataIgnoreCase();
+ }
+ }
+ else // if (pValueSource2)
+ {
+ if (mrParam.bCaseSens)
+ {
+ bOk = (*pValueSource2 == rItem.maString.getString());
+ }
+ else
+ {
+ // fallback
+ const svl::SharedString rSource2(mrStrPool.intern(*pValueSource2));
+ // Fast string equality check by comparing string identifiers.
+ bOk = rSource2.getDataIgnoreCase() == rItem.maString.getDataIgnoreCase();
+ }
+ }
+
+ if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else
+ {
+ // Where do we find a match (if at all)
+ sal_Int32 nStrPos;
+
+ if (!mbCaseSensitive)
+ { // Common case for vlookup etc.
+ const svl::SharedString rSource(
+ pValueSource1 ? *pValueSource1 : mrStrPool.intern(*pValueSource2));
+
+ const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase();
+ const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
+
+ assert(pCellStr != nullptr);
+ if (pQuer == nullptr)
+ pQuer = svl::SharedString::getEmptyString().getDataIgnoreCase();
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (pCellStr->length - pQuer->length)
+ : 0;
+
+ if (nIndex < 0)
+ nStrPos = -1;
+ else
+ { // OUString::indexOf
+ nStrPos = rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
+ pCellStr->length - nIndex,
+ pQuer->buffer, pQuer->length);
+
+ if (nStrPos >= 0)
+ nStrPos += nIndex;
+ }
+ }
+ else
+ {
+ const OUString& rValue
+ = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ const OUString aQueryStr = rItem.maString.getString();
+ const LanguageType nLang
+ = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+ setupTransliteratorIfNeeded();
+ const OUString aCell(mpTransliteration->transliterate(
+ rValue, nLang, 0, rValue.getLength(), nullptr));
+
+ const OUString aQuer(mpTransliteration->transliterate(
+ aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (aCell.getLength() - aQuer.getLength())
+ : 0;
+ nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, nIndex));
+ }
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_CONTAINS:
+ bOk = (nStrPos != -1);
+ break;
+ case SC_NOT_EQUAL:
+ case SC_DOES_NOT_CONTAIN:
+ bOk = (nStrPos == -1);
+ break;
+ case SC_BEGINS_WITH:
+ bOk = (nStrPos == 0);
+ break;
+ case SC_DOES_NOT_BEGIN_WITH:
+ bOk = (nStrPos != 0);
+ break;
+ case SC_ENDS_WITH:
+ bOk = (nStrPos >= 0);
+ break;
+ case SC_DOES_NOT_END_WITH:
+ bOk = (nStrPos < 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+ else
+ { // use collator here because data was probably sorted
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ setupCollatorIfNeeded();
+ sal_Int32 nCompare = mpCollator->compareString(rValue, rItem.maString.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS:
+ bOk = (nCompare < 0);
+ break;
+ case SC_GREATER:
+ bOk = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL:
+ bOk = (nCompare <= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ case SC_GREATER_EQUAL:
+ bOk = (nCompare >= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+ bool bHasConditionalColor = false;
+ // Text color can be set via conditional formatting - check that first
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ color = pColor->GetValue();
+ bHasConditionalColor = true;
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
+ color = pColor->GetValue();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByBackgroundColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+
+ // Background color can be set via conditional formatting - check that first
+ bool bHasConditionalColor = false;
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ color = pBackgroundColor->GetColor();
+ bHasConditionalColor = true;
+ }
+ }
+
+ ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, mrTab.GetTab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ color = *(pColFormat->GetColor(aPos));
+ bHasConditionalColor = true;
+ }
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
+ color = pBrush->GetColor();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+// 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;
+
+ if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
+ && rEntry.eOp != SC_LESS_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
+ && rEntry.eOp != SC_GREATER_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
+ }
+
+ return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, 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.meType == CELLTYPE_VALUE)
+ value = aCell.mfValue;
+ else if (aCell.meType == CELLTYPE_FORMULA
+ && aCell.mpFormula->GetErrCode() != FormulaError::NONE
+ && aCell.mpFormula->IsValue())
+ {
+ value = aCell.mpFormula->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 (const auto& 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 (const auto& 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);
+ }
+ }
+ }
+ }
+ }
+ const svl::SharedString* cellSharedString = nullptr;
+ OUString cellString;
+ bool cellStringSet = false;
+ const bool 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()
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ // 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 (cellSharedString != nullptr)
+ {
+ 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 (const auto& 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 (const auto& 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 (const auto& rItem : rItems)
+ {
+ if (rItem.meType == ScQueryEntry::ByTextColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (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;
+ }
+ else if (isQueryByString(rEntry.eOp, rItem.meType, aCell))
+ {
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ std::pair<bool, bool> aThisRes;
+ if (cellSharedString && bFastCompareByString) // fast
+ aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
+ else if (cellSharedString)
+ aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
+ else
+ aThisRes = compareByString(rEntry, rItem, nullptr, &cellString);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (mrParam.mbRangeLookup)
+ {
+ std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+
+ if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
+ break;
+ }
+ return aRes;
+}
+
+bool ScQueryEvaluator::ValidQuery(SCROW nRow, const ScRefCellValue* pCell,
+ sc::TableColumnBlockPositionSet* pBlockPos)
+{
+ if (!mrParam.GetEntry(0).bDoQuery)
+ return true;
+
+ tools::Long nPos = -1;
+ ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
+ for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
+ {
+ const ScQueryEntry& rEntry = *it;
+
+ // 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;
+ else if (pBlockPos)
+ { // hinted mdds access
+ aCell = const_cast<ScTable&>(mrTab).GetCellValue(
+ nCol, *pBlockPos->getBlockPosition(nCol), nRow);
+ }
+ else
+ aCell = mrTab.GetCellValue(nCol, nRow);
+
+ std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
+
+ if (nPos == -1)
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ else
+ {
+ if (rEntry.eConnect == SC_AND)
+ {
+ mpPasst[nPos] = mpPasst[nPos] && aRes.first;
+ mpTest[nPos] = mpTest[nPos] && aRes.second;
+ }
+ else
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ }
+ }
+
+ for (tools::Long j = 1; j <= nPos; j++)
+ {
+ mpPasst[0] = mpPasst[0] || mpPasst[j];
+ mpTest[0] = mpTest[0] || mpTest[j];
+ }
+
+ bool bRet = mpPasst[0];
+ if (mpTestEqualCondition)
+ *mpTestEqualCondition = mpTest[0];
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */