Files
leocad/common/lc_filter.cpp
T
2025-07-24 16:48:04 -07:00

289 lines
7.4 KiB
C++

#include "lc_global.h"
#include "lc_filter.h"
lcFilter::lcFilter(const std::string_view FilterString)
{
size_t TokenStart = std::string_view::npos;
FilterOp Op = FilterOp::Or;
bool InQuotes = false;
auto EndToken=[this, FilterString, &Op, &TokenStart, &InQuotes](size_t Index)
{
if (!InQuotes && TokenStart != std::string_view::npos)
{
std::string_view Token = FilterString.substr(TokenStart, Index - TokenStart);
if (Token == "AND")
Op = FilterOp::And;
else if (Token == "OR")
Op = FilterOp::Or;
else
{
mFilterParts.emplace_back(FilterPart{Op, Token.find_first_of("*?") != std::string_view::npos, std::string(Token)});
Op = FilterOp::And;
}
TokenStart = std::string_view::npos;
}
};
for (size_t Index = 0; Index < FilterString.size(); Index++)
{
switch (FilterString[Index])
{
case ' ':
EndToken(Index);
break;
case '-':
if (!InQuotes && TokenStart == std::string_view::npos)
{
if (Op == FilterOp::And)
Op = FilterOp::AndNot;
else if (Op == FilterOp::Or)
Op = FilterOp::OrNot;
}
break;
case '\"':
if (InQuotes)
{
std::string_view Token = FilterString.substr(TokenStart + 1, Index - TokenStart - 1);
if (!Token.empty())
mFilterParts.emplace_back(FilterPart{Op, Token.find_first_of("*?") != std::string_view::npos, std::string(Token)});
TokenStart = std::string_view::npos;
Op = FilterOp::And;
}
else
{
if (TokenStart == std::string_view::npos)
TokenStart = Index;
}
InQuotes = !InQuotes;
break;
default:
if (TokenStart == std::string_view::npos)
TokenStart = Index;
break;
}
}
EndToken(FilterString.size());
}
bool lcFilter::Match(const char* String) const
{
bool CurrentMatch = mFilterParts.empty();
for (const FilterPart& FilterPart : mFilterParts)
{
bool Match = FilterPart.Wildcard ? FastWildCompare(String, FilterPart.String.c_str()) : strcasestr(String, FilterPart.String.c_str());
switch (FilterPart.Op)
{
case FilterOp::And:
CurrentMatch &= Match;
break;
case FilterOp::AndNot:
CurrentMatch &= !Match;
break;
case FilterOp::Or:
CurrentMatch |= Match;
break;
case FilterOp::OrNot:
CurrentMatch |= !Match;
break;
}
}
return CurrentMatch;
}
// Copyright 2018 IBM Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Compares two text strings. Accepts '?' as a single-character wildcard.
// For each '*' wildcard, seeks out a matching sequence of any characters
// beyond it. Otherwise compares the strings a character at a time.
//
bool lcFilter::FastWildCompare(const char* Tame, const char* Wild)
{
const char* WildSequence; // Points to prospective wild string match after '*'
const char* TameSequence; // Points to prospective tame string match
auto NotEquals=[](char a, char b)
{
// Lowercase the characters to be compared.
if (a >= 'A' && a <= 'Z')
a += ('a' - 'A');
if (b >= 'A' && b <= 'Z')
b += ('a' - 'A');
return a != b;
};
// Find a first wildcard, if one exists, and the beginning of any
// prospectively matching sequence after it.
do
{
// Check for the end from the start. Get out fast, if possible.
if (!*Tame)
{
if (*Wild)
{
while (*(Wild++) == '*')
{
if (!(*Wild))
{
return true; // "ab" matches "ab*".
}
}
return false; // "abcd" doesn't match "abc".
}
else
{
return true; // "abc" matches "abc".
}
}
else if (*Wild == '*')
{
// Got wild: set up for the second loop and skip on down there.
while (*(++Wild) == '*')
{
continue;
}
if (!*Wild)
{
return true; // "abc*" matches "abcd".
}
// Search for the next prospective match.
if (*Wild != '?')
{
while (NotEquals(*Wild, *Tame))
{
if (!*(++Tame))
{
return false; // "a*bc" doesn't match "ab".
}
}
}
// Keep fallback positions for retry in case of incomplete match.
WildSequence = Wild;
TameSequence = Tame;
break;
}
else if (NotEquals(*Wild, *Tame) && *Wild != '?')
{
return false; // "abc" doesn't match "abd".
}
++Wild; // Everything's a match, so far.
++Tame;
} while (true);
// Find any further wildcards and any further matching sequences.
do
{
if (*Wild == '*')
{
// Got wild again.
while (*(++Wild) == '*')
{
continue;
}
if (!*Wild)
{
return true; // "ab*c*" matches "abcd".
}
if (!*Tame)
{
return false; // "*bcd*" doesn't match "abc".
}
// Search for the next prospective match.
if (*Wild != '?')
{
while (NotEquals(*Wild, *Tame))
{
if (!*(++Tame))
{
return false; // "a*b*c" doesn't match "ab".
}
}
}
// Keep the new fallback positions.
WildSequence = Wild;
TameSequence = Tame;
}
else if (NotEquals(*Wild, *Tame) && *Wild != '?')
{
// The equivalent portion of the upper loop is really simple.
if (!*Tame)
{
return false; // "*bcd" doesn't match "abc".
}
// A fine time for questions.
while (*WildSequence == '?')
{
++WildSequence;
++TameSequence;
}
Wild = WildSequence;
// Fall back, but never so far again.
while (NotEquals(*Wild, *(++TameSequence)))
{
if (!*TameSequence)
{
return false; // "*a*b" doesn't match "ac".
}
}
Tame = TameSequence;
}
// Another check for the end, at the end.
if (!*Tame)
{
if (!*Wild)
{
return true; // "*bc" matches "abc".
}
else
{
return false; // "*bc" doesn't match "abcd".
}
}
++Wild; // Everything's still a match.
++Tame;
} while (true);
}