Messagelib

searchlinecommand.cpp
1/*
2 SPDX-FileCopyrightText: 2024-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "searchlinecommand.h"
8#include <KLocalizedString>
9#include <QDateTime>
10
11// #define DEBUG_COMMAND_PARSER 1
12
13using namespace Qt::Literals::StringLiterals;
14using namespace MessageList::Core;
15QMap<QString, SearchLineCommand::SearchLineType> SearchLineCommand::mKeyList = {
16 {SearchLineCommand::searchLineTypeToString(Subject), SearchLineCommand::SearchLineType::Subject},
17 {SearchLineCommand::searchLineTypeToString(Body), SearchLineCommand::SearchLineType::Body},
18 {SearchLineCommand::searchLineTypeToString(To), SearchLineCommand::SearchLineType::To},
19 {SearchLineCommand::searchLineTypeToString(Cc), SearchLineCommand::SearchLineType::Cc},
20 {SearchLineCommand::searchLineTypeToString(Bcc), SearchLineCommand::SearchLineType::Bcc},
21 {SearchLineCommand::searchLineTypeToString(From), SearchLineCommand::SearchLineType::From},
22 {SearchLineCommand::searchLineTypeToString(HasAttachment), SearchLineCommand::SearchLineType::HasAttachment},
23 {SearchLineCommand::searchLineTypeToString(HasInvitation), SearchLineCommand::SearchLineType::HasInvitation},
24 {SearchLineCommand::searchLineTypeToString(IsUnRead), SearchLineCommand::SearchLineType::IsUnRead},
25 {SearchLineCommand::searchLineTypeToString(IsRead), SearchLineCommand::SearchLineType::IsRead},
26 {SearchLineCommand::searchLineTypeToString(IsImportant), SearchLineCommand::SearchLineType::IsImportant},
27 {SearchLineCommand::searchLineTypeToString(IsIgnored), SearchLineCommand::SearchLineType::IsIgnored},
28 {SearchLineCommand::searchLineTypeToString(IsHam), SearchLineCommand::SearchLineType::IsHam},
29 {SearchLineCommand::searchLineTypeToString(IsSpam), SearchLineCommand::SearchLineType::IsSpam},
30 {SearchLineCommand::searchLineTypeToString(IsWatched), SearchLineCommand::SearchLineType::IsWatched},
31 {SearchLineCommand::searchLineTypeToString(IsReplied), SearchLineCommand::SearchLineType::IsReplied},
32 {SearchLineCommand::searchLineTypeToString(IsForwarded), SearchLineCommand::SearchLineType::IsForwarded},
33 {SearchLineCommand::searchLineTypeToString(IsEncrypted), SearchLineCommand::SearchLineType::IsEncrypted},
34 {SearchLineCommand::searchLineTypeToString(IsQueued), SearchLineCommand::SearchLineType::IsQueued},
35 {SearchLineCommand::searchLineTypeToString(IsSent), SearchLineCommand::SearchLineType::IsSent},
36 {SearchLineCommand::searchLineTypeToString(IsDeleted), SearchLineCommand::SearchLineType::IsDeleted},
37 {SearchLineCommand::searchLineTypeToString(IsAction), SearchLineCommand::SearchLineType::IsAction},
38 {SearchLineCommand::searchLineTypeToString(Size), SearchLineCommand::SearchLineType::Size},
39 {SearchLineCommand::searchLineTypeToString(Smaller), SearchLineCommand::SearchLineType::Larger},
40 {SearchLineCommand::searchLineTypeToString(Larger), SearchLineCommand::SearchLineType::Smaller},
41 {SearchLineCommand::searchLineTypeToString(OlderThan), SearchLineCommand::SearchLineType::OlderThan},
42 {SearchLineCommand::searchLineTypeToString(NewerThan), SearchLineCommand::SearchLineType::NewerThan},
43 {SearchLineCommand::searchLineTypeToString(Category), SearchLineCommand::SearchLineType::Category},
44 // after:before:older:newer:
45 // category:
46 // TODO add support for OR
47};
48
49SearchLineCommand::SearchLineCommand() = default;
50
51SearchLineCommand::~SearchLineCommand() = default;
52
53bool SearchLineCommand::hasSubType(const QString &v)
54{
55 return v == QLatin1StringView("is") || v == QLatin1StringView("has");
56}
57
58bool SearchLineCommand::hasSubType(SearchLineCommand::SearchLineType type)
59{
60 return type == Date || type == Size || type == To || type == Bcc || type == Cc || type == From || type == Subject || type == Smaller || type == Larger
61 || type == OlderThan || type == NewerThan || type == Body || type == Category;
62}
63
64bool SearchLineCommand::isEmpty() const
65{
66 return mSearchLineInfo.isEmpty();
67}
68
69QString SearchLineCommand::searchLineTypeToString(SearchLineType type)
70{
71 switch (type) {
72 case Unknown:
73 case HasStateOrAttachment:
74 case Literal:
75 case Date:
76 return {};
77 case OlderThan:
78 return QStringLiteral("older_than");
79 case NewerThan:
80 return QStringLiteral("newer_than");
81 case Smaller:
82 return QStringLiteral("smaller");
83 case Larger:
84 return QStringLiteral("larger");
85 case Size:
86 return QStringLiteral("size");
87 case To:
88 return QStringLiteral("to");
89 case Bcc:
90 return QStringLiteral("bcc");
91 case Cc:
92 return QStringLiteral("cc");
93 case From:
94 return QStringLiteral("from");
95 case Subject:
96 return QStringLiteral("subject");
97 case Body:
98 return QStringLiteral("body");
99 case Category:
100 return QStringLiteral("category");
101 case HasAttachment:
102 return QStringLiteral("has:attachment");
103 case HasInvitation:
104 return QStringLiteral("has:invitation");
105 case IsImportant:
106 return QStringLiteral("is:important");
107 case IsRead:
108 return QStringLiteral("is:read");
109 case IsUnRead:
110 return QStringLiteral("is:unread");
111 case IsIgnored:
112 return QStringLiteral("is:ignored");
113 case IsHam:
114 return QStringLiteral("is:ham");
115 case IsSpam:
116 return QStringLiteral("is:spam");
117 case IsWatched:
118 return QStringLiteral("is:watched");
119 case IsReplied:
120 return QStringLiteral("is:replied");
121 case IsForwarded:
122 return QStringLiteral("is:forwarded");
123 case IsEncrypted:
124 return QStringLiteral("is:encrypted");
125 case IsQueued:
126 return QStringLiteral("is:queued");
127 case IsSent:
128 return QStringLiteral("is:sent");
129 case IsAction:
130 return QStringLiteral("is:action");
131 case IsDeleted:
132 return QStringLiteral("is:deleted");
133 }
134 return {};
135}
136
137QString SearchLineCommand::convertSearchLinetypeToTranslatedString(SearchLineCommand::SearchLineType type) const
138{
139 switch (type) {
140 case Unknown:
141 case HasStateOrAttachment:
142 return {};
143 // TODO implement Date
144 case Date:
145 case OlderThan:
146 case NewerThan:
147 return {};
148 case Literal:
149 return i18n("Literal string");
150 case Smaller:
151 return i18n("Size is smaller than");
152 case Larger:
153 return i18n("Size is larger than");
154 case Size:
155 return i18n("Size is");
156 case To:
157 return i18n("To contains");
158 case Bcc:
159 return i18n("BCC contains");
160 case Cc:
161 return i18n("CC contains");
162 case From:
163 return i18n("From contains");
164 case Subject:
165 return i18n("Subject contains");
166 case Body:
167 return i18n("Body contains");
168 case Category:
169 return i18n("Mail has tag");
170 case HasAttachment:
171 return i18n("Mail has attachment");
172 case HasInvitation:
173 return i18n("Mail has invitation");
174 case IsImportant:
175 return i18n("Mail is important");
176 case IsRead:
177 return i18n("Mail is read");
178 case IsUnRead:
179 return i18n("Mail is Unread");
180 case IsIgnored:
181 return i18n("Mail is Ignored");
182 case IsHam:
183 return i18n("Mail is Ham");
184 case IsSpam:
185 return i18n("Mail is Spam");
186 case IsWatched:
187 return i18n("Mail is watched");
188 case IsReplied:
189 return i18n("Mail is replied");
190 case IsForwarded:
191 return i18n("Mail is forwarded");
192 case IsEncrypted:
193 return i18n("Mail is encrypted");
194 case IsQueued:
195 return i18n("Mail is queued");
196 case IsSent:
197 return i18n("Mail is sent");
198 case IsDeleted:
199 return i18n("Mail is deleted");
200 case IsAction:
201 return i18n("Mail is action");
202 }
203 return {};
204}
205
206QString SearchLineCommand::generateCommadLineStr() const
207{
208 QString result;
209 for (const auto &info : mSearchLineInfo) {
210 if (!result.isEmpty()) {
211 result += QLatin1Char(' ') + i18n("AND") + QLatin1Char(' ');
212 }
213 const QString translatedType = convertSearchLinetypeToTranslatedString(info.type);
214 if (!translatedType.isEmpty()) {
215 result += translatedType;
216 }
217 if (!info.argument.isEmpty()) {
218 if (!translatedType.isEmpty()) {
219 result += QLatin1Char(' ');
220 }
221 result += info.argument;
222 }
223 }
224 return result;
225}
226
227SearchLineCommand::SearchLineInfo SearchLineCommand::isAnotherInfo(QString tmp, SearchLineInfo searchLineInfo)
228{
229 if (!tmp.contains(QLatin1Char(' '))) {
230 return {};
231 }
232 if (tmp.endsWith(QLatin1StringView("is")) || tmp.endsWith(QLatin1StringView("has"))) {
233#ifdef DEBUG_COMMAND_PARSER
234 qDebug() << " found has subtype " << tmp;
235#endif
236 return {};
237 }
238 const QStringList keys = mKeyList.keys();
239 for (const QString &key : keys) {
240 if (tmp.endsWith(key)) {
241#ifdef DEBUG_COMMAND_PARSER
242 qDebug() << " found element !!!!!! " << tmp;
243#endif
244 tmp.remove(key);
245 tmp.removeLast(); // Remove last space
246 searchLineInfo.argument = tmp;
247 if (!searchLineInfo.argument.isEmpty() && searchLineInfo.type == Unknown) {
248 searchLineInfo.type = Literal;
249 }
250 if (searchLineInfo.isValid()) {
251 appendSearchLineInfo(searchLineInfo);
252 }
253#ifdef DEBUG_COMMAND_PARSER
254 qDebug() << " Add searchLineInfo" << searchLineInfo << mSearchLineInfo;
255#endif
256
257 SearchLineInfo info;
258 info.type = mKeyList.value(key);
259 return info;
260 }
261 }
262 return {};
263}
264
265bool SearchLineCommand::hasOnlyOneLiteralCommand() const
266{
267 return mSearchLineInfo.count() == 1 && (mSearchLineInfo.at(0).type == SearchLineType::Literal);
268}
269
270void SearchLineCommand::parseSearchLineCommand(const QString &str)
271{
272 mSearchLineInfo.clear();
273 if (str.isEmpty()) {
274 return;
275 }
276 SearchLineInfo searchLineInfo;
277 QString tmp;
278 int parentheses = 0;
279 for (int i = 0, total = str.length(); i < total; ++i) {
280 const QChar ch = str.at(i);
281 if (ch == QLatin1Char(':')) {
282#ifdef DEBUG_COMMAND_PARSER
283 qDebug() << " tmp ! " << tmp;
284#endif
285 const SearchLineCommand::SearchLineInfo newInfo = isAnotherInfo(tmp, searchLineInfo);
286 if (newInfo.type != Unknown) {
287 tmp.clear();
288 searchLineInfo = newInfo;
289 } else if (mKeyList.contains(tmp.trimmed())) {
290#ifdef DEBUG_COMMAND_PARSER
291 qDebug() << " contains " << tmp;
292#endif
293 searchLineInfo.type = mKeyList.value(tmp.trimmed());
294 tmp.clear();
295 } else if (hasSubType(tmp)) {
296 searchLineInfo.type = HasStateOrAttachment;
297 tmp += ch;
298 // continue
299 } else {
300 tmp += ch;
301 }
302 } else if (ch.isSpace()) {
303 const SearchLineCommand::SearchLineInfo newInfo = isAnotherInfo(tmp, searchLineInfo);
304 if (newInfo.type != Unknown) {
305 tmp.clear();
306 searchLineInfo = newInfo;
307 } else if (mKeyList.contains(tmp)) { // We can use is:... or has:...
308 searchLineInfo.type = mKeyList.value(tmp);
309 tmp.clear();
310 }
311#ifdef DEBUG_COMMAND_PARSER
312 qDebug() << " is space " << "pare" << parentheses << " tmp " << tmp << "searchLineInfo.type " << searchLineInfo.type
313 << " searchLineInfo.argument.isEmpty() " << searchLineInfo.argument.isEmpty();
314#endif
315 if (tmp.isEmpty() && hasSubType(searchLineInfo.type) && parentheses == 0) {
316#ifdef DEBUG_COMMAND_PARSER
317 qDebug() << "clear invalid type" << searchLineInfo;
318#endif
319 searchLineInfo.type = Unknown;
320 tmp.clear();
321 } else if (hasSubType(searchLineInfo.type)) {
322 tmp += ch;
323 } else if (searchLineInfo.type != Unknown && parentheses == 0) {
324 searchLineInfo.argument = tmp;
325 tmp.clear();
326#ifdef DEBUG_COMMAND_PARSER
327 qDebug() << "clear tmp argument " << searchLineInfo;
328#endif
329 } else { // Literal
330 tmp += ch;
331 }
332 if (searchLineInfo.isValid() && parentheses == 0) {
333 appendSearchLineInfo(searchLineInfo);
334 searchLineInfo.clear();
335 tmp.clear();
336 }
337 } else if (ch == QLatin1Char('(')) {
338 parentheses++;
339 if (parentheses > 1) {
340 tmp += ch;
341 }
342#ifdef DEBUG_COMMAND_PARSER
343 qDebug() << " parenthese ( equal " << parentheses;
344#endif
345 } else if (ch == QLatin1Char(')')) {
346 parentheses--;
347 if (parentheses > 0) {
348 tmp += ch;
349 }
350#ifdef DEBUG_COMMAND_PARSER
351 qDebug() << " parenthese ) equal " << parentheses;
352#endif
353 if (parentheses == 0) {
354 searchLineInfo.argument = tmp;
355 tmp.clear();
356#ifdef DEBUG_COMMAND_PARSER
357 qDebug() << " new values " << searchLineInfo;
358#endif
359 appendSearchLineInfo(searchLineInfo);
360 searchLineInfo.clear();
361 }
362 } else {
363 tmp += ch;
364#ifdef DEBUG_COMMAND_PARSER
365 qDebug() << " tmp " << tmp << " ch " << ch << "end";
366#endif
367 }
368 }
369 if (searchLineInfo.type != Unknown) {
370 if (searchLineInfo.type == HasStateOrAttachment) {
371#ifdef DEBUG_COMMAND_PARSER
372 qDebug() << " type is HasStateOrAttachment";
373#endif
374 if (mKeyList.contains(tmp)) {
375 searchLineInfo.type = mKeyList.value(tmp);
376 appendSearchLineInfo(searchLineInfo);
377 }
378 } else {
379 if (!tmp.isEmpty()) {
380#ifdef DEBUG_COMMAND_PARSER
381 qDebug() << " add as original searchLineInfo" << searchLineInfo;
382#endif
383 const SearchLineCommand::SearchLineInfo newInfo = isAnotherInfo(tmp, searchLineInfo);
384 if (newInfo.type != Unknown) {
385 searchLineInfo = newInfo;
386 } else {
387 searchLineInfo.argument = tmp;
388 }
389 appendSearchLineInfo(searchLineInfo);
390 }
391 }
392 } else {
393 if (!tmp.isEmpty()) {
394 searchLineInfo.type = Literal;
395 searchLineInfo.argument = tmp;
396 appendSearchLineInfo(searchLineInfo);
397 }
398 }
399#ifdef DEBUG_COMMAND_PARSER
400 qDebug() << " END " << mSearchLineInfo;
401#endif
402 // TODO add date ?
403 // TODO add support for double quote
404 // We need to extend emailquery or creating query by hand
405}
406
407void SearchLineCommand::appendSearchLineInfo(SearchLineInfo searchLineInfo)
408{
409 if (searchLineInfo.mustBeUnique()) {
410 if (mSearchLineInfo.contains(searchLineInfo)) {
411#ifdef DEBUG_COMMAND_PARSER
412 qDebug() << " Already exist " << searchLineInfo;
413#endif
414 return;
415 }
416 }
417 mSearchLineInfo.append(std::move(searchLineInfo));
418}
419
420QList<SearchLineCommand::SearchLineInfo> SearchLineCommand::searchLineInfo() const
421{
422 return mSearchLineInfo;
423}
424
425void SearchLineCommand::setSearchLineInfo(const QList<SearchLineInfo> &newSearchLineInfo)
426{
427 mSearchLineInfo = newSearchLineInfo;
428}
429
430void SearchLineCommand::SearchLineInfo::clear()
431{
432 type = SearchLineCommand::SearchLineType::Unknown;
433 argument.clear();
434}
435
436bool SearchLineCommand::SearchLineInfo::isValid() const
437{
438 if (type == SearchLineType::Unknown || type == SearchLineCommand::HasStateOrAttachment) {
439 return false;
440 }
441 if (type == SearchLineType::Literal && !argument.isEmpty()) {
442 return true;
443 }
444 if (hasSubType(type) && !argument.isEmpty()) {
445 return true;
446 }
447 if (!hasSubType(type) && argument.isEmpty()) {
448 return true;
449 }
450 return false;
451}
452
453bool SearchLineCommand::SearchLineInfo::operator==(const SearchLineInfo &other) const
454{
455 return type == other.type && argument == other.argument;
456}
457
458bool SearchLineCommand::SearchLineInfo::isValidDate() const
459{
460 if (argument.isEmpty()) {
461 return false;
462 }
463 return QDateTime::fromString(argument).isValid();
464}
465
466bool SearchLineCommand::SearchLineInfo::mustBeUnique() const
467{
468 return type == HasAttachment || type == IsImportant || type == IsRead || type == IsUnRead || type == IsIgnored || type == IsHam || type == IsSpam
469 || type == IsWatched || type == IsReplied || type == IsForwarded || type == IsEncrypted || type == IsQueued || type == IsSent || type == IsDeleted
470 || type == IsAction;
471}
472
473qint64 SearchLineCommand::SearchLineInfo::convertArgumentAsSize() const
474{
475 // TODO convert it
476 return {};
477}
478
479QDebug operator<<(QDebug d, const MessageList::Core::SearchLineCommand::SearchLineInfo &info)
480{
481 d << " type " << info.type;
482 d << " argument " << info.argument;
483 return d;
484}
485
486#include "moc_searchlinecommand.cpp"
QString i18n(const char *text, const TYPE &arg...)
QDebug operator<<(QDebug dbg, const DcrawInfoContainer &c)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
bool isSpace(char32_t ucs4)
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
void clear()
qsizetype count() const const
QString & append(QChar ch)
const QChar at(qsizetype position) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & removeLast()
QString trimmed() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:47:09 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.