1/*
2 SPDX-FileCopyrightText: 2014-2015 Vishesh Handa <vhanda@kde.org>
3 SPDX-FileCopyrightText: 2014 Denis Steckelmacher <steckdenis@yahoo.fr>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "advancedqueryparser.h"
9
10#include <QStringList>
11#include <QStack>
12#include <QDate>
13
14using namespace Baloo;
15
16AdvancedQueryParser::AdvancedQueryParser()
17{
18}
19
20static QStringList lex(const QString& text)
21{
22 QStringList tokenList;
23 QString token;
24 bool inQuotes = false;
25
26 for (int i = 0, end = text.size(); i != end; ++i) {
27 QChar c = text.at(i);
28
29 if (c == QLatin1Char('"')) {
30 // Quotes start or end string literals
31 if (inQuotes) {
32 tokenList.append(t: token);
33 token.clear();
34 }
35 inQuotes = !inQuotes;
36 } else if (inQuotes) {
37 // Don't do any processing in strings
38 token.append(c);
39 } else if (c.isSpace()) {
40 // Spaces end tokens
41 if (!token.isEmpty()) {
42 tokenList.append(t: token);
43 token.clear();
44 }
45 } else if (c == QLatin1Char('(') || c == QLatin1Char(')')) {
46 // Parentheses end tokens, and are tokens by themselves
47 if (!token.isEmpty()) {
48 tokenList.append(t: token);
49 token.clear();
50 }
51 tokenList.append(t: c);
52 } else if (c == QLatin1Char('>') || c == QLatin1Char('<') || c == QLatin1Char(':') || c == QLatin1Char('=')) {
53 // Operators end tokens
54 if (!token.isEmpty()) {
55 tokenList.append(t: token);
56 token.clear();
57 }
58 // accept '=' after any of the above
59 if (((i + 1) < end) && (text.at(i: i + 1) == QLatin1Char('='))) {
60 tokenList.append(t: text.mid(position: i, n: 2));
61 i++;
62 } else {
63 tokenList.append(t: c);
64 }
65 } else {
66 // Simply extend the current token
67 token.append(c);
68 }
69 }
70
71 if (!token.isEmpty()) {
72 tokenList.append(t: token);
73 }
74
75 return tokenList;
76}
77
78static void addTermToStack(QStack<Term>& stack, const Term& termInConstruction, Term::Operation op)
79{
80 Term &tos = stack.top();
81
82 tos = Term(tos, op, termInConstruction);
83}
84
85Term AdvancedQueryParser::parse(const QString& text)
86{
87 // The parser does not do any look-ahead but has to store some state
88 QStack<Term> stack;
89 QStack<Term::Operation> ops;
90 Term termInConstruction;
91 bool valueExpected = false;
92
93 stack.push(t: Term());
94 ops.push(t: Term::And);
95
96 // Lex the input string
97 QStringList tokens = lex(text);
98 for (const QString &token : tokens) {
99 // If a key and an operator have been parsed, now is time for a value
100 if (valueExpected) {
101 // When the parser encounters a literal, it puts it in the value of
102 // termInConstruction so that "foo bar baz" is parsed as expected.
103 auto property = termInConstruction.value().toString();
104 if (property.isEmpty()) {
105 qDebug() << "Binary operator without first argument encountered:" << text;
106 return Term();
107 }
108 termInConstruction.setProperty(property);
109
110 termInConstruction.setValue(token);
111 valueExpected = false;
112 continue;
113 }
114
115 // Handle the logic operators
116 if (token == QLatin1String("AND")) {
117 if (!termInConstruction.isEmpty()) {
118 addTermToStack(stack, termInConstruction, op: ops.top());
119 termInConstruction = Term();
120 }
121 ops.top() = Term::And;
122 continue;
123 } else if (token == QLatin1String("OR")) {
124 if (!termInConstruction.isEmpty()) {
125 addTermToStack(stack, termInConstruction, op: ops.top());
126 termInConstruction = Term();
127 }
128 ops.top() = Term::Or;
129 continue;
130 }
131
132 // Handle the different comparators (and braces)
133 Term::Comparator comparator = Term::Auto;
134
135 switch (token.isEmpty() ? '\0' : token.at(i: 0).toLatin1()) {
136 case ':':
137 comparator = Term::Contains;
138 break;
139 case '=':
140 comparator = Term::Equal;
141 break;
142 case '<': {
143 if (token.size() == 1) {
144 comparator = Term::Less;
145 } else if (token[1] == QLatin1Char('=')) {
146 comparator = Term::LessEqual;
147 }
148 break;
149 }
150 case '>': {
151 if (token.size() == 1) {
152 comparator = Term::Greater;
153 } else if (token[1] == QLatin1Char('=')) {
154 comparator = Term::GreaterEqual;
155 }
156 break;
157 }
158 case '(':
159 if (!termInConstruction.isEmpty()) {
160 addTermToStack(stack, termInConstruction, op: ops.top());
161 ops.top() = Term::And;
162 }
163
164 stack.push(t: Term());
165 ops.push(t: Term::And);
166 termInConstruction = Term();
167
168 continue;
169 case ')':
170 // Prevent a stack underflow if the user writes "a b ))))"
171 if (stack.size() > 1) {
172 // Don't forget the term just before the closing brace
173 if (termInConstruction.value().isValid()) {
174 addTermToStack(stack, termInConstruction, op: ops.top());
175 }
176
177 // stack.pop() is the term that has just been closed. Append
178 // it to the term just above it.
179 ops.pop();
180 addTermToStack(stack, termInConstruction: stack.pop(), op: ops.top());
181 ops.top() = Term::And;
182 termInConstruction = Term();
183 }
184
185 continue;
186 default:
187 break;
188 }
189
190 if (comparator != Term::Auto) {
191 // Set the comparator of the term in construction and expect a value
192 termInConstruction.setComparator(comparator);
193 valueExpected = true;
194 } else {
195 // A new term will be started, so termInConstruction has to be appended
196 // to the top-level subterm list.
197 if (!termInConstruction.isEmpty()) {
198 addTermToStack(stack, termInConstruction, op: ops.top());
199 ops.top() = Term::And;
200 }
201
202 termInConstruction = Term(QString(), token);
203 }
204 }
205
206 if (valueExpected) {
207 termInConstruction.setProperty(termInConstruction.value().toString());
208 termInConstruction.setValue(QString());
209 termInConstruction.setComparator(Term::Contains);
210 }
211
212 if (termInConstruction.value().isValid()) {
213 addTermToStack(stack, termInConstruction, op: ops.top());
214 }
215
216 // Process unclosed parentheses
217 ops.pop();
218 while (stack.size() > 1) {
219 // stack.pop() is the term that has to be closed. Append
220 // it to the term just above it.
221 addTermToStack(stack, termInConstruction: stack.pop(), op: ops.top());
222 }
223
224 return stack.top();
225}
226

source code of baloo/src/lib/advancedqueryparser.cpp