articlematcher.cpp 8.48 KB
Newer Older
1
/*
2
 * articlematcher.cpp
3
 *
4
 * SPDX-FileCopyrightText: 2004, 2005 Frerich Raabe <raabe@kde.org>
Frerich Raabe's avatar
Frerich Raabe committed
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 */
8
#include "articlematcher.h"
Frank Osterfeld's avatar
Frank Osterfeld committed
9
#include "article.h"
10
#include "types.h"
Laurent Montel's avatar
Laurent Montel committed
11
#include <QUrl>
Laurent Montel's avatar
Laurent Montel committed
12
#include <KConfig>
13
#include <KConfigGroup>
Laurent Montel's avatar
Laurent Montel committed
14
#include "akregator_debug.h"
15

16
#include <QRegularExpression>
17

Laurent Montel's avatar
Laurent Montel committed
18 19 20
namespace Akregator {
namespace Filters {
AbstractMatcher::AbstractMatcher()
Laurent Montel's avatar
Laurent Montel committed
21
{
Laurent Montel's avatar
Laurent Montel committed
22
}
23

Laurent Montel's avatar
Laurent Montel committed
24 25 26
AbstractMatcher::~AbstractMatcher()
{
}
27

28 29
QString Criterion::subjectToString(Subject subj)
{
Laurent Montel's avatar
Laurent Montel committed
30 31
    switch (subj) {
    case Title:
32
        return QStringLiteral("Title");
Laurent Montel's avatar
Laurent Montel committed
33
    case Link:
34
        return QStringLiteral("Link");
Laurent Montel's avatar
Laurent Montel committed
35
    case Description:
36
        return QStringLiteral("Description");
Laurent Montel's avatar
Laurent Montel committed
37
    case Status:
38
        return QStringLiteral("Status");
Laurent Montel's avatar
Laurent Montel committed
39
    case KeepFlag:
40
        return QStringLiteral("KeepFlag");
Laurent Montel's avatar
Laurent Montel committed
41
    case Author:
42
        return QStringLiteral("Author");
43
    }
Laurent Montel's avatar
Laurent Montel committed
44
    return {};
45 46
}

Laurent Montel's avatar
Laurent Montel committed
47
Criterion::Subject Criterion::stringToSubject(const QString &subjStr)
48
{
49
    if (subjStr == QLatin1String("Title")) {
50
        return Title;
51
    } else if (subjStr == QLatin1String("Link")) {
52
        return Link;
53
    } else if (subjStr == QLatin1String("Description")) {
54
        return Description;
55
    } else if (subjStr == QLatin1String("Status")) {
56
        return Status;
57
    } else if (subjStr == QLatin1String("KeepFlag")) {
58
        return KeepFlag;
59
    } else if (subjStr == QLatin1String("Author")) {
60
        return Author;
Laurent Montel's avatar
Laurent Montel committed
61
    }
62 63 64 65 66 67 68

    // hopefully never reached
    return Description;
}

QString Criterion::predicateToString(Predicate pred)
{
Laurent Montel's avatar
Laurent Montel committed
69 70
    switch (pred) {
    case Contains:
Laurent Montel's avatar
Laurent Montel committed
71
        return QStringLiteral("Contains");
Laurent Montel's avatar
Laurent Montel committed
72
    case Equals:
Laurent Montel's avatar
Laurent Montel committed
73
        return QStringLiteral("Equals");
Laurent Montel's avatar
Laurent Montel committed
74
    case Matches:
Laurent Montel's avatar
Laurent Montel committed
75
        return QStringLiteral("Matches");
Laurent Montel's avatar
Laurent Montel committed
76
    case Negation:
Laurent Montel's avatar
Laurent Montel committed
77
        return QStringLiteral("Negation");
78
    }
Laurent Montel's avatar
Laurent Montel committed
79
    return {};
80 81
}

Laurent Montel's avatar
Laurent Montel committed
82
Criterion::Predicate Criterion::stringToPredicate(const QString &predStr)
83
{
84
    if (predStr == QLatin1String("Contains")) {
85
        return Contains;
86
    } else if (predStr == QLatin1String("Equals")) {
87
        return Equals;
88
    } else if (predStr == QLatin1String("Matches")) {
89
        return Matches;
90
    } else if (predStr == QLatin1String("Negation")) {
91
        return Negation;
Laurent Montel's avatar
Laurent Montel committed
92
    }
93

94 95 96 97
    // hopefully never reached
    return Contains;
}

98 99 100 101
Criterion::Criterion()
{
}

Laurent Montel's avatar
Laurent Montel committed
102 103 104 105
Criterion::Criterion(Subject subject, Predicate predicate, const QVariant &object)
    : m_subject(subject)
    , m_predicate(predicate)
    , m_object(object)
106
{
107 108
}

Laurent Montel's avatar
Laurent Montel committed
109
void Criterion::writeConfig(KConfigGroup *config) const
110
{
111
    config->writeEntry(QStringLiteral("subject"), subjectToString(m_subject));
112

113
    config->writeEntry(QStringLiteral("predicate"), predicateToString(m_predicate));
114

115
    config->writeEntry(QStringLiteral("objectType"), QString::fromLatin1(m_object.typeName()));
116

117
    config->writeEntry(QStringLiteral("objectValue"), m_object);
118 119
}

Laurent Montel's avatar
Laurent Montel committed
120
void Criterion::readConfig(KConfigGroup *config)
121
{
122 123
    m_subject = stringToSubject(config->readEntry(QStringLiteral("subject"), QString()));
    m_predicate = stringToPredicate(config->readEntry(QStringLiteral("predicate"), QString()));
124
    QVariant::Type type = QVariant::nameToType(config->readEntry(QStringLiteral("objType"), QString()).toLatin1().constData());
125

Laurent Montel's avatar
Laurent Montel committed
126
    if (type != QVariant::Invalid) {
127
        m_object = config->readEntry(QStringLiteral("objectValue"), QVariant(type));
128
    }
129 130
}

Laurent Montel's avatar
Laurent Montel committed
131
bool Criterion::satisfiedBy(const Article &article) const
132
{
Laurent Montel's avatar
Laurent Montel committed
133
    if (article.isNull()) {
134 135 136
        return false;
    }

137
    QVariant concreteSubject;
138

Laurent Montel's avatar
Laurent Montel committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    switch (m_subject) {
    case Title:
        concreteSubject = QVariant(article.title());
        break;
    case Description:
        concreteSubject = QVariant(article.description());
        break;
    case Link:
        // ### Maybe use prettyUrl here?
        concreteSubject = QVariant(article.link().url());
        break;
    case Status:
        concreteSubject = QVariant(article.status());
        break;
    case KeepFlag:
        concreteSubject = QVariant(article.keep());
        break;
    case Author:
        concreteSubject = QVariant(article.authorName());
158 159 160 161
    }

    bool satisfied = false;

Laurent Montel's avatar
Laurent Montel committed
162
    const auto predicateType = static_cast<Predicate>(m_predicate & ~Negation);
Laurent Montel's avatar
Laurent Montel committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176
    QString subjectType = QLatin1String(concreteSubject.typeName());

    switch (predicateType) {
    case Contains:
        satisfied = concreteSubject.toString().indexOf(m_object.toString(), 0, Qt::CaseInsensitive) != -1;
        break;
    case Equals:
        if (subjectType == QLatin1String("int")) {
            satisfied = concreteSubject.toInt() == m_object.toInt();
        } else {
            satisfied = concreteSubject.toString() == m_object.toString();
        }
        break;
    case Matches:
177
        satisfied = concreteSubject.toString().contains(QRegularExpression(m_object.toString()));
Laurent Montel's avatar
Laurent Montel committed
178 179
        break;
    default:
Laurent Montel's avatar
Laurent Montel committed
180
        qCDebug(AKREGATOR_LOG) << "Internal inconsistency; predicateType should never be Negation";
Laurent Montel's avatar
Laurent Montel committed
181
        break;
182 183
    }

Laurent Montel's avatar
Laurent Montel committed
184
    if (m_predicate & Negation) {
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        satisfied = !satisfied;
    }

    return satisfied;
}

Criterion::Subject Criterion::subject() const
{
    return m_subject;
}

Criterion::Predicate Criterion::predicate() const
{
    return m_predicate;
}

201
QVariant Criterion::object() const
202 203 204 205
{
    return m_object;
}

206
ArticleMatcher::ArticleMatcher()
Laurent Montel's avatar
Laurent Montel committed
207
    : m_association(None)
208 209 210
{
}

211 212 213 214
ArticleMatcher::~ArticleMatcher()
{
}

Laurent Montel's avatar
Laurent Montel committed
215
ArticleMatcher::ArticleMatcher(const QVector<Criterion> &criteria, Association assoc)
Laurent Montel's avatar
Laurent Montel committed
216 217
    : m_criteria(criteria)
    , m_association(assoc)
218 219 220
{
}

Laurent Montel's avatar
Laurent Montel committed
221
bool ArticleMatcher::matches(const Article &a) const
222
{
Laurent Montel's avatar
Laurent Montel committed
223 224 225 226 227 228 229
    switch (m_association) {
    case LogicalOr:
        return anyCriterionMatches(a);
    case LogicalAnd:
        return allCriteriaMatch(a);
    default:
        break;
230 231 232 233
    }
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
234
void ArticleMatcher::writeConfig(KConfigGroup *config) const
235
{
Laurent Montel's avatar
Laurent Montel committed
236
    config->writeEntry(QStringLiteral("matcherAssociation"), associationToString(m_association));
237

Laurent Montel's avatar
Laurent Montel committed
238
    config->writeEntry(QStringLiteral("matcherCriteriaCount"), m_criteria.count());
239

240
    QString criterionGroupPrefix = config->name() + QLatin1String("_Criterion");
241

Laurent Montel's avatar
Laurent Montel committed
242 243
    const int criteriaSize(m_criteria.size());
    for (int index = 0; index < criteriaSize; ++index) {
Laurent Montel's avatar
Laurent Montel committed
244
        *config = KConfigGroup(config->config(), criterionGroupPrefix + QString::number(index));
Laurent Montel's avatar
Laurent Montel committed
245
        m_criteria.at(index).writeConfig(config);
246 247 248
    }
}

Laurent Montel's avatar
Laurent Montel committed
249
void ArticleMatcher::readConfig(KConfigGroup *config)
250 251
{
    m_criteria.clear();
252
    m_association = stringToAssociation(config->readEntry(QStringLiteral("matcherAssociation"), QString()));
253

Laurent Montel's avatar
Laurent Montel committed
254
    const int count = config->readEntry(QStringLiteral("matcherCriteriaCount"), 0);
255

Laurent Montel's avatar
Laurent Montel committed
256
    const QString criterionGroupPrefix = config->name() + QLatin1String("_Criterion");
257

Laurent Montel's avatar
Laurent Montel committed
258
    for (int i = 0; i < count; ++i) {
259
        Criterion c;
Laurent Montel's avatar
Laurent Montel committed
260
        *config = KConfigGroup(config->config(), criterionGroupPrefix + QString::number(i));
261 262 263 264 265
        c.readConfig(config);
        m_criteria.append(c);
    }
}

Laurent Montel's avatar
Laurent Montel committed
266
bool ArticleMatcher::operator==(const AbstractMatcher &other) const
267
{
Laurent Montel's avatar
Laurent Montel committed
268 269
    auto *ptr = const_cast<AbstractMatcher *>(&other);
    auto *o = dynamic_cast<ArticleMatcher *>(ptr);
Laurent Montel's avatar
Laurent Montel committed
270
    if (!o) {
271
        return false;
Laurent Montel's avatar
Laurent Montel committed
272
    } else {
273
        return m_association == o->m_association && m_criteria == o->m_criteria;
Laurent Montel's avatar
Laurent Montel committed
274
    }
275
}
Laurent Montel's avatar
Laurent Montel committed
276

Laurent Montel's avatar
Laurent Montel committed
277
bool ArticleMatcher::operator!=(const AbstractMatcher &other) const
278 279
{
    return !(*this == other);
280 281
}

Laurent Montel's avatar
Laurent Montel committed
282
bool ArticleMatcher::anyCriterionMatches(const Article &a) const
283
{
Laurent Montel's avatar
Laurent Montel committed
284
    if (m_criteria.isEmpty()) {
285
        return true;
Laurent Montel's avatar
Laurent Montel committed
286
    }
Laurent Montel's avatar
Laurent Montel committed
287 288
    const int criteriaSize(m_criteria.size());
    for (int index = 0; index < criteriaSize; ++index) {
Laurent Montel's avatar
Laurent Montel committed
289
        if (m_criteria.at(index).satisfiedBy(a)) {
290 291 292 293 294 295
            return true;
        }
    }
    return false;
}

Laurent Montel's avatar
Laurent Montel committed
296
bool ArticleMatcher::allCriteriaMatch(const Article &a) const
297
{
Laurent Montel's avatar
Laurent Montel committed
298
    if (m_criteria.isEmpty()) {
299
        return true;
Laurent Montel's avatar
Laurent Montel committed
300
    }
Laurent Montel's avatar
Laurent Montel committed
301 302
    const int criteriaSize(m_criteria.size());
    for (int index = 0; index < criteriaSize; ++index) {
Laurent Montel's avatar
Laurent Montel committed
303
        if (!m_criteria.at(index).satisfiedBy(a)) {
304 305 306 307 308 309
            return false;
        }
    }
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
310
ArticleMatcher::Association ArticleMatcher::stringToAssociation(const QString &assocStr)
311
{
312
    if (assocStr == QLatin1String("LogicalAnd")) {
313
        return LogicalAnd;
314
    } else if (assocStr == QLatin1String("LogicalOr")) {
315
        return LogicalOr;
Laurent Montel's avatar
Laurent Montel committed
316
    } else {
317
        return None;
Laurent Montel's avatar
Laurent Montel committed
318
    }
319 320 321 322
}

QString ArticleMatcher::associationToString(Association association)
{
Laurent Montel's avatar
Laurent Montel committed
323 324
    switch (association) {
    case LogicalAnd:
Laurent Montel's avatar
Laurent Montel committed
325
        return QStringLiteral("LogicalAnd");
Laurent Montel's avatar
Laurent Montel committed
326
    case LogicalOr:
Laurent Montel's avatar
Laurent Montel committed
327
        return QStringLiteral("LogicalOr");
Laurent Montel's avatar
Laurent Montel committed
328
    default:
Laurent Montel's avatar
Laurent Montel committed
329
        return QStringLiteral("None");
330 331
    }
}
332
} //namespace Filters
333
} //namespace Akregator