[evolution-data-server] Bug 550796 - Implement free form filter expression
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] Bug 550796 - Implement free form filter expression
- Date: Tue, 9 Dec 2014 08:16:22 +0000 (UTC)
commit 05edaf7f9f8922d95578f9e3a4b31e809ced6c89
Author: Milan Crha <mcrha redhat com>
Date: Tue Dec 9 09:15:01 2014 +0100
Bug 550796 - Implement free form filter expression
Shared helper code, maybe not only for the Evolution.
libedataserver/Makefile.am | 2 +
libedataserver/e-free-form-exp.c | 352 ++++++++++++++++++++++++++++++++++++++
libedataserver/e-free-form-exp.h | 44 +++++
libedataserver/libedataserver.h | 1 +
4 files changed, 399 insertions(+), 0 deletions(-)
---
diff --git a/libedataserver/Makefile.am b/libedataserver/Makefile.am
index aad5c5a..c41474e 100644
--- a/libedataserver/Makefile.am
+++ b/libedataserver/Makefile.am
@@ -53,6 +53,7 @@ libedataserver_1_2_la_SOURCES = \
e-collator.c \
e-credentials.c \
e-flag.c \
+ e-free-form-exp.c \
e-gdbus-templates.c \
e-iterator.c \
e-list.c \
@@ -131,6 +132,7 @@ libedataserverinclude_HEADERS = \
e-collator.h \
e-credentials.h \
e-flag.h \
+ e-free-form-exp.h \
e-gdbus-templates.h \
e-iterator.h \
e-list.h \
diff --git a/libedataserver/e-free-form-exp.c b/libedataserver/e-free-form-exp.c
new file mode 100644
index 0000000..667bdf7
--- /dev/null
+++ b/libedataserver/e-free-form-exp.c
@@ -0,0 +1,352 @@
+/*
+ * e-free-form-exp.c
+ *
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e-free-form-exp.h"
+
+/* <free-form-expression> := <token> *(" " <token>)
+ <token> := <and> | <or> | <not> | <expr>
+ <and> := "and:(" <free-form-expression>+ ")"
+ <or> := "or:(" <free-form-expression>+ ")"
+ <not> := "not:(" <free-form-expression>+ ")"
+ <expr> := <name> ["-" <options>] ":" <text>
+ <name> := CHAR+
+ <options> := CHAR+
+ <text> := ANYCHAR-EXCEPT-WHITESPACE | "\"" QUOTEDANYCHAR "\""
+*/
+
+static GSList *
+ffe_tokenize_words (const gchar *ffe)
+{
+ GSList *words = NULL;
+ const gchar *ptr, *start;
+ gboolean in_quotes = FALSE;
+
+ if (!ffe)
+ return NULL;
+
+ for (ptr = ffe, start = ptr; ptr == ffe || ptr[-1] != 0; ptr++) {
+ if (in_quotes && (*ptr == '\"' || !*ptr)) {
+ if (ptr[1] == '\"') {
+ ptr++;
+ } else {
+ gchar *qword;
+ gint ii, jj;
+
+ in_quotes = FALSE;
+
+ qword = g_malloc (ptr - start + 2);
+
+ /* tab (\t) as the first character is a marker
+ that the string was quoted */
+ qword[0] = '\t';
+ jj = 1;
+
+ /* convert double-quotes (\"\") into single quotes (\") */
+ for (ii = 0; ii < ptr - start; ii++, jj++) {
+ qword[jj] = start[ii];
+
+ if (start[ii] == '\"') {
+ if (start[ii + 1] == '\"')
+ ii++;
+ }
+ }
+
+ qword[jj] = '\0';
+
+ words = g_slist_prepend (words, qword);
+ start = ptr + 1;
+ }
+ } else if (*ptr == '\"' && (ptr == ffe ||
+ ptr[-1] == ':' ||
+ ptr[-1] == '(' ||
+ ptr[-1] == ' ' ||
+ ptr[-1] == '\t' ||
+ ptr[-1] == '\n' ||
+ ptr[-1] == '\r')) {
+ if (ptr > start) {
+ words = g_slist_prepend (words, g_strndup (start, ptr - start));
+ }
+ in_quotes = TRUE;
+ start = ptr + 1;
+ } else if (!in_quotes) {
+ /* word separators */
+ if ((*ptr == '(' && start != ptr) || *ptr == ')' || *ptr == ' ' || *ptr == '\t' ||
*ptr == '\n' || *ptr == '\r' || !*ptr) {
+ if (ptr > start || (ptr >= start && *ptr == '(')) {
+ words = g_slist_prepend (words, g_strndup (start, ptr - start + (*ptr
== '(' ? 1 : 0)));
+ }
+
+ if (*ptr == ')') {
+ words = g_slist_prepend (words, g_strdup (")"));
+ }
+
+ start = ptr + 1;
+ }
+ }
+ }
+
+ return g_slist_reverse (words);
+}
+
+static const EFreeFormExpSymbol *
+ffe_find_symbol_for (const EFreeFormExpSymbol *symbols,
+ const gchar *word)
+{
+ const gchar *colon;
+ gint ii, jj, kk;
+
+ g_return_val_if_fail (symbols != NULL, NULL);
+ g_return_val_if_fail (word != NULL, NULL);
+
+ colon = strchr (word, ':');
+
+ if (colon <= word && *word)
+ return NULL;
+
+ for (ii = 0; symbols[ii].names; ii++) {
+ const gchar *names = symbols[ii].names;
+
+ if (!*word && !*names)
+ return &(symbols[ii]);
+
+ if (!*word)
+ continue;
+
+ for (kk = 0; names[kk]; kk++) {
+ for (jj = 0; word[jj] && names[kk + jj] && names[kk + jj] != ':'; jj++) {
+ if (g_ascii_toupper (word[jj]) != g_ascii_toupper (names[kk + jj]) ||
word[jj] == '-' || word[jj] == ':')
+ break;
+ }
+
+ if ((word[jj] == '-' || word[jj] == ':') && (names[kk + jj] == ':' || !names[kk +
jj]))
+ return &(symbols[ii]);
+
+ while (names[kk] && names[kk] != ':')
+ kk++;
+
+ if (!names[kk])
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+ffe_process_word (const EFreeFormExpSymbol *symbols,
+ const gchar *in_word,
+ const gchar *next_word,
+ GString **psexp)
+{
+ GString *sexp;
+ gchar *word = NULL, *options = NULL, *subsexp;
+ const EFreeFormExpSymbol *symbol = NULL;
+ gboolean used_next_word = FALSE;
+
+ g_return_val_if_fail (symbols != NULL, FALSE);
+ g_return_val_if_fail (in_word != NULL, FALSE);
+ g_return_val_if_fail (psexp != NULL, FALSE);
+
+ if (*in_word == '\t') {
+ /* tab (\t) as the first character is a marker
+ that the string was quoted */
+ in_word++;
+ } else {
+ const gchar *dash, *colon;
+
+ /* <function>[-<options>]:values */
+ dash = strchr (in_word, '-');
+ colon = strchr (in_word, ':');
+
+ if (colon > in_word) {
+ if (dash > in_word && dash < colon) {
+ options = g_strndup (dash + 1, colon - dash - 1);
+ word = g_strndup (in_word, dash - in_word + 1);
+ word[dash - in_word] = ':';
+ } else {
+ word = g_strndup (in_word, colon - in_word + 1);
+ }
+ }
+
+ if (word) {
+ symbol = ffe_find_symbol_for (symbols, word);
+ if (!symbol) {
+ g_free (word);
+ g_free (options);
+ word = NULL;
+ options = NULL;
+ } else if (colon[1]) {
+ g_free (word);
+ word = NULL;
+ in_word = colon + 1;
+ } else if (next_word) {
+ g_free (word);
+ word = NULL;
+
+ in_word = next_word;
+ if (*in_word == '\t')
+ in_word++;
+ used_next_word = TRUE;
+ } else {
+ g_free (word);
+ g_free (options);
+ word = NULL;
+ options = NULL;
+ }
+ }
+ }
+
+ if (!symbol)
+ symbol = ffe_find_symbol_for (symbols, "");
+
+ g_return_val_if_fail (symbol != NULL, FALSE);
+ g_return_val_if_fail (symbol->build_sexp != NULL, FALSE);
+
+ sexp = *psexp;
+ subsexp = symbol->build_sexp (word ? word : in_word, options, symbol->hint);
+
+ if (subsexp && *subsexp) {
+ if (!sexp) {
+ sexp = g_string_new (subsexp);
+ } else {
+ g_string_append (sexp, subsexp);
+ }
+ }
+
+ g_free (word);
+ g_free (options);
+ g_free (subsexp);
+
+ *psexp = sexp;
+
+ return used_next_word;
+}
+
+static void
+ffe_finish_and_or_not (GString *sexp)
+{
+ g_return_if_fail (sexp != NULL);
+
+ if (sexp->len > 4) {
+ if (g_str_has_suffix (sexp->str + sexp->len - 5, "(and ") ||
+ g_str_has_suffix (sexp->str + sexp->len - 5, "(not ")) {
+ g_string_truncate (sexp, sexp->len - 5);
+ } else if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
+ g_string_truncate (sexp, sexp->len - 4);
+ } else {
+ g_string_append (sexp, ")");
+ }
+ } else if (sexp->len == 4) {
+ if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
+ g_string_truncate (sexp, sexp->len - 4);
+ } else {
+ g_string_append (sexp, ")");
+ }
+ } else {
+ g_string_append (sexp, ")");
+ }
+}
+
+/**
+ * e_free_form_exp_to_sexp:
+ * @free_form_exp: a Free Form Expression
+ * @symbols: known symbols, which can be used in the Free From Expression
+ *
+ * Converts the @free_form_exp to an S-Expression using the S-Expression
+ * builders defined in the @symbols. The @symbols should have one symbol
+ * with an empty string as its name, which is used for words which do not
+ * have a symbol name prefix.
+ *
+ * The @symbols is a NULL-terminated array of known symbols. The NULL should
+ * be set for the symbol's name.
+ *
+ * Returns: converted @free_form_exp into S-Expression, %NULL on error.
+ * Free the returned string with a g_free(), when done with it.
+ *
+ * Since: 3.14
+ **/
+gchar *
+e_free_form_exp_to_sexp (const gchar *free_form_exp,
+ const EFreeFormExpSymbol *symbols)
+{
+ GSList *raw_words, *link;
+ GString *sexp = NULL;
+ gint deep_stack = 0;
+
+ g_return_val_if_fail (free_form_exp != NULL, NULL);
+ g_return_val_if_fail (symbols != NULL, NULL);
+
+ raw_words = ffe_tokenize_words (free_form_exp);
+
+ for (link = raw_words; link; link = g_slist_next (link)) {
+ const gchar *word = link->data;
+
+ if (!word)
+ continue;
+
+ if (*word == '\t') {
+ /* tab (\t) as the first character is a marker
+ that the string was quoted */
+ ffe_process_word (symbols, word + 1, NULL, &sexp);
+ } else if (g_ascii_strncasecmp (word, "not:(", 5) == 0 ||
+ g_ascii_strncasecmp (word, "and:(", 5) == 0 ||
+ g_ascii_strncasecmp (word, "or:(", 4) == 0) {
+ if (!sexp)
+ sexp = g_string_new ("");
+
+ if (g_ascii_tolower (*word) == 'n')
+ g_string_append (sexp, "(not ");
+ else if (g_ascii_tolower (*word) == 'a')
+ g_string_append (sexp, "(and ");
+ else
+ g_string_append (sexp, "(or ");
+
+ deep_stack++;
+ } else if (g_ascii_strcasecmp (word, ")") == 0) {
+ if (deep_stack) {
+ g_return_val_if_fail (sexp != NULL, NULL);
+
+ ffe_finish_and_or_not (sexp);
+ deep_stack--;
+ }
+ } else {
+ if (ffe_process_word (symbols, word, link->next ? link->next->data : NULL, &sexp))
+ link = g_slist_next (link);
+ }
+ }
+
+ g_slist_free_full (raw_words, g_free);
+
+ while (deep_stack > 0) {
+ ffe_finish_and_or_not (sexp);
+ deep_stack--;
+ }
+
+ if (sexp) {
+ g_string_prepend (sexp, "(and ");
+ g_string_append (sexp, ")");
+ }
+
+ return sexp ? g_string_free (sexp, FALSE) : NULL;
+}
diff --git a/libedataserver/e-free-form-exp.h b/libedataserver/e-free-form-exp.h
new file mode 100644
index 0000000..e34358c
--- /dev/null
+++ b/libedataserver/e-free-form-exp.h
@@ -0,0 +1,44 @@
+/*
+ * e-free-form-exp.h
+ *
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_FREE_FORM_EXP_H
+#define E_FREE_FORM_EXP_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EFreeFormExpSymbol {
+ const gchar *names; /* names (alternative separated by a colon (':')); use an empty string for a
default sexp builder */
+ const gchar *hint; /* passed into build_sexp */
+ gchar * (*build_sexp)(const gchar *word,
+ const gchar *options,
+ const gchar *hint);
+} EFreeFormExpSymbol;
+
+gchar * e_free_form_exp_to_sexp (const gchar *free_form_exp,
+ const EFreeFormExpSymbol *symbols);
+
+G_END_DECLS
+
+#endif /* E_FREE_FORM_EXP_H */
diff --git a/libedataserver/libedataserver.h b/libedataserver/libedataserver.h
index 169f8eb..26dbe6b 100644
--- a/libedataserver/libedataserver.h
+++ b/libedataserver/libedataserver.h
@@ -28,6 +28,7 @@
#include <libedataserver/e-data-server-util.h>
#include <libedataserver/e-debug-log.h>
#include <libedataserver/e-flag.h>
+#include <libedataserver/e-free-form-exp.h>
#include <libedataserver/e-gdbus-templates.h>
#include <libedataserver/e-iterator.h>
#include <libedataserver/e-list-iterator.h>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]