#include "matcher.h" #include #include #include #include "logger.h" #include "matchable.h" #include "matcherexception.h" #include "scopemeasure.h" #include "utils.h" namespace newsboat { Matcher::Matcher() {} Matcher::Matcher(const std::string& expr) : exp(expr) { parse(expr); } std::string Matcher::get_expression() { return exp; } bool Matcher::parse(const std::string& expr) { ScopeMeasure measurer("Matcher::parse"); errmsg = ""; bool b = p.parse_string(expr); if (b) { exp = expr; } else { errmsg = utils::wstr2str(p.get_error()); } LOG(Level::DEBUG, "Matcher::parse: parsing `%s' succeeded: %d", expr, b ? 1 : 0); return b; } bool Matcher::matches(Matchable* item) { /* * with this method, every class that is derived from Matchable can be * matched against a filter expression that was previously passed to the * class with the parse() method. * * This makes it easy to use the Matcher virtually everywhere, since C++ * allows multiple inheritance (i.e. deriving from Matchable can even be * used in class hierarchies), and deriving from Matchable means that * you only have to implement the method attribute_value(). * * The whole matching code is speed-critical, as the matching happens on * a lot of different occasions, and slow matching can be easily * measured (and felt by the user) on slow computers with a lot of items * to match. */ bool retval = false; if (item) { ScopeMeasure m1("Matcher::matches"); retval = matches_r(p.get_root(), item); } return retval; } std::string get_attr_or_throw(Matchable* item, const std::string& attr_name) { const auto attr = item->attribute_value(attr_name); if (!attr.has_value()) { LOG(Level::WARN, "Matcher::matches: attribute %s is not available", attr_name); throw MatcherException(MatcherException::Type::ATTRIB_UNAVAIL, attr_name); } return attr.value(); } bool Matcher::matchop_lt(expression* e, Matchable* item) { const int ilit = string_to_num(e->literal); const auto attr = get_attr_or_throw(item, e->name); const int iatt = string_to_num(attr); return iatt < ilit; } bool Matcher::matchop_between(expression* e, Matchable* item) { const auto attr = get_attr_or_throw(item, e->name); const int att = string_to_num(attr); const std::vector lit = utils::tokenize(e->literal, ":"); if (lit.size() < 2) { return false; } int i1 = string_to_num(lit[0]); int i2 = string_to_num(lit[1]); if (i1 > i2) { std::swap(i1, i2); } return (att >= i1 && att <= i2); } bool Matcher::matchop_gt(expression* e, Matchable* item) { const auto attr = get_attr_or_throw(item, e->name); const int iatt = string_to_num(attr); const int ilit = string_to_num(e->literal); return iatt > ilit; } bool Matcher::matchop_rxeq(expression* e, Matchable* item) { const auto attr = get_attr_or_throw(item, e->name); if (!e->regex) { e->regex = new regex_t; int err; if ((err = regcomp(e->regex, e->literal.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0) { delete e->regex; e->regex = nullptr; char buf[1024]; regerror(err, e->regex, buf, sizeof(buf)); throw MatcherException( MatcherException::Type::INVALID_REGEX, e->literal, buf); } } if (regexec(e->regex, attr.c_str(), 0, nullptr, 0) == 0) { return true; } return false; } bool Matcher::matchop_cont(expression* e, Matchable* item) { const auto attr = get_attr_or_throw(item, e->name); const std::vector elements = utils::tokenize(attr, " "); const std::string literal = e->literal; for (const auto& elem : elements) { if (literal == elem) { return true; } } return false; } bool Matcher::matchop_eq(expression* e, Matchable* item) { const auto attr = get_attr_or_throw(item, e->name); return (attr == e->literal); } bool Matcher::matches_r(expression* e, Matchable* item) { if (e) { switch (e->op) { /* the operator "and" and "or" simply connect two different * subexpressions */ case LOGOP_AND: // short-circuit evaluation in C -> short circuit evaluation in the filter language return matches_r(e->l, item) && matches_r(e->r, item); case LOGOP_OR: return matches_r(e->l, item) || matches_r(e->r, item); // same here /* while the other operators connect an attribute with a value */ case MATCHOP_EQ: return matchop_eq(e, item); case MATCHOP_NE: return !matchop_eq(e, item); case MATCHOP_LT: return matchop_lt(e, item); case MATCHOP_BETWEEN: return matchop_between(e, item); case MATCHOP_GT: return matchop_gt(e, item); case MATCHOP_LE: return !matchop_gt(e, item); case MATCHOP_GE: return !matchop_lt(e, item); case MATCHOP_RXEQ: return matchop_rxeq(e, item); case MATCHOP_RXNE: return !matchop_rxeq(e, item); case MATCHOP_CONTAINS: return matchop_cont(e, item); case MATCHOP_CONTAINSNOT: return !matchop_cont(e, item); } return false; } else { return true; // shouldn't happen } } std::string Matcher::get_parse_error() { return errmsg; } int Matcher::string_to_num(const std::string& number) { int result = 0; std::istringstream(number) >> result; return result; } } // namespace newsboat