1 | /* |
2 | SPDX-FileCopyrightText: 2018 Dan Leinir Turthra Jensen <admin@leinir.dk> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.1-or-later |
5 | */ |
6 | |
7 | #include "tagsfilterchecker.h" |
8 | |
9 | #include <knewstuffcore_debug.h> |
10 | |
11 | #include <QMap> |
12 | |
13 | namespace KNSCore |
14 | { |
15 | class TagsFilterCheckerPrivate |
16 | { |
17 | public: |
18 | TagsFilterCheckerPrivate() |
19 | { |
20 | } |
21 | ~TagsFilterCheckerPrivate() |
22 | { |
23 | qDeleteAll(c: validators); |
24 | } |
25 | class Validator; |
26 | // If people start using a LOT of validators (>20ish), we can always change it, but |
27 | // for now it seems reasonable that QMap is better than QHash here... |
28 | QMap<QString, Validator *> validators; |
29 | |
30 | class Validator |
31 | { |
32 | public: |
33 | Validator(const QString &tag, const QString &value) |
34 | : m_tag(tag) |
35 | { |
36 | if (!value.isNull()) { |
37 | m_acceptedValues << value; |
38 | } |
39 | } |
40 | virtual ~Validator() |
41 | { |
42 | } |
43 | virtual bool filterAccepts(const QString &tag, const QString &value) = 0; |
44 | |
45 | protected: |
46 | friend class TagsFilterCheckerPrivate; |
47 | QString m_tag; |
48 | QStringList m_acceptedValues; |
49 | }; |
50 | |
51 | // Will only accept entries which have one of the accepted values set for the tag key |
52 | class EqualityValidator : public Validator |
53 | { |
54 | public: |
55 | EqualityValidator(const QString &tag, const QString &value) |
56 | : Validator(tag, value) |
57 | { |
58 | } |
59 | ~EqualityValidator() override |
60 | { |
61 | } |
62 | bool filterAccepts(const QString &tag, const QString &value) override |
63 | { |
64 | bool result = true; |
65 | if (tag == m_tag && !m_acceptedValues.contains(str: value)) { |
66 | qCDebug(KNEWSTUFFCORE) << "Item excluded by filter on" << m_tag << "because" << value << "was not included in" << m_acceptedValues; |
67 | result = false; |
68 | } |
69 | return result; |
70 | } |
71 | }; |
72 | |
73 | // Will only accept entries which have none of the values set for the tag key |
74 | class InequalityValidator : public Validator |
75 | { |
76 | public: |
77 | InequalityValidator(const QString &tag, const QString &value) |
78 | : Validator(tag, value) |
79 | { |
80 | } |
81 | ~InequalityValidator() override |
82 | { |
83 | } |
84 | bool filterAccepts(const QString &tag, const QString &value) override |
85 | { |
86 | bool result = true; |
87 | if (tag == m_tag && m_acceptedValues.contains(str: value)) { |
88 | qCDebug(KNEWSTUFFCORE) << "Item excluded by filter on" << m_tag << "because" << value << "was included in" << m_acceptedValues; |
89 | result = false; |
90 | } |
91 | return result; |
92 | } |
93 | }; |
94 | |
95 | void addValidator(const QString &filter) |
96 | { |
97 | int pos = 0; |
98 | if ((pos = filter.indexOf(s: QLatin1String("==" ))) > -1) { |
99 | QString tag = filter.left(n: pos); |
100 | QString value = filter.mid(position: tag.length() + 2); |
101 | Validator *val = validators.value(key: tag, defaultValue: nullptr); |
102 | if (!val) { |
103 | val = new EqualityValidator(tag, QString()); |
104 | validators.insert(key: tag, value: val); |
105 | } |
106 | val->m_acceptedValues << value; |
107 | qCDebug(KNEWSTUFFCORE) << "Created EqualityValidator for tag" << tag << "with value" << value; |
108 | } else if ((pos = filter.indexOf(s: QLatin1String("!=" ))) > -1) { |
109 | QString tag = filter.left(n: pos); |
110 | QString value = filter.mid(position: tag.length() + 2); |
111 | Validator *val = validators.value(key: tag, defaultValue: nullptr); |
112 | if (!val) { |
113 | val = new InequalityValidator(tag, QString()); |
114 | validators.insert(key: tag, value: val); |
115 | } |
116 | val->m_acceptedValues << value; |
117 | qCDebug(KNEWSTUFFCORE) << "Created InequalityValidator for tag" << tag << "with value" << value; |
118 | } else { |
119 | qCDebug(KNEWSTUFFCORE) << "Critical error attempting to create tag filter validators. The filter is defined as" << filter |
120 | << "which is not in the accepted formats key==value or key!=value" ; |
121 | } |
122 | } |
123 | }; |
124 | |
125 | TagsFilterChecker::TagsFilterChecker(const QStringList &tagFilter) |
126 | : d(new TagsFilterCheckerPrivate) |
127 | { |
128 | for (const QString &filter : tagFilter) { |
129 | d->addValidator(filter); |
130 | } |
131 | } |
132 | |
133 | TagsFilterChecker::~TagsFilterChecker() = default; |
134 | |
135 | bool TagsFilterChecker::filterAccepts(const QStringList &tags) |
136 | { |
137 | // if any tag in the content matches any of the tag filters, skip this entry |
138 | qCDebug(KNEWSTUFFCORE) << "Checking tags list" << tags << "against validators with keys" << d->validators.keys(); |
139 | for (const QString &tag : tags) { |
140 | if (tag.isEmpty()) { |
141 | // This happens when you do a split on an empty string (not an empty list, a list with one empty element... because reasons). |
142 | // Also handy for other things, i guess, though, so let's just catch it here. |
143 | continue; |
144 | } |
145 | QStringList current = tag.split(sep: QLatin1Char('=')); |
146 | if (current.length() > 2) { |
147 | qCDebug(KNEWSTUFFCORE) << "Critical error attempting to filter tags. Entry has tag defined as" << tag |
148 | << "which is not in the format \"key=value\" or \"key\"." ; |
149 | return false; |
150 | } else if (current.length() == 1) { |
151 | // If the tag is defined simply as a key, we give it the value "1", just to make our filtering work simpler |
152 | current << QStringLiteral("1" ); |
153 | } |
154 | QMap<QString, TagsFilterCheckerPrivate::Validator *>::const_iterator i = d->validators.constBegin(); |
155 | while (i != d->validators.constEnd()) { |
156 | if (!i.value()->filterAccepts(tag: current.at(i: 0), value: current.at(i: 1))) { |
157 | return false; |
158 | } |
159 | ++i; |
160 | } |
161 | } |
162 | // If we have arrived here, nothing has filtered the entry |
163 | // out (by being either incorrectly tagged or a filter rejecting |
164 | // it), and consequently it is an acceptable entry. |
165 | return true; |
166 | } |
167 | |
168 | } |
169 | |