File indexing completed on 2024-04-14 03:54:08

0001 /*
0002     SPDX-FileCopyrightText: 2018 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "tagsfilterchecker.h"
0008 
0009 #include <knewstuffcore_debug.h>
0010 
0011 #include <QMap>
0012 
0013 namespace KNSCore
0014 {
0015 class TagsFilterCheckerPrivate
0016 {
0017 public:
0018     TagsFilterCheckerPrivate()
0019     {
0020     }
0021     ~TagsFilterCheckerPrivate()
0022     {
0023         qDeleteAll(validators);
0024     }
0025     class Validator;
0026     // If people start using a LOT of validators (>20ish), we can always change it, but
0027     // for now it seems reasonable that QMap is better than QHash here...
0028     QMap<QString, Validator *> validators;
0029 
0030     class Validator
0031     {
0032     public:
0033         Validator(const QString &tag, const QString &value)
0034             : m_tag(tag)
0035         {
0036             if (!value.isNull()) {
0037                 m_acceptedValues << value;
0038             }
0039         }
0040         virtual ~Validator()
0041         {
0042         }
0043         virtual bool filterAccepts(const QString &tag, const QString &value) = 0;
0044 
0045     protected:
0046         friend class TagsFilterCheckerPrivate;
0047         QString m_tag;
0048         QStringList m_acceptedValues;
0049     };
0050 
0051     // Will only accept entries which have one of the accepted values set for the tag key
0052     class EqualityValidator : public Validator
0053     {
0054     public:
0055         EqualityValidator(const QString &tag, const QString &value)
0056             : Validator(tag, value)
0057         {
0058         }
0059         ~EqualityValidator() override
0060         {
0061         }
0062         bool filterAccepts(const QString &tag, const QString &value) override
0063         {
0064             bool result = true;
0065             if (tag == m_tag && !m_acceptedValues.contains(value)) {
0066                 qCDebug(KNEWSTUFFCORE) << "Item excluded by filter on" << m_tag << "because" << value << "was not included in" << m_acceptedValues;
0067                 result = false;
0068             }
0069             return result;
0070         }
0071     };
0072 
0073     // Will only accept entries which have none of the values set for the tag key
0074     class InequalityValidator : public Validator
0075     {
0076     public:
0077         InequalityValidator(const QString &tag, const QString &value)
0078             : Validator(tag, value)
0079         {
0080         }
0081         ~InequalityValidator() override
0082         {
0083         }
0084         bool filterAccepts(const QString &tag, const QString &value) override
0085         {
0086             bool result = true;
0087             if (tag == m_tag && m_acceptedValues.contains(value)) {
0088                 qCDebug(KNEWSTUFFCORE) << "Item excluded by filter on" << m_tag << "because" << value << "was included in" << m_acceptedValues;
0089                 result = false;
0090             }
0091             return result;
0092         }
0093     };
0094 
0095     void addValidator(const QString &filter)
0096     {
0097         int pos = 0;
0098         if ((pos = filter.indexOf(QLatin1String("=="))) > -1) {
0099             QString tag = filter.left(pos);
0100             QString value = filter.mid(tag.length() + 2);
0101             Validator *val = validators.value(tag, nullptr);
0102             if (!val) {
0103                 val = new EqualityValidator(tag, QString());
0104                 validators.insert(tag, val);
0105             }
0106             val->m_acceptedValues << value;
0107             qCDebug(KNEWSTUFFCORE) << "Created EqualityValidator for tag" << tag << "with value" << value;
0108         } else if ((pos = filter.indexOf(QLatin1String("!="))) > -1) {
0109             QString tag = filter.left(pos);
0110             QString value = filter.mid(tag.length() + 2);
0111             Validator *val = validators.value(tag, nullptr);
0112             if (!val) {
0113                 val = new InequalityValidator(tag, QString());
0114                 validators.insert(tag, val);
0115             }
0116             val->m_acceptedValues << value;
0117             qCDebug(KNEWSTUFFCORE) << "Created InequalityValidator for tag" << tag << "with value" << value;
0118         } else {
0119             qCDebug(KNEWSTUFFCORE) << "Critical error attempting to create tag filter validators. The filter is defined as" << filter
0120                                    << "which is not in the accepted formats key==value or key!=value";
0121         }
0122     }
0123 };
0124 
0125 TagsFilterChecker::TagsFilterChecker(const QStringList &tagFilter)
0126     : d(new TagsFilterCheckerPrivate)
0127 {
0128     for (const QString &filter : tagFilter) {
0129         d->addValidator(filter);
0130     }
0131 }
0132 
0133 TagsFilterChecker::~TagsFilterChecker() = default;
0134 
0135 bool TagsFilterChecker::filterAccepts(const QStringList &tags)
0136 {
0137     // if any tag in the content matches any of the tag filters, skip this entry
0138     qCDebug(KNEWSTUFFCORE) << "Checking tags list" << tags << "against validators with keys" << d->validators.keys();
0139     for (const QString &tag : tags) {
0140         if (tag.isEmpty()) {
0141             // This happens when you do a split on an empty string (not an empty list, a list with one empty element... because reasons).
0142             // Also handy for other things, i guess, though, so let's just catch it here.
0143             continue;
0144         }
0145         QStringList current = tag.split(QLatin1Char('='));
0146         if (current.length() > 2) {
0147             qCDebug(KNEWSTUFFCORE) << "Critical error attempting to filter tags. Entry has tag defined as" << tag
0148                                    << "which is not in the format \"key=value\" or \"key\".";
0149             return false;
0150         } else if (current.length() == 1) {
0151             // If the tag is defined simply as a key, we give it the value "1", just to make our filtering work simpler
0152             current << QStringLiteral("1");
0153         }
0154         QMap<QString, TagsFilterCheckerPrivate::Validator *>::const_iterator i = d->validators.constBegin();
0155         while (i != d->validators.constEnd()) {
0156             if (!i.value()->filterAccepts(current.at(0), current.at(1))) {
0157                 return false;
0158             }
0159             ++i;
0160         }
0161     }
0162     // If we have arrived here, nothing has filtered the entry
0163     // out (by being either incorrectly tagged or a filter rejecting
0164     // it), and consequently it is an acceptable entry.
0165     return true;
0166 }
0167 
0168 }