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>
35#include <svl/zformat.hxx>
37
39{
40 switch (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(eOp))
58 return true;
59
60 switch (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
71bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
72{
73 bool bMatchWholeCell = docMatchWholeCell;
74 if (isPartialTextMatchOp(eOp))
75 // may have to do partial textural comparison.
76 bMatchWholeCell = false;
77 return bMatchWholeCell;
78}
79
81{
83}
84
86{
88}
89
91{
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{
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.eOp);
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{
154 return false;
155
156 return isQueryByValueForCell(rCell);
157}
158
160{
161 if (rCell.getType() == CELLTYPE_FORMULA
162 && rCell.getFormula()->GetErrCode() != FormulaError::NONE)
163 // Error values are compared as string.
164 return false;
165
166 return rCell.hasNumeric();
167}
168
170 const ScRefCellValue& rCell)
171{
172 if (isTextMatchOp(eOp))
173 return true;
174
176 return false;
177
178 return rCell.hasString();
179}
180
182{
183 sal_uInt32 nNumFmt
185 : mrTab.GetNumberFormat(nCol, nRow));
186 if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
187 // Any General of any locale is irrelevant for rounding.
188 nNumFmt = 0;
189 return nNumFmt;
190}
191
192std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& rCell, SCCOL nCol,
193 SCROW nRow, const ScQueryEntry& rEntry,
194 const ScQueryEntry::Item& rItem)
195{
196 bool bOk = false;
197 bool bTestEqual = false;
198 double nCellVal;
199 double fQueryVal = rItem.mfVal;
200 // Defer all number format detection to as late as possible as it's a
201 // bottle neck, even if that complicates the code. Also do not
202 // unnecessarily call ScDocument::RoundValueAsShown() for the same
203 // reason.
204 sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
205
206 switch (rCell.getType())
207 {
208 case CELLTYPE_VALUE:
209 nCellVal = rCell.getDouble();
210 break;
211 case CELLTYPE_FORMULA:
212 nCellVal = rCell.getFormula()->GetValue();
213 break;
214 default:
215 nCellVal = 0.0;
216 }
217 if (rItem.mbRoundForFilter && nCellVal != 0.0)
218 {
219 nNumFmt = getNumFmt(nCol, nRow);
220 if (nNumFmt)
221 {
222 switch (rCell.getType())
223 {
224 case CELLTYPE_VALUE:
225 case CELLTYPE_FORMULA:
226 nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, mpContext);
227 break;
228 default:
229 assert(!"can't be");
230 }
231 }
232 }
233
234 /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
235 * date+time format was queried rEntry.bQueryByDate is not set. In
236 * case other queries wanted to use this mechanism they should do
237 * the same, in other words only if rEntry.nVal is an integer value
238 * rEntry.bQueryByDate should be true and the time fraction be
239 * stripped here. */
240
241 if (rItem.meType == ScQueryEntry::ByDate)
242 {
243 if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
244 nNumFmt = getNumFmt(nCol, nRow);
245 if (nNumFmt)
246 {
247 SvNumberFormatter* pFormatter
249 const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
250 if (pEntry)
251 {
252 SvNumFormatType nNumFmtType = pEntry->GetType();
253 /* NOTE: Omitting the check for absence of
254 * css::util::NumberFormat::TIME would include also date+time formatted
255 * values of the same day. That may be desired in some
256 * cases, querying all time values of a day, but confusing
257 * in other cases. A user can always setup a standard
258 * filter query for x >= date AND x < date+1 */
259 if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & SvNumFormatType::TIME))
260 {
261 // The format is of date type. Strip off the time
262 // element.
263 nCellVal = ::rtl::math::approxFloor(nCellVal);
264 }
265 }
266 }
267 }
268
269 switch (rEntry.eOp)
270 {
271 case SC_EQUAL:
272 bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
273 break;
274 case SC_LESS:
275 bOk = (nCellVal < fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
276 break;
277 case SC_GREATER:
278 bOk = (nCellVal > fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
279 break;
280 case SC_LESS_EQUAL:
281 bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
282 if (bOk && mpTestEqualCondition)
283 bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
284 break;
285 case SC_GREATER_EQUAL:
286 bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
287 if (bOk && mpTestEqualCondition)
288 bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
289 break;
290 case SC_NOT_EQUAL:
291 bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
292 break;
293 default:
294 assert(false);
295 break;
296 }
297
298 return std::pair<bool, bool>(bOk, bTestEqual);
299}
300
302 const svl::SharedString** sharedString)
303{
304 if (rCell.getType() == CELLTYPE_FORMULA
305 && rCell.getFormula()->GetErrCode() != FormulaError::NONE)
306 {
307 // Error cell is evaluated as string (for now).
308 const FormulaError error = rCell.getFormula()->GetErrCode();
309 auto it = mCachedSharedErrorStrings.find(error);
310 if (it == mCachedSharedErrorStrings.end())
311 {
313 auto pos = mCachedSharedErrorStrings.insert({ error, str });
314 assert(pos.second); // inserted
315 it = pos.first;
316 }
317 *sharedString = &it->second;
318 return OUString();
319 }
320 else if (rCell.getType() == CELLTYPE_STRING)
321 {
322 *sharedString = rCell.getSharedString();
323 return OUString();
324 }
325 else
326 {
327 sal_uInt32 nFormat
329 : mrTab.GetNumberFormat(nCol, nRow);
330 SvNumberFormatter* pFormatter
332 return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrDoc, sharedString, true);
333 }
334}
335
337{
338 // If this is true, then there's a fast path in compareByString() which
339 // can be selected using the template argument to get fast code
340 // that will not check the same conditions every time. This makes a difference
341 // in fast lookups that search for an exact value (case sensitive or not).
342 const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
343 const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
344 // SC_EQUAL is part of isTextMatchOp(rEntry)
345 return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
346 && isMatchWholeCell(rEntry.eOp);
347}
348
349// The value is placed inside one parameter: [pValueSource1] or [pValueSource2] but never in both.
350// For the template argument see isFastCompareByString().
351template <bool bFast>
352std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry,
353 const ScQueryEntry::Item& rItem,
354 const svl::SharedString* pValueSource1,
355 const OUString* pValueSource2)
356{
357 bool bOk = false;
358 bool bTestEqual = false;
359 bool bMatchWholeCell;
360 if (bFast)
361 bMatchWholeCell = true;
362 else
363 bMatchWholeCell = isMatchWholeCell(rEntry.eOp);
364 const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
365 const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
366
367 assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
368 // [pValueSource1] or [pValueSource2] but never both of them or none of them
369 assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
370
371 if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
372 {
373 const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
374
375 sal_Int32 nStart = 0;
376 sal_Int32 nEnd = rValue.getLength();
377
378 // from 614 on, nEnd is behind the found text
379 bool bMatch = false;
380 if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
381 {
382 nEnd = 0;
383 nStart = rValue.getLength();
384 bMatch
385 = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
386 ->SearchBackward(rValue, &nStart, &nEnd);
387 }
388 else
389 {
390 bMatch
391 = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
392 ->SearchForward(rValue, &nStart, &nEnd);
393 }
394 if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != rValue.getLength()))
395 bMatch = false; // RegExp must match entire cell string
396 if (bRealWildOrRegExp)
397 {
398 switch (rEntry.eOp)
399 {
400 case SC_EQUAL:
401 case SC_CONTAINS:
402 bOk = bMatch;
403 break;
404 case SC_NOT_EQUAL:
406 bOk = !bMatch;
407 break;
408 case SC_BEGINS_WITH:
409 bOk = (bMatch && (nStart == 0));
410 break;
412 bOk = !(bMatch && (nStart == 0));
413 break;
414 case SC_ENDS_WITH:
415 bOk = (bMatch && (nEnd == rValue.getLength()));
416 break;
418 bOk = !(bMatch && (nEnd == rValue.getLength()));
419 break;
420 default:
421 assert(false);
422 break;
423 }
424 }
425 else
426 bTestEqual = bMatch;
427 }
428 if (bFast || !bRealWildOrRegExp)
429 {
430 // Simple string matching i.e. no regexp match.
431 if (bFast || isTextMatchOp(rEntry.eOp))
432 {
433 // Check this even with bFast.
434 if (rItem.meType != ScQueryEntry::ByString && rItem.maString.isEmpty())
435 {
436 // #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup),
437 // the query value is assigned directly, and the string is empty. In that case,
438 // don't find any string (isEqual would find empty string results in formula cells).
439 bOk = false;
440 if (rEntry.eOp == SC_NOT_EQUAL)
441 bOk = !bOk;
442 }
443 else if (bFast || bMatchWholeCell)
444 {
445 if (bFast || pValueSource1)
446 {
447 // Fast string equality check by comparing string identifiers.
448 // This is the bFast path, all conditions should lead here on bFast == true.
449 if (mrParam.bCaseSens)
450 {
451 bOk = pValueSource1->getData() == rItem.maString.getData();
452 }
453 else
454 {
455 bOk = pValueSource1->getDataIgnoreCase()
456 == rItem.maString.getDataIgnoreCase();
457 }
458 }
459 else // if (pValueSource2)
460 {
461 if (mrParam.bCaseSens)
462 {
463 bOk = (*pValueSource2 == rItem.maString.getString());
464 }
465 else
466 {
467 // fallback
468 const svl::SharedString rSource2(mrStrPool.intern(*pValueSource2));
469 // Fast string equality check by comparing string identifiers.
470 bOk = rSource2.getDataIgnoreCase() == rItem.maString.getDataIgnoreCase();
471 }
472 }
473
474 if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
475 bOk = !bOk;
476 }
477 else
478 {
479 // Where do we find a match (if at all)
480 sal_Int32 nStrPos;
481
482 if (!mbCaseSensitive)
483 { // Common case for vlookup etc.
484 const svl::SharedString rSource(
485 pValueSource1 ? *pValueSource1 : mrStrPool.intern(*pValueSource2));
486
487 const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase();
488 const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
489
490 assert(pCellStr != nullptr);
491 if (pQuer == nullptr)
493
494 const sal_Int32 nIndex
495 = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
496 ? (pCellStr->length - pQuer->length)
497 : 0;
498
499 if (nIndex < 0)
500 nStrPos = -1;
501 else
502 { // OUString::indexOf
503 nStrPos = rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
504 pCellStr->length - nIndex,
505 pQuer->buffer, pQuer->length);
506
507 if (nStrPos >= 0)
508 nStrPos += nIndex;
509 }
510 }
511 else
512 {
513 const OUString& rValue
514 = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
515 const OUString aQueryStr = rItem.maString.getString();
516 const LanguageType nLang
517 = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
519 const OUString aCell(mpTransliteration->transliterate(
520 rValue, nLang, 0, rValue.getLength(), nullptr));
521
522 const OUString aQuer(mpTransliteration->transliterate(
523 aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
524
525 const sal_Int32 nIndex
526 = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
527 ? (aCell.getLength() - aQuer.getLength())
528 : 0;
529 nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, nIndex));
530 }
531 switch (rEntry.eOp)
532 {
533 case SC_EQUAL:
534 case SC_CONTAINS:
535 bOk = (nStrPos != -1);
536 break;
537 case SC_NOT_EQUAL:
539 bOk = (nStrPos == -1);
540 break;
541 case SC_BEGINS_WITH:
542 bOk = (nStrPos == 0);
543 break;
545 bOk = (nStrPos != 0);
546 break;
547 case SC_ENDS_WITH:
548 bOk = (nStrPos >= 0);
549 break;
551 bOk = (nStrPos < 0);
552 break;
553 default:
554 assert(false);
555 break;
556 }
557 }
558 }
559 else
560 { // use collator here because data was probably sorted
561 const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
563 sal_Int32 nCompare = mpCollator->compareString(rValue, rItem.maString.getString());
564 switch (rEntry.eOp)
565 {
566 case SC_LESS:
567 bOk = (nCompare < 0);
568 break;
569 case SC_GREATER:
570 bOk = (nCompare > 0);
571 break;
572 case SC_LESS_EQUAL:
573 bOk = (nCompare <= 0);
574 if (bOk && mpTestEqualCondition && !bTestEqual)
575 bTestEqual = (nCompare == 0);
576 break;
577 case SC_GREATER_EQUAL:
578 bOk = (nCompare >= 0);
579 if (bOk && mpTestEqualCondition && !bTestEqual)
580 bTestEqual = (nCompare == 0);
581 break;
582 default:
583 assert(false);
584 break;
585 }
586 }
587 }
588
589 return std::pair<bool, bool>(bOk, bTestEqual);
590}
591
592std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW nRow,
593 const ScQueryEntry::Item& rItem)
594{
595 ScAddress aPos(nCol, nRow, mrTab.GetTab());
597
598 bool bMatch = rItem.maColor == color;
599 return std::pair<bool, bool>(bMatch, false);
600}
601
603 const ScQueryEntry::Item& rItem)
604{
605 ScAddress aPos(nCol, nRow, mrTab.GetTab());
607
608 bool bMatch = rItem.maColor == color;
609 return std::pair<bool, bool>(bMatch, false);
610}
611
612// To be called only if both isQueryByValue() and isQueryByString()
613// returned false and range lookup is wanted! In range lookup comparison
614// numbers are less than strings. Nothing else is compared.
616 const ScQueryEntry& rEntry,
617 const ScQueryEntry::Item& rItem)
618{
619 bool bTestEqual = false;
620
621 if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
622 && rEntry.eOp != SC_LESS_EQUAL)
623 return std::pair<bool, bool>(false, bTestEqual);
624
625 if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
626 && rEntry.eOp != SC_GREATER_EQUAL)
627 return std::pair<bool, bool>(false, bTestEqual);
628
629 if (rItem.meType == ScQueryEntry::ByString)
630 {
631 if (rCell.getType() == CELLTYPE_FORMULA
632 && rCell.getFormula()->GetErrCode() != FormulaError::NONE)
633 // Error values are compared as string.
634 return std::pair<bool, bool>(false, bTestEqual);
635
636 return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
637 }
638
639 return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
640}
641
642std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue& aCell,
643 const ScQueryEntry& rEntry, size_t nEntryIndex)
644{
645 std::pair<bool, bool> aRes(false, false);
646 const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
647 if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
648 {
649 if (rEntry.IsQueryByEmpty())
650 aRes.first = aCell.isEmpty();
651 else
652 {
653 assert(rEntry.IsQueryByNonEmpty());
654 aRes.first = !aCell.isEmpty();
655 }
656 return aRes;
657 }
658 if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
659 {
660 // If there are many items to query for (autofilter does this), then try to search
661 // efficiently in those items. So first search all the items of the relevant type,
662 // If that does not find anything, fall back to the generic code.
663 double value = 0;
664 bool valid = true;
665 // For ScQueryEntry::ByValue check that the cell either is a value or is a formula
666 // that has a value and is not an error (those are compared as strings). This
667 // is basically simplified isQueryByValue().
668 if (aCell.getType() == CELLTYPE_VALUE)
669 value = aCell.getDouble();
670 else if (aCell.getType() == CELLTYPE_FORMULA
671 && aCell.getFormula()->GetErrCode() != FormulaError::NONE
672 && aCell.getFormula()->IsValue())
673 {
674 value = aCell.getFormula()->GetValue();
675 }
676 else
677 valid = false;
678 if (valid)
679 {
680 if (rItems.size() >= 100)
681 {
682 // Sort, cache and binary search for the value in items.
683 // Don't bother comparing approximately.
684 if (mCachedSortedItemValues.size() <= nEntryIndex)
685 {
686 mCachedSortedItemValues.resize(nEntryIndex + 1);
687 auto& values = mCachedSortedItemValues[nEntryIndex];
688 values.reserve(rItems.size());
689 for (const auto& rItem : rItems)
690 if (rItem.meType == ScQueryEntry::ByValue)
691 values.push_back(rItem.mfVal);
692 std::sort(values.begin(), values.end());
693 }
694 auto& values = mCachedSortedItemValues[nEntryIndex];
695 auto it = std::lower_bound(values.begin(), values.end(), value);
696 if (it != values.end() && *it == value)
697 return std::make_pair(true, true);
698 }
699 else
700 {
701 for (const auto& rItem : rItems)
702 {
703 // For speed don't bother comparing approximately here, usually there either
704 // will be an exact match or it wouldn't match anyway.
705 if (rItem.meType == ScQueryEntry::ByValue && value == rItem.mfVal)
706 {
707 return std::make_pair(true, true);
708 }
709 }
710 }
711 }
712 }
713 const svl::SharedString* cellSharedString = nullptr;
714 std::optional<OUString> oCellString;
715 const bool bFastCompareByString = isFastCompareByString(rEntry);
716 if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
717 {
718 // The same as above but for strings. Try to optimize the case when
719 // it's a svl::SharedString comparison. That happens when SC_EQUAL is used
720 // and simple matching is used, see compareByString()
721 if (!oCellString)
722 oCellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
723 // Allow also checking ScQueryEntry::ByValue if the cell is not numeric,
724 // as in that case isQueryByNumeric() would be false and isQueryByString() would
725 // be true because of SC_EQUAL making isTextMatchOp() true.
727 // For ScQueryEntry::ByString check that the cell is represented by a shared string,
728 // which means it's either a string cell or a formula error. This is not as
729 // generous as isQueryByString() but it should be enough and better be safe.
730 if (cellSharedString != nullptr)
731 {
732 if (rItems.size() >= 100)
733 {
734 // Sort, cache and binary search for the string in items.
735 // Since each SharedString is identified by pointer value,
736 // sorting by pointer value is enough.
737 if (mCachedSortedItemStrings.size() <= nEntryIndex)
738 {
739 mCachedSortedItemStrings.resize(nEntryIndex + 1);
740 auto& values = mCachedSortedItemStrings[nEntryIndex];
741 values.reserve(rItems.size());
742 for (const auto& rItem : rItems)
743 {
744 if (rItem.meType == ScQueryEntry::ByString
745 || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
746 {
747 values.push_back(mrParam.bCaseSens
748 ? rItem.maString.getData()
749 : rItem.maString.getDataIgnoreCase());
750 }
751 }
752 std::sort(values.begin(), values.end());
753 }
754 auto& values = mCachedSortedItemStrings[nEntryIndex];
755 const rtl_uString* string = mrParam.bCaseSens
756 ? cellSharedString->getData()
757 : cellSharedString->getDataIgnoreCase();
758 auto it = std::lower_bound(values.begin(), values.end(), string);
759 if (it != values.end() && *it == string)
760 return std::make_pair(true, true);
761 }
762 else
763 {
764 for (const auto& rItem : rItems)
765 {
766 if ((rItem.meType == ScQueryEntry::ByString
767 || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
769 ? cellSharedString->getData() == rItem.maString.getData()
770 : cellSharedString->getDataIgnoreCase()
771 == rItem.maString.getDataIgnoreCase()))
772 {
773 return std::make_pair(true, true);
774 }
775 }
776 }
777 }
778 }
779 // Generic handling.
780 for (const auto& rItem : rItems)
781 {
782 if (rItem.meType == ScQueryEntry::ByTextColor)
783 {
784 std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
785 aRes.first |= aThisRes.first;
786 aRes.second |= aThisRes.second;
787 }
788 else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
789 {
790 std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
791 aRes.first |= aThisRes.first;
792 aRes.second |= aThisRes.second;
793 }
794 else if (isQueryByValue(rEntry.eOp, rItem.meType, aCell))
795 {
796 std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
797 aRes.first |= aThisRes.first;
798 aRes.second |= aThisRes.second;
799 }
800 else if (isQueryByString(rEntry.eOp, rItem.meType, aCell))
801 {
802 if (!oCellString)
803 oCellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
804 std::pair<bool, bool> aThisRes;
805 if (cellSharedString && bFastCompareByString) // fast
806 aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
807 else if (cellSharedString)
808 aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
809 else
810 aThisRes = compareByString(rEntry, rItem, nullptr, &*oCellString);
811 aRes.first |= aThisRes.first;
812 aRes.second |= aThisRes.second;
813 }
814 else if (mrParam.mbRangeLookup)
815 {
816 std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
817 aRes.first |= aThisRes.first;
818 aRes.second |= aThisRes.second;
819 }
820
821 if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
822 break;
823 }
824 return aRes;
825}
826
829{
830 if (!mrParam.GetEntry(0).bDoQuery)
831 return true;
832
833 tools::Long nPos = -1;
834 ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
835 for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
836 {
837 const ScQueryEntry& rEntry = *it;
838
839 // Short-circuit the test at the end of the loop - if this is SC_AND
840 // and the previous value is false, this value will not be needed.
841 // Disable this if pbTestEqualCondition is present as that one may get set
842 // even if the result is false (that also means pTest doesn't need to be
843 // handled here).
844 if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
845 && !mpPasst[nPos])
846 {
847 continue;
848 }
849
850 SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
851
852 // We can only handle one single direct query passed as a known pCell,
853 // subsequent queries have to obtain the cell.
854 ScRefCellValue aCell;
855 if (pCell && it == itBeg)
856 aCell = *pCell;
857 else if (pBlockPos)
858 { // hinted mdds access
859 aCell = const_cast<ScTable&>(mrTab).GetCellValue(
860 nCol, *pBlockPos->getBlockPosition(nCol), nRow);
861 }
862 else
863 aCell = mrTab.GetCellValue(nCol, nRow);
864
865 std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
866
867 if (nPos == -1)
868 {
869 nPos++;
870 mpPasst[nPos] = aRes.first;
871 mpTest[nPos] = aRes.second;
872 }
873 else
874 {
875 if (rEntry.eConnect == SC_AND)
876 {
877 mpPasst[nPos] = mpPasst[nPos] && aRes.first;
878 mpTest[nPos] = mpTest[nPos] && aRes.second;
879 }
880 else
881 {
882 nPos++;
883 mpPasst[nPos] = aRes.first;
884 mpTest[nPos] = aRes.second;
885 }
886 }
887 }
888
889 for (tools::Long j = 1; j <= nPos; j++)
890 {
891 mpPasst[0] = mpPasst[0] || mpPasst[j];
892 mpTest[0] = mpTest[0] || mpTest[j];
893 }
894
895 bool bRet = mpPasst[0];
898
899 return bRet;
900}
901
902/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int32 compareString(const OUString &s1, const OUString &s2) const
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
bool IsMatchWholeCell() const
Definition: docoptio.hxx:55
SC_DLLPUBLIC double RoundValueAsShown(double fVal, sal_uInt32 nFormat, const ScInterpreterContext *pContext=nullptr) const
Definition: documen4.cxx:627
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:467
SC_DLLPUBLIC const ScDocOptions & GetDocOptions() const
Definition: documen3.cxx:1936
double GetValue()
FormulaError GetErrCode()
static SC_DLLPUBLIC CollatorWrapper & GetCollator()
case-insensitive collator
Definition: global.cxx:1095
static SC_DLLPUBLIC ::utl::TransliterationWrapper & GetTransliteration()
Definition: global.cxx:1026
static std::optional< SvtSysLocale > oSysLocale
Definition: global.hxx:544
static OUString GetErrorString(FormulaError nErrNumber)
Definition: global.cxx:315
ScQueryEvaluator(ScDocument &rDoc, const ScTable &rTab, const ScQueryParam &rParam, const ScInterpreterContext *pContext=nullptr, bool *pTestEqualCondition=nullptr)
std::unique_ptr< bool[]> mpTestDynamic
bool maBool[nFixedBools]
const ScQueryParam & mrParam
OUString getCellString(const ScRefCellValue &rCell, SCROW nRow, SCCOL nCol, const svl::SharedString **sharedString)
bool isMatchWholeCell(ScQueryOp eOp) const
bool isTestWildOrRegExp(const ScQueryEntry &rEntry) const
std::pair< bool, bool > compareByString(const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem, const svl::SharedString *pValueSource1, const OUString *pValueSource2)
std::unique_ptr< bool[]> mpBoolDynamic
std::vector< std::vector< double > > mCachedSortedItemValues
void setupTransliteratorIfNeeded()
std::pair< bool, bool > compareByTextColor(SCCOL nCol, SCROW nRow, const ScQueryEntry::Item &rItem)
static bool isTextMatchOp(ScQueryOp eOp)
std::unordered_map< FormulaError, svl::SharedString > mCachedSharedErrorStrings
sal_uInt32 getNumFmt(SCCOL nCol, SCROW nRow)
static bool isPartialTextMatchOp(ScQueryOp eOp)
const ScInterpreterContext * mpContext
std::pair< bool, bool > processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue &aCell, const ScQueryEntry &rEntry, size_t nEntryIndex)
const bool mbCaseSensitive
static bool isQueryByValueForCell(const ScRefCellValue &rCell)
svl::SharedStringPool & mrStrPool
const bool mbMatchWholeCell
ScDocument & mrDoc
utl::TransliterationWrapper * mpTransliteration
void setupCollatorIfNeeded()
static bool isQueryByValue(ScQueryOp eOp, ScQueryEntry::QueryType eType, const ScRefCellValue &rCell)
bool maTest[nFixedBools]
static std::pair< bool, bool > compareByRangeLookup(const ScRefCellValue &rCell, const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem)
CollatorWrapper * mpCollator
const SCSIZE mnEntryCount
bool ValidQuery(SCROW nRow, const ScRefCellValue *pCell=nullptr, sc::TableColumnBlockPositionSet *pBlockPos=nullptr)
bool isRealWildOrRegExp(const ScQueryEntry &rEntry) const
static bool isQueryByString(ScQueryOp eOp, ScQueryEntry::QueryType eType, const ScRefCellValue &rCell)
const ScTable & mrTab
static constexpr SCSIZE nFixedBools
std::vector< std::vector< const rtl_uString * > > mCachedSortedItemStrings
bool isFastCompareByString(const ScQueryEntry &rEntry) const
static bool isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
std::pair< bool, bool > compareByValue(const ScRefCellValue &rCell, SCCOL nCol, SCROW nRow, const ScQueryEntry &rEntry, const ScQueryEntry::Item &rItem)
std::pair< bool, bool > compareByBackgroundColor(SCCOL nCol, SCROW nRow, const ScQueryEntry::Item &rItem)
ScRefCellValue GetCellValue(SCCOL nCol, sc::ColumnBlockPosition &rBlockPos, SCROW nRow)
Definition: table2.cxx:2030
sal_uInt32 GetNumberFormat(const ScInterpreterContext &rContext, const ScAddress &rPos) const
Definition: table2.cxx:2254
SCTAB GetTab() const
Definition: table.hxx:299
Color GetCellBackgroundColor(ScAddress aPos) const
Definition: table5.cxx:1011
Color GetCellTextColor(ScAddress aPos) const
Definition: table5.cxx:1052
const SvNumberformat * GetEntry(sal_uInt32 nKey) const
SvNumFormatType GetType() const
Set of column block positions only for one table.
ColumnBlockPosition * getBlockPosition(SCCOL nCol)
SharedString intern(const OUString &rStr)
const OUString & getString() const
rtl_uString * getDataIgnoreCase()
bool isEmpty() const
rtl_uString * getData()
static const SharedString & getEmptyString()
bool SearchForward(const OUString &rStr, sal_Int32 *pStart, sal_Int32 *pEnd, css::util::SearchResult *pRes=nullptr)
bool SearchBackward(const OUString &rStr, sal_Int32 *pStart, sal_Int32 *pEnd, css::util::SearchResult *pRes=nullptr)
OUString transliterate(const OUString &rStr, sal_Int32 nStart, sal_Int32 nLen) const
FormulaError
DocumentType eType
@ SC_AND
Definition: global.hxx:855
@ CELLTYPE_STRING
Definition: global.hxx:275
@ CELLTYPE_FORMULA
Definition: global.hxx:276
@ CELLTYPE_VALUE
Definition: global.hxx:274
ScQueryOp
Definition: global.hxx:834
@ SC_DOES_NOT_CONTAIN
Definition: global.hxx:846
@ SC_LESS_EQUAL
Definition: global.hxx:838
@ SC_LESS
Definition: global.hxx:836
@ SC_GREATER_EQUAL
Definition: global.hxx:839
@ SC_CONTAINS
Definition: global.hxx:845
@ SC_ENDS_WITH
Definition: global.hxx:849
@ SC_DOES_NOT_END_WITH
Definition: global.hxx:850
@ SC_BEGINS_WITH
Definition: global.hxx:847
@ SC_GREATER
Definition: global.hxx:837
@ SC_EQUAL
Definition: global.hxx:835
@ SC_NOT_EQUAL
Definition: global.hxx:840
@ SC_DOES_NOT_BEGIN_WITH
Definition: global.hxx:848
sal_Int32 nIndex
sal_uInt16 nPos
long Long
std::vector< char * > values
ParserContextSharedPtr mpContext
SvNumberFormatter * GetFormatTable() const
svl::SharedString maString
Definition: queryentry.hxx:49
Each instance of this struct represents a single filtering criteria.
Definition: queryentry.hxx:34
SCCOLROW nField
Definition: queryentry.hxx:61
bool IsQueryByNonEmpty() const
Definition: queryentry.cxx:109
ScQueryConnect eConnect
Definition: queryentry.hxx:63
std::vector< Item > QueryItemsType
Definition: queryentry.hxx:58
bool IsQueryByEmpty() const
Definition: queryentry.cxx:87
utl::TextSearch * GetSearchTextPtr(utl::SearchParam::SearchType eSearchType, bool bCaseSens, bool bWildMatchSel) const
creates pSearchParam and pSearchText if necessary
Definition: queryentry.cxx:194
ScQueryOp eOp
Definition: queryentry.hxx:62
QueryItemsType & GetQueryItems()
Definition: queryentry.hxx:75
const_iterator begin() const
Definition: queryparam.cxx:57
EntriesType::const_iterator const_iterator
Definition: queryparam.hxx:71
SC_DLLPUBLIC const ScQueryEntry & GetEntry(SCSIZE n) const
Definition: queryparam.cxx:116
utl::SearchParam::SearchType eSearchType
Definition: queryparam.hxx:43
const_iterator end() const
Definition: queryparam.cxx:62
bool mbRangeLookup
for spreadsheet functions like MATCH, LOOKUP, HLOOKUP, VLOOKUP
Definition: queryparam.hxx:49
This is very similar to ScCellValue, except that it references the original value instead of copying ...
Definition: cellvalue.hxx:108
ScFormulaCell * getFormula() const
Definition: cellvalue.hxx:137
double getDouble() const
Definition: cellvalue.hxx:134
bool isEmpty() const
Definition: cellvalue.cxx:667
bool hasNumeric() const
Definition: cellvalue.cxx:619
const svl::SharedString * getSharedString() const
Definition: cellvalue.hxx:135
bool hasString() const
Definition: cellvalue.cxx:614
CellType getType() const
Definition: cellvalue.hxx:133
sal_Int16 SCCOL
Definition: types.hxx:21
sal_Int32 SCROW
Definition: types.hxx:17
size_t pos
SvNumFormatType
#define SV_COUNTRY_LANGUAGE_OFFSET
constexpr sal_uInt32 NUMBERFORMAT_ENTRY_NOT_FOUND