1 /*
2 * e-free-form-exp.c
3 *
4 * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
5 *
6 * This library is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include "evolution-data-server-config.h"
21
22 #include <string.h>
23
24 #include "e-free-form-exp.h"
25
26 /* <free-form-expression> := <token> *(" " <token>)
27 <token> := <and> | <or> | <not> | <expr>
28 <and> := "and:(" <free-form-expression>+ ")"
29 <or> := "or:(" <free-form-expression>+ ")"
30 <not> := "not:(" <free-form-expression>+ ")"
31 <expr> := <name> ["-" <options>] ":" <text>
32 <name> := CHAR+
33 <options> := CHAR+
34 <text> := ANYCHAR-EXCEPT-WHITESPACE | "\"" QUOTEDANYCHAR "\""
35 */
36
37 static GSList *
ffe_tokenize_words(const gchar * ffe)38 ffe_tokenize_words (const gchar *ffe)
39 {
40 GSList *words = NULL;
41 const gchar *ptr, *start;
42 gboolean in_quotes = FALSE;
43
44 if (!ffe)
45 return NULL;
46
47 for (ptr = ffe, start = ptr; ptr == ffe || ptr[-1] != 0; ptr++) {
48 if (in_quotes && (*ptr == '\"' || !*ptr)) {
49 if (ptr[1] == '\"') {
50 ptr++;
51 } else {
52 gchar *qword;
53 gint ii, jj;
54
55 in_quotes = FALSE;
56
57 qword = g_malloc (ptr - start + 2);
58
59 /* tab (\t) as the first character is a marker
60 that the string was quoted */
61 qword[0] = '\t';
62 jj = 1;
63
64 /* convert double-quotes (\"\") into single quotes (\") */
65 for (ii = 0; ii < ptr - start; ii++, jj++) {
66 qword[jj] = start[ii];
67
68 if (start[ii] == '\"') {
69 if (start[ii + 1] == '\"')
70 ii++;
71 }
72 }
73
74 qword[jj] = '\0';
75
76 words = g_slist_prepend (words, qword);
77 start = ptr + 1;
78 }
79 } else if (*ptr == '\"' && (ptr == ffe ||
80 ptr[-1] == ':' ||
81 ptr[-1] == '(' ||
82 ptr[-1] == ' ' ||
83 ptr[-1] == '\t' ||
84 ptr[-1] == '\n' ||
85 ptr[-1] == '\r')) {
86 if (ptr > start) {
87 words = g_slist_prepend (words, g_strndup (start, ptr - start));
88 }
89 in_quotes = TRUE;
90 start = ptr + 1;
91 } else if (!in_quotes) {
92 /* word separators */
93 if ((*ptr == '(' && start != ptr) || *ptr == ')' || *ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r' || !*ptr) {
94 if (ptr > start || (ptr >= start && *ptr == '(')) {
95 words = g_slist_prepend (words, g_strndup (start, ptr - start + (*ptr == '(' ? 1 : 0)));
96 }
97
98 if (*ptr == ')') {
99 words = g_slist_prepend (words, g_strdup (")"));
100 }
101
102 start = ptr + 1;
103 }
104 }
105 }
106
107 return g_slist_reverse (words);
108 }
109
110 static const EFreeFormExpSymbol *
ffe_find_symbol_for(const EFreeFormExpSymbol * symbols,const gchar * word)111 ffe_find_symbol_for (const EFreeFormExpSymbol *symbols,
112 const gchar *word)
113 {
114 const gchar *colon;
115 gint ii, jj, kk;
116
117 g_return_val_if_fail (symbols != NULL, NULL);
118 g_return_val_if_fail (word != NULL, NULL);
119
120 colon = strchr (word, ':');
121
122 if (colon <= word && *word)
123 return NULL;
124
125 for (ii = 0; symbols[ii].names; ii++) {
126 const gchar *names = symbols[ii].names;
127
128 if (!*word && !*names)
129 return &(symbols[ii]);
130
131 if (!*word)
132 continue;
133
134 for (kk = 0; names[kk]; kk++) {
135 for (jj = 0; word[jj] && names[kk + jj] && names[kk + jj] != ':'; jj++) {
136 if (g_ascii_toupper (word[jj]) != g_ascii_toupper (names[kk + jj]) || word[jj] == '-' || word[jj] == ':')
137 break;
138 }
139
140 if ((word[jj] == '-' || word[jj] == ':') && (names[kk + jj] == ':' || !names[kk + jj]))
141 return &(symbols[ii]);
142
143 while (names[kk] && names[kk] != ':')
144 kk++;
145
146 if (!names[kk])
147 break;
148 }
149 }
150
151 return NULL;
152 }
153
154 static gboolean
ffe_process_word(const EFreeFormExpSymbol * symbols,const gchar * in_word,const gchar * next_word,GString ** psexp)155 ffe_process_word (const EFreeFormExpSymbol *symbols,
156 const gchar *in_word,
157 const gchar *next_word,
158 GString **psexp)
159 {
160 GString *sexp;
161 gchar *options = NULL, *subsexp;
162 const EFreeFormExpSymbol *symbol = NULL;
163 gboolean used_next_word = FALSE;
164
165 g_return_val_if_fail (symbols != NULL, FALSE);
166 g_return_val_if_fail (in_word != NULL, FALSE);
167 g_return_val_if_fail (psexp != NULL, FALSE);
168
169 if (*in_word == '\t') {
170 /* tab (\t) as the first character is a marker
171 that the string was quoted */
172 in_word++;
173 } else {
174 gchar *word = NULL;
175 const gchar *dash, *colon;
176
177 /* <function>[-<options>]:values */
178 dash = strchr (in_word, '-');
179 colon = strchr (in_word, ':');
180
181 if (colon > in_word) {
182 if (dash > in_word && dash < colon) {
183 options = g_strndup (dash + 1, colon - dash - 1);
184 word = g_strndup (in_word, dash - in_word + 1);
185 word[dash - in_word] = ':';
186 } else {
187 word = g_strndup (in_word, colon - in_word + 1);
188 }
189 }
190
191 if (word) {
192 symbol = ffe_find_symbol_for (symbols, word);
193 if (!symbol) {
194 g_free (options);
195 options = NULL;
196 } else if (colon[1]) {
197 in_word = colon + 1;
198 } else if (next_word) {
199 in_word = next_word;
200 if (*in_word == '\t')
201 in_word++;
202 used_next_word = TRUE;
203 } else {
204 g_free (options);
205 options = NULL;
206 }
207
208 g_free (word);
209 }
210 }
211
212 if (!symbol)
213 symbol = ffe_find_symbol_for (symbols, "");
214
215 g_return_val_if_fail (symbol != NULL, FALSE);
216 g_return_val_if_fail (symbol->build_sexp != NULL, FALSE);
217
218 sexp = *psexp;
219 subsexp = symbol->build_sexp (in_word, options, symbol->hint);
220
221 if (subsexp && *subsexp) {
222 if (!sexp) {
223 sexp = g_string_new (subsexp);
224 } else {
225 g_string_append (sexp, subsexp);
226 }
227 }
228
229 g_free (options);
230 g_free (subsexp);
231
232 *psexp = sexp;
233
234 return used_next_word;
235 }
236
237 static void
ffe_finish_and_or_not(GString * sexp)238 ffe_finish_and_or_not (GString *sexp)
239 {
240 g_return_if_fail (sexp != NULL);
241
242 if (sexp->len > 4) {
243 if (g_str_has_suffix (sexp->str + sexp->len - 5, "(and ") ||
244 g_str_has_suffix (sexp->str + sexp->len - 5, "(not ")) {
245 g_string_truncate (sexp, sexp->len - 5);
246 } else if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
247 g_string_truncate (sexp, sexp->len - 4);
248 } else {
249 g_string_append_c (sexp, ')');
250 }
251 } else if (sexp->len == 4) {
252 if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
253 g_string_truncate (sexp, sexp->len - 4);
254 } else {
255 g_string_append_c (sexp, ')');
256 }
257 } else {
258 g_string_append_c (sexp, ')');
259 }
260 }
261
262 /**
263 * e_free_form_exp_to_sexp:
264 * @free_form_exp: a Free Form Expression
265 * @symbols: known symbols, which can be used in the Free From Expression
266 *
267 * Converts the @free_form_exp to an S-Expression using the S-Expression
268 * builders defined in the @symbols. The @symbols should have one symbol
269 * with an empty string as its name, which is used for words which do not
270 * have a symbol name prefix.
271 *
272 * The @symbols is a NULL-terminated array of known symbols. The NULL should
273 * be set for the symbol's name.
274 *
275 * Returns: converted @free_form_exp into S-Expression, %NULL on error.
276 * Free the returned string with a g_free(), when done with it.
277 *
278 * Since: 3.16
279 **/
280 gchar *
e_free_form_exp_to_sexp(const gchar * free_form_exp,const EFreeFormExpSymbol * symbols)281 e_free_form_exp_to_sexp (const gchar *free_form_exp,
282 const EFreeFormExpSymbol *symbols)
283 {
284 GSList *raw_words, *link;
285 GString *sexp = NULL;
286 gint deep_stack = 0;
287
288 g_return_val_if_fail (free_form_exp != NULL, NULL);
289 g_return_val_if_fail (symbols != NULL, NULL);
290
291 raw_words = ffe_tokenize_words (free_form_exp);
292
293 for (link = raw_words; link; link = g_slist_next (link)) {
294 const gchar *word = link->data;
295
296 if (!word)
297 continue;
298
299 if (*word == '\t') {
300 /* tab (\t) as the first character is a marker
301 that the string was quoted */
302 ffe_process_word (symbols, word + 1, NULL, &sexp);
303 } else if (g_ascii_strncasecmp (word, "not:(", 5) == 0 ||
304 g_ascii_strncasecmp (word, "and:(", 5) == 0 ||
305 g_ascii_strncasecmp (word, "or:(", 4) == 0) {
306 if (!sexp)
307 sexp = g_string_new ("");
308
309 if (g_ascii_tolower (*word) == 'n')
310 g_string_append (sexp, "(not ");
311 else if (g_ascii_tolower (*word) == 'a')
312 g_string_append (sexp, "(and ");
313 else
314 g_string_append (sexp, "(or ");
315
316 deep_stack++;
317 } else if (g_ascii_strcasecmp (word, ")") == 0) {
318 if (deep_stack) {
319 g_return_val_if_fail (sexp != NULL, NULL);
320
321 ffe_finish_and_or_not (sexp);
322 deep_stack--;
323 }
324 } else {
325 if (ffe_process_word (symbols, word, link->next ? link->next->data : NULL, &sexp))
326 link = g_slist_next (link);
327 }
328 }
329
330 g_slist_free_full (raw_words, g_free);
331
332 while (deep_stack > 0) {
333 ffe_finish_and_or_not (sexp);
334 deep_stack--;
335 }
336
337 if (sexp) {
338 g_string_prepend (sexp, "(and ");
339 g_string_append_c (sexp, ')');
340 }
341
342 return sexp ? g_string_free (sexp, FALSE) : NULL;
343 }
344