LibreOffice Module sc (master)  1
queryevaluator.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <queryevaluator.hxx>
21 
22 #include <cellform.hxx>
23 #include <cellvalue.hxx>
24 #include <colorscale.hxx>
25 #include <document.hxx>
26 #include <docoptio.hxx>
27 #include <queryparam.hxx>
28 #include <scitems.hxx>
29 #include <table.hxx>
30 
31 #include <editeng/brushitem.hxx>
32 #include <editeng/colritem.hxx>
33 #include <svl/numformat.hxx>
34 #include <svl/sharedstringpool.hxx>
35 #include <svl/zformat.hxx>
37 
39 {
40  switch (rEntry.eOp)
41  {
42  // these operators can only be used with textural comparisons.
43  case SC_CONTAINS:
45  case SC_BEGINS_WITH:
46  case SC_ENDS_WITH:
49  return true;
50  default:;
51  }
52  return false;
53 }
54 
56 {
57  if (isPartialTextMatchOp(rEntry))
58  return true;
59 
60  switch (rEntry.eOp)
61  {
62  // these operators can be used for either textural or value comparison.
63  case SC_EQUAL:
64  case SC_NOT_EQUAL:
65  return true;
66  default:;
67  }
68  return false;
69 }
70 
71 bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, const ScQueryEntry& rEntry)
72 {
73  bool bMatchWholeCell = docMatchWholeCell;
74  if (isPartialTextMatchOp(rEntry))
75  // may have to do partial textural comparison.
76  bMatchWholeCell = false;
77  return bMatchWholeCell;
78 }
79 
81 {
83 }
84 
86 {
87  return isMatchWholeCellHelper(rDoc.GetDocOptions().IsMatchWholeCell(), rEntry);
88 }
89 
91 {
92  if (!mpTransliteration)
94 }
95 
97 {
98  if (!mpCollator)
100 }
101 
103  const ScQueryParam& rParam, const ScInterpreterContext* pContext,
104  bool* pTestEqualCondition)
105  : mrDoc(rDoc)
106  , mrStrPool(rDoc.GetSharedStringPool())
107  , mrTab(rTab)
108  , mrParam(rParam)
109  , mpTestEqualCondition(pTestEqualCondition)
110  , mpTransliteration(nullptr)
111  , mpCollator(nullptr)
112  , mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell())
113  , mbCaseSensitive(rParam.bCaseSens)
114  , mpContext(pContext)
115  , mnEntryCount(mrParam.GetEntryCount())
116 {
117  if (mnEntryCount <= nFixedBools)
118  {
119  mpPasst = &maBool[0];
120  mpTest = &maTest[0];
121  }
122  else
123  {
124  mpBoolDynamic.reset(new bool[mnEntryCount]);
125  mpTestDynamic.reset(new bool[mnEntryCount]);
126  mpPasst = mpBoolDynamic.get();
127  mpTest = mpTestDynamic.get();
128  }
129 }
130 
132 {
134  return false;
135 
136  return isTextMatchOp(rEntry);
137 }
138 
140 {
142  return false;
143 
145  return false;
146 
147  return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
148 }
149 
151  const ScRefCellValue& rCell)
152 {
153  if (rItem.meType == ScQueryEntry::ByString || isPartialTextMatchOp(rEntry))
154  return false;
155 
156  return isQueryByValueForCell(rCell);
157 }
158 
160 {
161  if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
162  // Error values are compared as string.
163  return false;
164 
165  return rCell.hasNumeric();
166 }
167 
169  const ScRefCellValue& rCell)
170 {
171  if (isTextMatchOp(rEntry))
172  return true;
173 
174  if (rItem.meType != ScQueryEntry::ByString)
175  return false;
176 
177  return rCell.hasString();
178 }
179 
181 {
182  sal_uInt32 nNumFmt
184  : mrTab.GetNumberFormat(nCol, nRow));
185  if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
186  // Any General of any locale is irrelevant for rounding.
187  nNumFmt = 0;
188  return nNumFmt;
189 }
190 
191 std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& rCell, SCCOL nCol,
192  SCROW nRow, const ScQueryEntry& rEntry,
193  const ScQueryEntry::Item& rItem)
194 {
195  bool bOk = false;
196  bool bTestEqual = false;
197  double nCellVal;
198  double fQueryVal = rItem.mfVal;
199  // Defer all number format detection to as late as possible as it's a
200  // bottle neck, even if that complicates the code. Also do not
201  // unnecessarily call ScDocument::RoundValueAsShown() for the same
202  // reason.
203  sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
204 
205  switch (rCell.meType)
206  {
207  case CELLTYPE_VALUE:
208  nCellVal = rCell.mfValue;
209  break;
210  case CELLTYPE_FORMULA:
211  nCellVal = rCell.mpFormula->GetValue();
212  break;
213  default:
214  nCellVal = 0.0;
215  }
216  if (rItem.mbRoundForFilter && nCellVal != 0.0)
217  {
218  nNumFmt = getNumFmt(nCol, nRow);
219  if (nNumFmt)
220  {
221  switch (rCell.meType)
222  {
223  case CELLTYPE_VALUE:
224  case CELLTYPE_FORMULA:
225  nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, mpContext);
226  break;
227  default:
228  assert(!"can't be");
229  }
230  }
231  }
232 
233  /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
234  * date+time format was queried rEntry.bQueryByDate is not set. In
235  * case other queries wanted to use this mechanism they should do
236  * the same, in other words only if rEntry.nVal is an integer value
237  * rEntry.bQueryByDate should be true and the time fraction be
238  * stripped here. */
239 
240  if (rItem.meType == ScQueryEntry::ByDate)
241  {
242  if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
243  nNumFmt = getNumFmt(nCol, nRow);
244  if (nNumFmt)
245  {
246  SvNumberFormatter* pFormatter
248  const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
249  if (pEntry)
250  {
251  SvNumFormatType nNumFmtType = pEntry->GetType();
252  /* NOTE: Omitting the check for absence of
253  * css::util::NumberFormat::TIME would include also date+time formatted
254  * values of the same day. That may be desired in some
255  * cases, querying all time values of a day, but confusing
256  * in other cases. A user can always setup a standard
257  * filter query for x >= date AND x < date+1 */
258  if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & SvNumFormatType::TIME))
259  {
260  // The format is of date type. Strip off the time
261  // element.
262  nCellVal = ::rtl::math::approxFloor(nCellVal);
263  }
264  }
265  }
266  }
267 
268  switch (rEntry.eOp)
269  {
270  case SC_EQUAL:
271  bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
272  break;
273  case SC_LESS:
274  bOk = (nCellVal < fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
275  break;
276  case SC_GREATER:
277  bOk = (nCellVal > fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
278  break;
279  case SC_LESS_EQUAL:
280  bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
281  if (bOk && mpTestEqualCondition)
282  bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
283  break;
284  case SC_GREATER_EQUAL:
285  bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
286  if (bOk && mpTestEqualCondition)
287  bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
288  break;
289  case SC_NOT_EQUAL:
290  bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
291  break;
292  default:
293  assert(false);
294  break;
295  }
296 
297  return std::pair<bool, bool>(bOk, bTestEqual);
298 }
299 
301  const ScQueryEntry& rEntry,
302  const svl::SharedString** sharedString)
303 {
304  if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
305  {
306  // Error cell is evaluated as string (for now).
307  const FormulaError error = rCell.mpFormula->GetErrCode();
308  auto it = mCachedSharedErrorStrings.find(error);
309  if (it == mCachedSharedErrorStrings.end())
310  {
312  auto pos = mCachedSharedErrorStrings.insert({ error, str });
313  assert(pos.second); // inserted
314  it = pos.first;
315  }
316  *sharedString = &it->second;
317  return OUString();
318  }
319  else if (rCell.meType == CELLTYPE_STRING)
320  {
321  *sharedString = rCell.mpString;
322  return OUString();
323  }
324  else
325  {
326  sal_uInt32 nFormat
327  = mpContext
328  ? mrTab.GetNumberFormat(*mpContext, ScAddress(static_cast<SCCOL>(rEntry.nField),
329  nRow, mrTab.GetTab()))
330  : mrTab.GetNumberFormat(static_cast<SCCOL>(rEntry.nField), nRow);
331  SvNumberFormatter* pFormatter
333  return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrDoc, sharedString,
334  rEntry.bDoQuery);
335  }
336 }
337 
339 {
340  // If this is true, then there's a fast path in compareByString() which
341  // can be selected using the template argument to get fast code
342  // that will not check the same conditions every time. This makes a difference
343  // in fast lookups that search for an exact value (case sensitive or not).
344  const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
345  const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
346  // SC_EQUAL is part of isTextMatchOp(rEntry)
347  return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
348  && isMatchWholeCell(rEntry);
349 }
350 
351 // The value is placed inside one parameter: [pValueSource1] or [pValueSource2] but never in both.
352 // For the template argument see isFastCompareByString().
353 template <bool bFast>
354 std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry,
355  const ScQueryEntry::Item& rItem,
356  const svl::SharedString* pValueSource1,
357  const OUString* pValueSource2)
358 {
359  bool bOk = false;
360  bool bTestEqual = false;
361  bool bMatchWholeCell;
362  if (bFast)
363  bMatchWholeCell = true;
364  else
365  bMatchWholeCell = isMatchWholeCell(rEntry);
366  const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
367  const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
368 
369  assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
370  // [pValueSource1] or [pValueSource2] but never both of them or none of them
371  assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
372 
373  if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
374  {
375  const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
376 
377  sal_Int32 nStart = 0;
378  sal_Int32 nEnd = rValue.getLength();
379 
380  // from 614 on, nEnd is behind the found text
381  bool bMatch = false;
382  if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
383  {
384  nEnd = 0;
385  nStart = rValue.getLength();
386  bMatch
387  = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
388  ->SearchBackward(rValue, &nStart, &nEnd);
389  }
390  else
391  {
392  bMatch
393  = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
394  ->SearchForward(rValue, &nStart, &nEnd);
395  }
396  if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != rValue.getLength()))
397  bMatch = false; // RegExp must match entire cell string
398  if (bRealWildOrRegExp)
399  {
400  switch (rEntry.eOp)
401  {
402  case SC_EQUAL:
403  case SC_CONTAINS:
404  bOk = bMatch;
405  break;
406  case SC_NOT_EQUAL:
407  case SC_DOES_NOT_CONTAIN:
408  bOk = !bMatch;
409  break;
410  case SC_BEGINS_WITH:
411  bOk = (bMatch && (nStart == 0));
412  break;
414  bOk = !(bMatch && (nStart == 0));
415  break;
416  case SC_ENDS_WITH:
417  bOk = (bMatch && (nEnd == rValue.getLength()));
418  break;
420  bOk = !(bMatch && (nEnd == rValue.getLength()));
421  break;
422  default:
423  assert(false);
424  break;
425  }
426  }
427  else
428  bTestEqual = bMatch;
429  }
430  if (bFast || !bRealWildOrRegExp)
431  {
432  // Simple string matching i.e. no regexp match.
433  if (bFast || isTextMatchOp(rEntry))
434  {
435  // Check this even with bFast.
436  if (rItem.meType != ScQueryEntry::ByString && rItem.maString.isEmpty())
437  {
438  // #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup),
439  // the query value is assigned directly, and the string is empty. In that case,
440  // don't find any string (isEqual would find empty string results in formula cells).
441  bOk = false;
442  if (rEntry.eOp == SC_NOT_EQUAL)
443  bOk = !bOk;
444  }
445  else if (bFast || bMatchWholeCell)
446  {
447  if (bFast || pValueSource1)
448  {
449  // Fast string equality check by comparing string identifiers.
450  // This is the bFast path, all conditions should lead here on bFast == true.
451  if (mrParam.bCaseSens)
452  {
453  bOk = pValueSource1->getData() == rItem.maString.getData();
454  }
455  else
456  {
457  bOk = pValueSource1->getDataIgnoreCase()
458  == rItem.maString.getDataIgnoreCase();
459  }
460  }
461  else // if (pValueSource2)
462  {
463  if (mrParam.bCaseSens)
464  {
465  bOk = (*pValueSource2 == rItem.maString.getString());
466  }
467  else
468  {
469  // fallback
470  const svl::SharedString rSource2(mrStrPool.intern(*pValueSource2));
471  // Fast string equality check by comparing string identifiers.
472  bOk = rSource2.getDataIgnoreCase() == rItem.maString.getDataIgnoreCase();
473  }
474  }
475 
476  if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
477  bOk = !bOk;
478  }
479  else
480  {
481  // Where do we find a match (if at all)
482  sal_Int32 nStrPos;
483 
484  if (!mbCaseSensitive)
485  { // Common case for vlookup etc.
486  const svl::SharedString rSource(
487  pValueSource1 ? *pValueSource1 : mrStrPool.intern(*pValueSource2));
488 
489  const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase();
490  const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
491 
492  assert(pQuer != nullptr);
493  assert(pCellStr != nullptr);
494 
495  const sal_Int32 nIndex
496  = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
497  ? (pCellStr->length - pQuer->length)
498  : 0;
499 
500  if (nIndex < 0)
501  nStrPos = -1;
502  else
503  { // OUString::indexOf
504  nStrPos = rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
505  pCellStr->length - nIndex,
506  pQuer->buffer, pQuer->length);
507 
508  if (nStrPos >= 0)
509  nStrPos += nIndex;
510  }
511  }
512  else
513  {
514  const OUString& rValue
515  = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
516  const OUString aQueryStr = rItem.maString.getString();
517  const LanguageType nLang
518  = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
520  const OUString aCell(mpTransliteration->transliterate(
521  rValue, nLang, 0, rValue.getLength(), nullptr));
522 
523  const OUString aQuer(mpTransliteration->transliterate(
524  aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
525 
526  const sal_Int32 nIndex
527  = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
528  ? (aCell.getLength() - aQuer.getLength())
529  : 0;
530  nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, nIndex));
531  }
532  switch (rEntry.eOp)
533  {
534  case SC_EQUAL:
535  case SC_CONTAINS:
536  bOk = (nStrPos != -1);
537  break;
538  case SC_NOT_EQUAL:
539  case SC_DOES_NOT_CONTAIN:
540  bOk = (nStrPos == -1);
541  break;
542  case SC_BEGINS_WITH:
543  bOk = (nStrPos == 0);
544  break;
546  bOk = (nStrPos != 0);
547  break;
548  case SC_ENDS_WITH:
549  bOk = (nStrPos >= 0);
550  break;
552  bOk = (nStrPos < 0);
553  break;
554  default:
555  assert(false);
556  break;
557  }
558  }
559  }
560  else
561  { // use collator here because data was probably sorted
562  const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
564  sal_Int32 nCompare = mpCollator->compareString(rValue, rItem.maString.getString());
565  switch (rEntry.eOp)
566  {
567  case SC_LESS:
568  bOk = (nCompare < 0);
569  break;
570  case SC_GREATER:
571  bOk = (nCompare > 0);
572  break;
573  case SC_LESS_EQUAL:
574  bOk = (nCompare <= 0);
575  if (bOk && mpTestEqualCondition && !bTestEqual)
576  bTestEqual = (nCompare == 0);
577  break;
578  case SC_GREATER_EQUAL:
579  bOk = (nCompare >= 0);
580  if (bOk && mpTestEqualCondition && !bTestEqual)
581  bTestEqual = (nCompare == 0);
582  break;
583  default:
584  assert(false);
585  break;
586  }
587  }
588  }
589 
590  return std::pair<bool, bool>(bOk, bTestEqual);
591 }
592 
593 std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW nRow,
594  const ScQueryEntry::Item& rItem)
595 {
596  ScAddress aPos(nCol, nRow, mrTab.GetTab());
597  Color color;
598  bool bHasConditionalColor = false;
599  // Text color can be set via conditional formatting - check that first
600  const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
601  if (pPattern)
602  {
603  if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
604  {
605  const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
606  const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
607  color = pColor->GetValue();
608  bHasConditionalColor = true;
609  }
610  }
611 
612  if (!bHasConditionalColor)
613  {
614  const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
615  color = pColor->GetValue();
616  }
617 
618  bool bMatch = rItem.maColor == color;
619  return std::pair<bool, bool>(bMatch, false);
620 }
621 
622 std::pair<bool, bool> ScQueryEvaluator::compareByBackgroundColor(SCCOL nCol, SCROW nRow,
623  const ScQueryEntry::Item& rItem)
624 {
625  ScAddress aPos(nCol, nRow, mrTab.GetTab());
626  Color color;
627 
628  // Background color can be set via conditional formatting - check that first
629  bool bHasConditionalColor = false;
630  const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
631  if (pPattern)
632  {
633  if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
634  {
635  const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
636  const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
637  color = pBackgroundColor->GetColor();
638  bHasConditionalColor = true;
639  }
640  }
641 
642  ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, mrTab.GetTab());
643  if (pCondFormat)
644  {
645  for (size_t i = 0; i < pCondFormat->size(); i++)
646  {
647  auto aEntry = pCondFormat->GetEntry(i);
648  if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
649  {
650  const ScColorScaleFormat* pColFormat
651  = static_cast<const ScColorScaleFormat*>(aEntry);
652  color = *(pColFormat->GetColor(aPos));
653  bHasConditionalColor = true;
654  }
655  }
656  }
657 
658  if (!bHasConditionalColor)
659  {
660  const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
661  color = pBrush->GetColor();
662  }
663 
664  bool bMatch = rItem.maColor == color;
665  return std::pair<bool, bool>(bMatch, false);
666 }
667 
668 // To be called only if both isQueryByValue() and isQueryByString()
669 // returned false and range lookup is wanted! In range lookup comparison
670 // numbers are less than strings. Nothing else is compared.
671 std::pair<bool, bool> ScQueryEvaluator::compareByRangeLookup(const ScRefCellValue& rCell,
672  const ScQueryEntry& rEntry,
673  const ScQueryEntry::Item& rItem)
674 {
675  bool bTestEqual = false;
676 
677  if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
678  && rEntry.eOp != SC_LESS_EQUAL)
679  return std::pair<bool, bool>(false, bTestEqual);
680 
681  if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
682  && rEntry.eOp != SC_GREATER_EQUAL)
683  return std::pair<bool, bool>(false, bTestEqual);
684 
685  if (rItem.meType == ScQueryEntry::ByString)
686  {
687  if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
688  // Error values are compared as string.
689  return std::pair<bool, bool>(false, bTestEqual);
690 
691  return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
692  }
693 
694  return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
695 }
696 
697 std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue& aCell,
698  const ScQueryEntry& rEntry, size_t nEntryIndex)
699 {
700  std::pair<bool, bool> aRes(false, false);
701  const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
702  if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
703  {
704  if (rEntry.IsQueryByEmpty())
705  aRes.first = aCell.isEmpty();
706  else
707  {
708  assert(rEntry.IsQueryByNonEmpty());
709  aRes.first = !aCell.isEmpty();
710  }
711  return aRes;
712  }
713  if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
714  {
715  // If there are many items to query for (autofilter does this), then try to search
716  // efficiently in those items. So first search all the items of the relevant type,
717  // If that does not find anything, fall back to the generic code.
718  double value = 0;
719  bool valid = true;
720  // For ScQueryEntry::ByValue check that the cell either is a value or is a formula
721  // that has a value and is not an error (those are compared as strings). This
722  // is basically simplified isQueryByValue().
723  if (aCell.meType == CELLTYPE_VALUE)
724  value = aCell.mfValue;
725  else if (aCell.meType == CELLTYPE_FORMULA
726  && aCell.mpFormula->GetErrCode() != FormulaError::NONE
727  && aCell.mpFormula->IsValue())
728  {
729  value = aCell.mpFormula->GetValue();
730  }
731  else
732  valid = false;
733  if (valid)
734  {
735  if (rItems.size() >= 100)
736  {
737  // Sort, cache and binary search for the value in items.
738  // Don't bother comparing approximately.
739  if (mCachedSortedItemValues.size() <= nEntryIndex)
740  {
741  mCachedSortedItemValues.resize(nEntryIndex + 1);
742  auto& values = mCachedSortedItemValues[nEntryIndex];
743  values.reserve(rItems.size());
744  for (const auto& rItem : rItems)
745  if (rItem.meType == ScQueryEntry::ByValue)
746  values.push_back(rItem.mfVal);
747  std::sort(values.begin(), values.end());
748  }
749  auto& values = mCachedSortedItemValues[nEntryIndex];
750  auto it = std::lower_bound(values.begin(), values.end(), value);
751  if (it != values.end() && *it == value)
752  return std::make_pair(true, true);
753  }
754  else
755  {
756  for (const auto& rItem : rItems)
757  {
758  // For speed don't bother comparing approximately here, usually there either
759  // will be an exact match or it wouldn't match anyway.
760  if (rItem.meType == ScQueryEntry::ByValue && value == rItem.mfVal)
761  {
762  return std::make_pair(true, true);
763  }
764  }
765  }
766  }
767  }
768  const svl::SharedString* cellSharedString = nullptr;
769  OUString cellString;
770  bool cellStringSet = false;
771  const bool bFastCompareByString = isFastCompareByString(rEntry);
772  if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
773  {
774  // The same as above but for strings. Try to optimize the case when
775  // it's a svl::SharedString comparison. That happens when SC_EQUAL is used
776  // and simple matching is used, see compareByString()
777  if (!cellStringSet)
778  {
779  cellString = getCellString(aCell, nRow, rEntry, &cellSharedString);
780  cellStringSet = true;
781  }
782  // Allow also checking ScQueryEntry::ByValue if the cell is not numeric,
783  // as in that case isQueryByNumeric() would be false and isQueryByString() would
784  // be true because of SC_EQUAL making isTextMatchOp() true.
785  bool compareByValue = !isQueryByValueForCell(aCell);
786  // For ScQueryEntry::ByString check that the cell is represented by a shared string,
787  // which means it's either a string cell or a formula error. This is not as
788  // generous as isQueryByString() but it should be enough and better be safe.
789  if (cellSharedString != nullptr)
790  {
791  if (rItems.size() >= 100)
792  {
793  // Sort, cache and binary search for the string in items.
794  // Since each SharedString is identified by pointer value,
795  // sorting by pointer value is enough.
796  if (mCachedSortedItemStrings.size() <= nEntryIndex)
797  {
798  mCachedSortedItemStrings.resize(nEntryIndex + 1);
799  auto& values = mCachedSortedItemStrings[nEntryIndex];
800  values.reserve(rItems.size());
801  for (const auto& rItem : rItems)
802  {
803  if (rItem.meType == ScQueryEntry::ByString
804  || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
805  {
806  values.push_back(mrParam.bCaseSens
807  ? rItem.maString.getData()
808  : rItem.maString.getDataIgnoreCase());
809  }
810  }
811  std::sort(values.begin(), values.end());
812  }
813  auto& values = mCachedSortedItemStrings[nEntryIndex];
814  const rtl_uString* string = mrParam.bCaseSens
815  ? cellSharedString->getData()
816  : cellSharedString->getDataIgnoreCase();
817  auto it = std::lower_bound(values.begin(), values.end(), string);
818  if (it != values.end() && *it == string)
819  return std::make_pair(true, true);
820  }
821  else
822  {
823  for (const auto& rItem : rItems)
824  {
825  if ((rItem.meType == ScQueryEntry::ByString
826  || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
827  && (mrParam.bCaseSens
828  ? cellSharedString->getData() == rItem.maString.getData()
829  : cellSharedString->getDataIgnoreCase()
830  == rItem.maString.getDataIgnoreCase()))
831  {
832  return std::make_pair(true, true);
833  }
834  }
835  }
836  }
837  }
838  // Generic handling.
839  for (const auto& rItem : rItems)
840  {
841  if (rItem.meType == ScQueryEntry::ByTextColor)
842  {
843  std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
844  aRes.first |= aThisRes.first;
845  aRes.second |= aThisRes.second;
846  }
847  else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
848  {
849  std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
850  aRes.first |= aThisRes.first;
851  aRes.second |= aThisRes.second;
852  }
853  else if (isQueryByValue(rEntry, rItem, aCell))
854  {
855  std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
856  aRes.first |= aThisRes.first;
857  aRes.second |= aThisRes.second;
858  }
859  else if (isQueryByString(rEntry, rItem, aCell))
860  {
861  if (!cellStringSet)
862  {
863  cellString = getCellString(aCell, nRow, rEntry, &cellSharedString);
864  cellStringSet = true;
865  }
866  std::pair<bool, bool> aThisRes;
867  if (cellSharedString && bFastCompareByString) // fast
868  aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
869  else if (cellSharedString)
870  aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
871  else
872  aThisRes = compareByString(rEntry, rItem, nullptr, &cellString);
873  aRes.first |= aThisRes.first;
874  aRes.second |= aThisRes.second;
875  }
876  else if (mrParam.mbRangeLookup)
877  {
878  std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
879  aRes.first |= aThisRes.first;
880  aRes.second |= aThisRes.second;
881  }
882 
883  if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
884  break;
885  }
886  return aRes;
887 }
888 
891 {
892  if (!mrParam.GetEntry(0).bDoQuery)
893  return true;
894 
895  tools::Long nPos = -1;
896  ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
897  for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
898  {
899  const ScQueryEntry& rEntry = *it;
900 
901  // Short-circuit the test at the end of the loop - if this is SC_AND
902  // and the previous value is false, this value will not be needed.
903  // Disable this if pbTestEqualCondition is present as that one may get set
904  // even if the result is false (that also means pTest doesn't need to be
905  // handled here).
906  if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
907  && !mpPasst[nPos])
908  {
909  continue;
910  }
911 
912  SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
913 
914  // We can only handle one single direct query passed as a known pCell,
915  // subsequent queries have to obtain the cell.
916  ScRefCellValue aCell;
917  if (pCell && it == itBeg)
918  aCell = *pCell;
919  else if (pBlockPos)
920  { // hinted mdds access
921  aCell = const_cast<ScTable&>(mrTab).GetCellValue(
922  nCol, *pBlockPos->getBlockPosition(nCol), nRow);
923  }
924  else
925  aCell = mrTab.GetCellValue(nCol, nRow);
926 
927  std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
928 
929  if (nPos == -1)
930  {
931  nPos++;
932  mpPasst[nPos] = aRes.first;
933  mpTest[nPos] = aRes.second;
934  }
935  else
936  {
937  if (rEntry.eConnect == SC_AND)
938  {
939  mpPasst[nPos] = mpPasst[nPos] && aRes.first;
940  mpTest[nPos] = mpTest[nPos] && aRes.second;
941  }
942  else
943  {
944  nPos++;
945  mpPasst[nPos] = aRes.first;
946  mpTest[nPos] = aRes.second;
947  }
948  }
949  }
950 
951  for (tools::Long j = 1; j <= nPos; j++)
952  {
953  mpPasst[0] = mpPasst[0] || mpPasst[j];
954  mpTest[0] = mpTest[0] || mpTest[j];
955  }
956 
957  bool bRet = mpPasst[0];
960 
961  return bRet;
962 }
963 
964 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int32 nIndex
std::unordered_map< FormulaError, svl::SharedString > mCachedSharedErrorStrings
OUString getString() const
SharedString intern(const OUString &rStr)
ColumnBlockPosition * getBlockPosition(SCCOL nCol)
sal_Int32 compareString(const OUString &s1, const OUString &s2) const
const bool mbCaseSensitive
std::vector< char * > values
std::pair< bool, bool > compareByString(const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem, const svl::SharedString *pValueSource1, const OUString *pValueSource2)
SCTAB GetTab() const
Definition: table.hxx:285
rtl_uString * getDataIgnoreCase()
bool isEmpty() const
Definition: cellvalue.cxx:670
const ScTable & mrTab
bool isMatchWholeCell(const ScQueryEntry &rEntry) const
long Long
std::unique_ptr< bool[]> mpBoolDynamic
QueryItemsType & GetQueryItems()
Definition: queryentry.hxx:75
static std::optional< SvtSysLocale > oSysLocale
Definition: global.hxx:529
SCCOLROW nField
Definition: queryentry.hxx:61
bool IsQueryByEmpty() const
Definition: queryentry.cxx:87
This is very similar to ScCellValue, except that it references the original value instead of copying ...
Definition: cellvalue.hxx:103
ParserContextSharedPtr mpContext
static constexpr SCSIZE nFixedBools
const_iterator end() const
Definition: queryparam.cxx:61
bool isTestWildOrRegExp(const ScQueryEntry &rEntry) const
std::pair< bool, bool > compareByValue(const ScRefCellValue &rCell, SCCOL nCol, SCROW nRow, const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem)
const SCSIZE mnEntryCount
std::optional< Color > GetColor(const ScAddress &rAddr) const
Definition: colorscale.cxx:588
size_t pos
double GetValue()
constexpr sal_uInt32 NUMBERFORMAT_ENTRY_NOT_FOUND
bool hasString() const
Definition: cellvalue.cxx:617
FormulaError GetErrCode()
const bool mbMatchWholeCell
static bool isQueryByValue(const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem, const ScRefCellValue &rCell)
SC_DLLPUBLIC const ScQueryEntry & GetEntry(SCSIZE n) const
Definition: queryparam.cxx:115
static bool isQueryByValueForCell(const ScRefCellValue &rCell)
ScFormulaCell * mpFormula
Definition: cellvalue.hxx:110
sal_uInt32 getNumFmt(SCCOL nCol, SCROW nRow)
EntriesType::const_iterator const_iterator
Definition: queryparam.hxx:71
SC_DLLPUBLIC const ScPatternAttr * GetPattern(SCCOL nCol, SCROW nRow, SCTAB nTab) const
Definition: document.cxx:4799
size_t size() const
Definition: conditio.cxx:1771
SC_DLLPUBLIC const SfxPoolItem * GetAttr(SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich) const
Definition: document.cxx:4764
utl::TextSearch * GetSearchTextPtr(utl::SearchParam::SearchType eSearchType, bool bCaseSens, bool bWildMatchSel) const
creates pSearchParam and pSearchText if necessary
Definition: queryentry.cxx:194
std::pair< bool, bool > compareByTextColor(SCCOL nCol, SCROW nRow, const ScQueryEntry::Item &rItem)
const SfxPoolItem & GetItem(sal_uInt16 nWhichP) const
Definition: patattr.hxx:72
bool IsMatchWholeCell() const
Definition: docoptio.hxx:55
bool SearchForward(const OUString &rStr, sal_Int32 *pStart, sal_Int32 *pEnd, css::util::SearchResult *pRes=nullptr)
SC_DLLPUBLIC const ScDocOptions & GetDocOptions() const
Definition: documen3.cxx:1946
CollatorWrapper * mpCollator
const Color & GetColor() const
const ScFormatEntry * GetEntry(sal_uInt16 nPos) const
Definition: conditio.cxx:1785
void setupCollatorIfNeeded()
static bool isMatchWholeCellHelper(bool docMatchWholeCell, const ScQueryEntry &rEntry)
ScDocument & mrDoc
const_iterator begin() const
Definition: queryparam.cxx:56
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:459
SvNumFormatType GetType() const
static bool isQueryByString(const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem, const ScRefCellValue &rCell)
Set of column block positions only for one table.
int i
bool isEmpty() const
bool IsQueryByNonEmpty() const
Definition: queryentry.cxx:109
const svl::SharedString * mpString
Definition: cellvalue.hxx:108
sal_Int16 SCCOL
Definition: types.hxx:21
SvNumberFormatter * GetFormatTable() const
const ScQueryParam & mrParam
std::unique_ptr< bool[]> mpTestDynamic
std::pair< bool, bool > compareByBackgroundColor(SCCOL nCol, SCROW nRow, const ScQueryEntry::Item &rItem)
SvNumFormatType
static bool isPartialTextMatchOp(const ScQueryEntry &rEntry)
bool hasNumeric() const
Definition: cellvalue.cxx:622
#define SV_COUNTRY_LANGUAGE_OFFSET
SC_DLLPUBLIC const SfxItemSet * GetCondResult(SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue *pCell=nullptr) const
Definition: documen4.cxx:805
bool mbRangeLookup
for spreadsheet functions like MATCH, LOOKUP, HLOOKUP, VLOOKUP
Definition: queryparam.hxx:49
void setupTransliteratorIfNeeded()
rtl_uString * getData()
svl::SharedString maString
Definition: queryentry.hxx:49
FormulaError
svl::SharedStringPool & mrStrPool
constexpr TypedWhichId< SvxBrushItem > ATTR_BACKGROUND(148)
SC_DLLPUBLIC double RoundValueAsShown(double fVal, sal_uInt32 nFormat, const ScInterpreterContext *pContext=nullptr) const
Definition: documen4.cxx:641
bool isFastCompareByString(const ScQueryEntry &rEntry) const
CellType meType
Definition: cellvalue.hxx:105
bool ValidQuery(SCROW nRow, const ScRefCellValue *pCell=nullptr, sc::TableColumnBlockPositionSet *pBlockPos=nullptr)
sal_Int32 SCROW
Definition: types.hxx:17
bool SearchBackward(const OUString &rStr, sal_Int32 *pStart, sal_Int32 *pEnd, css::util::SearchResult *pRes=nullptr)
static bool isTextMatchOp(const ScQueryEntry &rEntry)
bool isRealWildOrRegExp(const ScQueryEntry &rEntry) const
std::pair< bool, bool > processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue &aCell, const ScQueryEntry &rEntry, size_t nEntryIndex)
std::vector< std::vector< double > > mCachedSortedItemValues
const ScInterpreterContext * mpContext
static SC_DLLPUBLIC CollatorWrapper & GetCollator()
case-insensitive collator
Definition: global.cxx:1055
static std::pair< bool, bool > compareByRangeLookup(const ScRefCellValue &rCell, const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem)
const SvNumberformat * GetEntry(sal_uInt32 nKey) const
bool maBool[nFixedBools]
static OUString GetInputString(const ScRefCellValue &rCell, sal_uInt32 nFormat, SvNumberFormatter &rFormatter, const ScDocument &rDoc, const svl::SharedString **pShared=nullptr, bool bFiltering=false, bool bForceSystemLocale=false)
Definition: cellform.cxx:129
OUString getCellString(const ScRefCellValue &rCell, SCROW nRow, const ScQueryEntry &rEntry, const svl::SharedString **sharedString)
std::vector< std::vector< const rtl_uString * > > mCachedSortedItemStrings
OUString transliterate(const OUString &rStr, sal_Int32 nStart, sal_Int32 nLen) const
constexpr TypedWhichId< ScCondFormatItem > ATTR_CONDITIONAL(154)
Any value
ScQueryConnect eConnect
Definition: queryentry.hxx:63
bool maTest[nFixedBools]
std::vector< Item > QueryItemsType
Definition: queryentry.hxx:58
SC_DLLPUBLIC ScConditionalFormat * GetCondFormat(SCCOL nCol, SCROW nRow, SCTAB nTab) const
Definition: documen4.cxx:851
ScQueryEvaluator(ScDocument &rDoc, const ScTable &rTab, const ScQueryParam &rParam, const ScInterpreterContext *pContext=nullptr, bool *pTestEqualCondition=nullptr)
sal_uInt32 GetNumberFormat(const ScInterpreterContext &rContext, const ScAddress &rPos) const
Definition: table2.cxx:2237
static SC_DLLPUBLIC::utl::TransliterationWrapper & GetTransliteration()
Definition: global.cxx:986
ScQueryOp eOp
Definition: queryentry.hxx:62
constexpr TypedWhichId< SvxColorItem > ATTR_FONT_COLOR(109)
Each instance of this struct represents a single filtering criteria.
Definition: queryentry.hxx:33
static OUString GetErrorString(FormulaError nErrNumber)
Definition: global.cxx:311
utl::SearchParam::SearchType eSearchType
Definition: queryparam.hxx:43
utl::TransliterationWrapper * mpTransliteration
const Color & GetValue() const
sal_uInt16 nPos
ScRefCellValue GetCellValue(SCCOL nCol, sc::ColumnBlockPosition &rBlockPos, SCROW nRow)
Definition: table2.cxx:2013