#include "lc_global.h" #include "lc_filter.h" #include "lc_string.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()) : lcstrcasestr(String, FilterPart.String.c_str()) != nullptr; 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); }