Baloo

advancedqueryparser.cpp
1 /*
2  SPDX-FileCopyrightText: 2014-2015 Vishesh Handa <[email protected]>
3  SPDX-FileCopyrightText: 2014 Denis Steckelmacher <[email protected]>
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 
14 using namespace Baloo;
15 
16 AdvancedQueryParser::AdvancedQueryParser()
17 {
18 }
19 
20 static 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(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(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(token);
49  token.clear();
50  }
51  tokenList.append(c);
52  } else if (c == QLatin1Char('>') || c == QLatin1Char('<') || c == QLatin1Char(':') || c == QLatin1Char('=')) {
53  // Operators end tokens
54  if (!token.isEmpty()) {
55  tokenList.append(token);
56  token.clear();
57  }
58  // accept '=' after any of the above
59  if (((i + 1) < end) && (text.at(i + 1) == QLatin1Char('='))) {
60  tokenList.append(text.mid(i, 2));
61  i++;
62  } else {
63  tokenList.append(c);
64  }
65  } else {
66  // Simply extend the current token
67  token.append(c);
68  }
69  }
70 
71  if (!token.isEmpty()) {
72  tokenList.append(token);
73  }
74 
75  return tokenList;
76 }
77 
78 static 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 
85 Term 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;
90  Term termInConstruction;
91  bool valueExpected = false;
92 
93  stack.push(Term());
94  ops.push(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, 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, 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(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, ops.top());
161  ops.top() = Term::And;
162  }
163 
164  stack.push(Term());
165  ops.push(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, 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, stack.pop(), 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, 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, 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, stack.pop(), ops.top());
222  }
223 
224  return stack.top();
225 }
void append(const T &value)
int size() const const
void clear()
T & top()
bool isSpace() const const
Implements storage for docIds without any associated data Instantiated for:
Definition: coding.cpp:11
bool isEmpty() const const
void push(const T &t)
const QChar at(int position) const const
int size() const const
char toLatin1() const const
QString mid(int position, int n) const const
QString & append(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Nov 29 2023 03:56:26 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.