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());
596 Color color;
597 bool bHasConditionalColor = false;
598 // Text color can be set via conditional formatting - check that first
599 const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
600 if (pPattern)
601 {
602 if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
603 {
604 const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
605 const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
606 color = pColor->GetValue();
607 bHasConditionalColor = true;
608 }
609 }
610
611 if (!bHasConditionalColor)
612 {
613 const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
614 color = pColor->GetValue();
615 }
616
617 bool bMatch = rItem.maColor == color;
618 return std::pair<bool, bool>(bMatch, false);
619}
620
622 const ScQueryEntry::Item& rItem)
623{
624 ScAddress aPos(nCol, nRow, mrTab.GetTab());
625 Color color;
626
627 // Background color can be set via conditional formatting - check that first
628 bool bHasConditionalColor = false;
629 const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
630 if (pPattern)
631 {
632 if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
633 {
634 const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
635 const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
636 color = pBackgroundColor->GetColor();
637 bHasConditionalColor = true;
638 }
639 }
640
641 ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, mrTab.GetTab());
642 if (pCondFormat)
643 {
644 for (size_t i = 0; i < pCondFormat->size(); i++)
645 {
646 auto aEntry = pCondFormat->GetEntry(i);
647 if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
648 {
649 const ScColorScaleFormat* pColFormat
650 = static_cast<const ScColorScaleFormat*>(aEntry);
651 color = *(pColFormat->GetColor(aPos));
652 bHasConditionalColor = true;
653 }
654 }
655 }
656
657 if (!bHasConditionalColor)
658 {
659 const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
660 color = pBrush->GetColor();
661 }
662
663 bool bMatch = rItem.maColor == color;
664 return std::pair<bool, bool>(bMatch, false);
665}
666
667// To be called only if both isQueryByValue() and isQueryByString()
668// returned false and range lookup is wanted! In range lookup comparison
669// numbers are less than strings. Nothing else is compared.
671 const ScQueryEntry& rEntry,
672 const ScQueryEntry::Item& rItem)
673{
674 bool bTestEqual = false;
675
676 if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
677 && rEntry.eOp != SC_LESS_EQUAL)
678 return std::pair<bool, bool>(false, bTestEqual);
679
680 if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
681 && rEntry.eOp != SC_GREATER_EQUAL)
682 return std::pair<bool, bool>(false, bTestEqual);
683
684 if (rItem.meType == ScQueryEntry::ByString)
685 {
686 if (rCell.getType() == CELLTYPE_FORMULA
687 && rCell.getFormula()->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
697std::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.getType() == CELLTYPE_VALUE)
724 value = aCell.getDouble();
725 else if (aCell.getType() == CELLTYPE_FORMULA
726 && aCell.getFormula()->GetErrCode() != FormulaError::NONE
727 && aCell.getFormula()->IsValue())
728 {
729 value = aCell.getFormula()->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 std::optional<OUString> oCellString;
770 const bool bFastCompareByString = isFastCompareByString(rEntry);
771 if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
772 {
773 // The same as above but for strings. Try to optimize the case when
774 // it's a svl::SharedString comparison. That happens when SC_EQUAL is used
775 // and simple matching is used, see compareByString()
776 if (!oCellString)
777 oCellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
778 // Allow also checking ScQueryEntry::ByValue if the cell is not numeric,
779 // as in that case isQueryByNumeric() would be false and isQueryByString() would
780 // be true because of SC_EQUAL making isTextMatchOp() true.
782 // For ScQueryEntry::ByString check that the cell is represented by a shared string,
783 // which means it's either a string cell or a formula error. This is not as
784 // generous as isQueryByString() but it should be enough and better be safe.
785 if (cellSharedString != nullptr)
786 {
787 if (rItems.size() >= 100)
788 {
789 // Sort, cache and binary search for the string in items.
790 // Since each SharedString is identified by pointer value,
791 // sorting by pointer value is enough.
792 if (mCachedSortedItemStrings.size() <= nEntryIndex)
793 {
794 mCachedSortedItemStrings.resize(nEntryIndex + 1);
795 auto& values = mCachedSortedItemStrings[nEntryIndex];
796 values.reserve(rItems.size());
797 for (const auto& rItem : rItems)
798 {
799 if (rItem.meType == ScQueryEntry::ByString
800 || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
801 {
802 values.push_back(mrParam.bCaseSens
803 ? rItem.maString.getData()
804 : rItem.maString.getDataIgnoreCase());
805 }
806 }
807 std::sort(values.begin(), values.end());
808 }
809 auto& values = mCachedSortedItemStrings[nEntryIndex];
810 const rtl_uString* string = mrParam.bCaseSens
811 ? cellSharedString->getData()
812 : cellSharedString->getDataIgnoreCase();
813 auto it = std::lower_bound(values.begin(), values.end(), string);
814 if (it != values.end() && *it == string)
815 return std::make_pair(true, true);
816 }
817 else
818 {
819 for (const auto& rItem : rItems)
820 {
821 if ((rItem.meType == ScQueryEntry::ByString
822 || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
824 ? cellSharedString->getData() == rItem.maString.getData()
825 : cellSharedString->getDataIgnoreCase()
826 == rItem.maString.getDataIgnoreCase()))
827 {
828 return std::make_pair(true, true);
829 }
830 }
831 }
832 }
833 }
834 // Generic handling.
835 for (const auto& rItem : rItems)
836 {
837 if (rItem.meType == ScQueryEntry::ByTextColor)
838 {
839 std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
840 aRes.first |= aThisRes.first;
841 aRes.second |= aThisRes.second;
842 }
843 else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
844 {
845 std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
846 aRes.first |= aThisRes.first;
847 aRes.second |= aThisRes.second;
848 }
849 else if (isQueryByValue(rEntry.eOp, rItem.meType, aCell))
850 {
851 std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
852 aRes.first |= aThisRes.first;
853 aRes.second |= aThisRes.second;
854 }
855 else if (isQueryByString(rEntry.eOp, rItem.meType, aCell))
856 {
857 if (!oCellString)
858 oCellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
859 std::pair<bool, bool> aThisRes;
860 if (cellSharedString && bFastCompareByString) // fast
861 aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
862 else if (cellSharedString)
863 aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
864 else
865 aThisRes = compareByString(rEntry, rItem, nullptr, &*oCellString);
866 aRes.first |= aThisRes.first;
867 aRes.second |= aThisRes.second;
868 }
869 else if (mrParam.mbRangeLookup)
870 {
871 std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
872 aRes.first |= aThisRes.first;
873 aRes.second |= aThisRes.second;
874 }
875
876 if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
877 break;
878 }
879 return aRes;
880}
881
884{
885 if (!mrParam.GetEntry(0).bDoQuery)
886 return true;
887
888 tools::Long nPos = -1;
889 ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
890 for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
891 {
892 const ScQueryEntry& rEntry = *it;
893
894 // Short-circuit the test at the end of the loop - if this is SC_AND
895 // and the previous value is false, this value will not be needed.
896 // Disable this if pbTestEqualCondition is present as that one may get set
897 // even if the result is false (that also means pTest doesn't need to be
898 // handled here).
899 if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
900 && !mpPasst[nPos])
901 {
902 continue;
903 }
904
905 SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
906
907 // We can only handle one single direct query passed as a known pCell,
908 // subsequent queries have to obtain the cell.
909 ScRefCellValue aCell;
910 if (pCell && it == itBeg)
911 aCell = *pCell;
912 else if (pBlockPos)
913 { // hinted mdds access
914 aCell = const_cast<ScTable&>(mrTab).GetCellValue(
915 nCol, *pBlockPos->getBlockPosition(nCol), nRow);
916 }
917 else
918 aCell = mrTab.GetCellValue(nCol, nRow);
919
920 std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
921
922 if (nPos == -1)
923 {
924 nPos++;
925 mpPasst[nPos] = aRes.first;
926 mpTest[nPos] = aRes.second;
927 }
928 else
929 {
930 if (rEntry.eConnect == SC_AND)
931 {
932 mpPasst[nPos] = mpPasst[nPos] && aRes.first;
933 mpTest[nPos] = mpTest[nPos] && aRes.second;
934 }
935 else
936 {
937 nPos++;
938 mpPasst[nPos] = aRes.first;
939 mpTest[nPos] = aRes.second;
940 }
941 }
942 }
943
944 for (tools::Long j = 1; j <= nPos; j++)
945 {
946 mpPasst[0] = mpPasst[0] || mpPasst[j];
947 mpTest[0] = mpTest[0] || mpTest[j];
948 }
949
950 bool bRet = mpPasst[0];
953
954 return bRet;
955}
956
957/* 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
std::optional< Color > GetColor(const ScAddress &rAddr) const
Definition: colorscale.cxx:594
const ScFormatEntry * GetEntry(sal_uInt16 nPos) const
Definition: conditio.cxx:1796
size_t size() const
Definition: conditio.cxx:1782
bool IsMatchWholeCell() const
Definition: docoptio.hxx:55
SC_DLLPUBLIC double RoundValueAsShown(double fVal, sal_uInt32 nFormat, const ScInterpreterContext *pContext=nullptr) const
Definition: documen4.cxx:634
SC_DLLPUBLIC ScConditionalFormat * GetCondFormat(SCCOL nCol, SCROW nRow, SCTAB nTab) const
Definition: documen4.cxx:844
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:467
SC_DLLPUBLIC const SfxPoolItem * GetAttr(SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich) const
Definition: document.cxx:4789
SC_DLLPUBLIC const SfxItemSet * GetCondResult(SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue *pCell=nullptr) const
Definition: documen4.cxx:798
SC_DLLPUBLIC const ScPatternAttr * GetPattern(SCCOL nCol, SCROW nRow, SCTAB nTab) const
Definition: document.cxx:4824
SC_DLLPUBLIC const ScDocOptions & GetDocOptions() const
Definition: documen3.cxx:1952
double GetValue()
FormulaError GetErrCode()
static SC_DLLPUBLIC CollatorWrapper & GetCollator()
case-insensitive collator
Definition: global.cxx:1093
static SC_DLLPUBLIC ::utl::TransliterationWrapper & GetTransliteration()
Definition: global.cxx:1024
static std::optional< SvtSysLocale > oSysLocale
Definition: global.hxx:543
static OUString GetErrorString(FormulaError nErrNumber)
Definition: global.cxx:315
const SfxPoolItem & GetItem(sal_uInt16 nWhichP) const
Definition: patattr.hxx:72
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:2029
sal_uInt32 GetNumberFormat(const ScInterpreterContext &rContext, const ScAddress &rPos) const
Definition: table2.cxx:2253
SCTAB GetTab() const
Definition: table.hxx:292
const SvNumberformat * GetEntry(sal_uInt32 nKey) const
SvNumFormatType GetType() const
const Color & GetColor() const
const Color & GetValue() 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:854
@ CELLTYPE_STRING
Definition: global.hxx:274
@ CELLTYPE_FORMULA
Definition: global.hxx:275
@ CELLTYPE_VALUE
Definition: global.hxx:273
ScQueryOp
Definition: global.hxx:833
@ SC_DOES_NOT_CONTAIN
Definition: global.hxx:845
@ SC_LESS_EQUAL
Definition: global.hxx:837
@ SC_LESS
Definition: global.hxx:835
@ SC_GREATER_EQUAL
Definition: global.hxx:838
@ SC_CONTAINS
Definition: global.hxx:844
@ SC_ENDS_WITH
Definition: global.hxx:848
@ SC_DOES_NOT_END_WITH
Definition: global.hxx:849
@ SC_BEGINS_WITH
Definition: global.hxx:846
@ SC_GREATER
Definition: global.hxx:836
@ SC_EQUAL
Definition: global.hxx:834
@ SC_NOT_EQUAL
Definition: global.hxx:839
@ SC_DOES_NOT_BEGIN_WITH
Definition: global.hxx:847
sal_Int32 nIndex
sal_uInt16 nPos
int i
long Long
std::vector< char * > values
constexpr TypedWhichId< SvxColorItem > ATTR_FONT_COLOR(109)
constexpr TypedWhichId< SvxBrushItem > ATTR_BACKGROUND(148)
constexpr TypedWhichId< ScCondFormatItem > ATTR_CONDITIONAL(154)
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