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

KDE's Doxygen guidelines are available online.