[devhelp] KeywordModel: delegate DhLink matching to DhSearchContext



commit 30075bd6e78189c1863c2cc71c7c541753c9b486
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Jan 18 14:45:43 2018 +0100

    KeywordModel: delegate DhLink matching to DhSearchContext
    
    The implementation is different.
    
    Now only two GPatternSpec's are created (prefix and nonprefix), not for
    each keyword separately. All keywords are concatenated with the '*' glob
    in-between.
    
    For example searching:
    "gtk_source_region region" is now equivalent to:
    "gtk_source_region*region".
    
    It's at least the behavior that I expect when I do a search with several
    keywords.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=782257

 src/dh-keyword-model.c  |  163 +++--------------------------------------------
 src/dh-search-context.c |   95 +++++++++++++++++++++++++++
 src/dh-search-context.h |    6 ++
 3 files changed, 110 insertions(+), 154 deletions(-)
---
diff --git a/src/dh-keyword-model.c b/src/dh-keyword-model.c
index 9b5bed9..30d079d 100644
--- a/src/dh-keyword-model.c
+++ b/src/dh-keyword-model.c
@@ -51,21 +51,10 @@ typedef struct {
         gint stamp;
 } DhKeywordModelPrivate;
 
-/* Store a keyword as well as glob
- * patterns that match at the start of a word
- * and one that matches in any position of a
- * word */
-typedef struct {
-        gchar *keyword;
-        GPatternSpec *glob_pattern_start;
-        GPatternSpec *glob_pattern_any;
-        guint has_glob : 1;
-} DhKeywordGlobPattern;
-
 typedef struct {
+        DhSearchContext *search_context;
         const gchar *search_string;
         GStrv keywords;
-        GList *keyword_globs;
         const gchar *book_id;
         const gchar *skip_book_id;
         const gchar *page_id;
@@ -369,73 +358,6 @@ dh_keyword_model_new (void)
         return g_object_new (DH_TYPE_KEYWORD_MODEL, NULL);
 }
 
-/* For each keyword, creates a DhKeywordGlobPattern with GPatternSpec's
- * allocated if there are any special glob characters ('*', '?') in the keyword.
- */
-static GList *
-dh_globbed_keywords_new (GStrv keywords)
-{
-        GList *list = NULL;
-        gint i;
-
-        if (keywords == NULL) {
-                return NULL;
-        }
-
-        for (i = 0; keywords[i] != NULL; i++) {
-                DhKeywordGlobPattern *data;
-
-                data = g_slice_new (DhKeywordGlobPattern);
-                data->keyword = keywords[i];
-
-                if (strchr (keywords[i], '*') != NULL ||
-                    strchr (keywords[i], '?') != NULL) {
-                        gchar *glob;
-
-                        data->has_glob = TRUE;
-
-                        /* g_pattern_match matches whole strings only, so
-                         * for globs, we need to end with a star for partial matches */
-                        glob = g_strdup_printf ("%s*", keywords[i]);
-                        data->glob_pattern_start = g_pattern_spec_new (glob);
-                        g_free (glob);
-
-                        glob = g_strdup_printf ("*%s*", keywords[i]);
-                        data->glob_pattern_any = g_pattern_spec_new (glob);
-                        g_free (glob);
-                } else {
-                        data->has_glob = FALSE;
-                }
-
-                list = g_list_prepend (list, data);
-        }
-
-        return g_list_reverse (list);
-}
-
-/* Frees all the datastructures and patterns associated with
- * keyword_globs as well as keyword_globs itself.  It does not free
- * DhKeywordGlobPattern->keyword however (only the pattern specs).
- */
-static void
-dh_globbed_keywords_free (GList *keyword_globs)
-{
-        GList *l;
-
-        for (l = keyword_globs; l != NULL; l = l->next) {
-                DhKeywordGlobPattern *data = l->data;
-
-                if (data->has_glob) {
-                        g_pattern_spec_free (data->glob_pattern_start);
-                        g_pattern_spec_free (data->glob_pattern_any);
-                }
-
-                g_slice_free (DhKeywordGlobPattern, data);
-        }
-
-        g_list_free (keyword_globs);
-}
-
 static GQueue *
 search_single_book (DhBook          *book,
                     SearchSettings  *settings,
@@ -449,77 +371,12 @@ search_single_book (DhBook          *book,
 
         for (l = dh_book_get_links (book);
              l != NULL && ret->length < max_hits;
-             l = g_list_next (l)) {
-                DhLink *link;
-                gboolean found;
-
-                link = l->data;
-                found = FALSE;
-
-                /* Filter by page? */
-                if (settings->page_id != NULL) {
-                        if (!dh_link_belongs_to_page (link,
-                                                      settings->page_id,
-                                                      settings->case_sensitive)) {
-                                /* No need of this keyword. */
-                                continue;
-                        }
-
-                        /* This means we got no keywords to look for. */
-                        if (settings->keywords == NULL) {
-                                /* Show all in the page */
-                                found = TRUE;
-                        }
-                }
-
-                if (!found && settings->keywords != NULL) {
-                        gboolean all_found;
-                        gboolean prefix_found;
-                        gchar *name;
-                        GList *list;
-
-                        name = (settings->case_sensitive ?
-                                g_strdup (dh_link_get_name (link)) :
-                                g_ascii_strdown (dh_link_get_name (link), -1));
-
-                        all_found = TRUE;
-                        prefix_found = FALSE;
-                        for (list = settings->keyword_globs; list != NULL; list = g_list_next (list)) {
-                                DhKeywordGlobPattern *data = list->data;
-
-                                /* If our keyword is a glob pattern, use
-                                 * it.  Otherwise, do more efficient string searching */
-                                if (data->has_glob) {
-                                        if (g_pattern_match_string (data->glob_pattern_start, name)) {
-                                                prefix_found = TRUE;
-                                                /* If we get a prefix match and we're not
-                                                 * looking for prefix, stop. */
-                                                if (!settings->prefix)
-                                                        break;
-                                        } else if (!g_pattern_match_string (data->glob_pattern_any, name)) {
-                                                all_found = FALSE;
-                                                break;
-                                        }
-                                } else {
-                                        if (g_str_has_prefix (name, data->keyword)) {
-                                                prefix_found = TRUE;
-                                                if (!settings->prefix)
-                                                        break;
-                                        } else if (g_strrstr (name, data->keyword) == NULL) {
-                                                all_found = FALSE;
-                                                break;
-                                        }
-                                }
-                        }
-
-                        g_free (name);
-
-                        found = all_found &&
-                                ((settings->prefix && prefix_found) ||
-                                 (!settings->prefix && !prefix_found));
-                }
+             l = l->next) {
+                DhLink *link = l->data;
 
-                if (found) {
+                if (_dh_search_context_match_link (settings->search_context,
+                                                   link,
+                                                   settings->prefix)) {
                         g_queue_push_tail (ret, link);
 
                         if (exact_link == NULL || dh_link_get_name (link) == NULL)
@@ -641,9 +498,9 @@ keyword_model_search (DhKeywordModel   *model,
         DhLink *other_books_exact_link = NULL;
         GQueue *out = g_queue_new ();
 
+        settings.search_context = search_context;
         settings.search_string = search_string;
         settings.keywords = _dh_search_context_get_keywords (search_context);
-        settings.keyword_globs = dh_globbed_keywords_new (settings.keywords);
         settings.book_id = priv->current_book_id;
         settings.skip_book_id = NULL;
         settings.page_id = _dh_search_context_get_page_id (search_context);
@@ -696,7 +553,7 @@ keyword_model_search (DhKeywordModel   *model,
         }
 
         if (out->length >= max_hits)
-                goto out;
+                return out;
 
         /* Look for non-prefixed matches in current book. */
         settings.prefix = FALSE;
@@ -712,7 +569,7 @@ keyword_model_search (DhKeywordModel   *model,
 
                 dh_util_queue_concat (out, in_book);
                 if (out->length >= max_hits)
-                        goto out;
+                        return out;
         }
 
         /* If still room for more items, look for non-prefixed items in other
@@ -726,8 +583,6 @@ keyword_model_search (DhKeywordModel   *model,
                                     NULL);
         dh_util_queue_concat (out, other_books);
 
-out:
-        dh_globbed_keywords_free (settings.keyword_globs);
         return out;
 }
 
diff --git a/src/dh-search-context.c b/src/dh-search-context.c
index 671a727..192d952 100644
--- a/src/dh-search-context.c
+++ b/src/dh-search-context.c
@@ -20,9 +20,19 @@
 #include <string.h>
 
 struct _DhSearchContext {
+        /* The content of the search string */
         gchar *book_id;
         gchar *page_id;
+
+        // If non-NULL, contains at least one non-empty string.
         GStrv keywords;
+
+        /* Derived data */
+
+        // The GPatternSpec's are NULL if @keywords is NULL.
+        GPatternSpec *pattern_spec_prefix;
+        GPatternSpec *pattern_spec_nonprefix;
+
         guint case_sensitive : 1;
 };
 
@@ -176,6 +186,39 @@ set_case_sensitive (DhSearchContext *search)
         }
 }
 
+static void
+create_pattern_specs (DhSearchContext *search)
+{
+        GString *pattern_prefix_string;
+        gchar *pattern_nonprefix_str;
+        gint i;
+
+        g_assert (search->pattern_spec_prefix == NULL);
+        g_assert (search->pattern_spec_nonprefix == NULL);
+
+        if (search->keywords == NULL)
+                return;
+
+        /* Prefix */
+        pattern_prefix_string = g_string_new (NULL);
+
+        for (i = 0; search->keywords[i] != NULL; i++) {
+                const gchar *cur_keyword = search->keywords[i];
+
+                g_string_append (pattern_prefix_string, cur_keyword);
+                g_string_append_c (pattern_prefix_string, '*');
+        }
+
+        search->pattern_spec_prefix = g_pattern_spec_new (pattern_prefix_string->str);
+
+        /* Non-prefix */
+        pattern_nonprefix_str = g_strconcat ("?*", pattern_prefix_string->str, NULL);
+        search->pattern_spec_nonprefix = g_pattern_spec_new (pattern_nonprefix_str);
+
+        g_string_free (pattern_prefix_string, TRUE);
+        g_free (pattern_nonprefix_str);
+}
+
 /* Returns: (transfer full) (nullable): a new #DhSearchContext, or %NULL if
  * @search_string is invalid.
  */
@@ -194,6 +237,7 @@ _dh_search_context_new (const gchar *search_string)
         }
 
         set_case_sensitive (search);
+        create_pattern_specs (search);
 
         return search;
 }
@@ -207,6 +251,13 @@ _dh_search_context_free (DhSearchContext *search)
         g_free (search->book_id);
         g_free (search->page_id);
         g_strfreev (search->keywords);
+
+        if (search->pattern_spec_prefix != NULL)
+                g_pattern_spec_free (search->pattern_spec_prefix);
+
+        if (search->pattern_spec_nonprefix != NULL)
+                g_pattern_spec_free (search->pattern_spec_nonprefix);
+
         g_free (search);
 }
 
@@ -241,3 +292,47 @@ _dh_search_context_get_case_sensitive (DhSearchContext *search)
 
         return search->case_sensitive;
 }
+
+/* This function assumes that checking that the DhBook (book_id) matches has
+ * already been done (to not check the book_id for each DhLink).
+ */
+gboolean
+_dh_search_context_match_link (DhSearchContext *search,
+                               DhLink          *link,
+                               gboolean         prefix)
+{
+        gchar *str_to_free = NULL;
+        const gchar *name;
+        gboolean match = FALSE;
+
+        g_return_val_if_fail (search != NULL, FALSE);
+        g_return_val_if_fail (link != NULL, FALSE);
+
+        /* Filter by page? */
+        if (search->page_id != NULL) {
+                if (!dh_link_belongs_to_page (link, search->page_id, TRUE))
+                        return FALSE;
+
+                if (search->keywords == NULL)
+                        /* Show all in the page. */
+                        return TRUE;
+        }
+
+        if (search->keywords == NULL)
+                return FALSE;
+
+        if (search->case_sensitive) {
+                name = dh_link_get_name (link);
+        } else {
+                str_to_free = g_ascii_strdown (dh_link_get_name (link), -1);
+                name = str_to_free;
+        }
+
+        if (prefix)
+                match = g_pattern_match_string (search->pattern_spec_prefix, name);
+        else
+                match = g_pattern_match_string (search->pattern_spec_nonprefix, name);
+
+        g_free (str_to_free);
+        return match;
+}
diff --git a/src/dh-search-context.h b/src/dh-search-context.h
index 2f748a1..ea00d92 100644
--- a/src/dh-search-context.h
+++ b/src/dh-search-context.h
@@ -20,6 +20,7 @@
 #define DH_SEARCH_CONTEXT_H
 
 #include <glib.h>
+#include "dh-link.h"
 
 G_BEGIN_DECLS
 
@@ -43,6 +44,11 @@ GStrv                   _dh_search_context_get_keywords         (DhSearchContext
 G_GNUC_INTERNAL
 gboolean                _dh_search_context_get_case_sensitive   (DhSearchContext *search);
 
+G_GNUC_INTERNAL
+gboolean                _dh_search_context_match_link           (DhSearchContext *search,
+                                                                 DhLink          *link,
+                                                                 gboolean         prefix);
+
 G_END_DECLS
 
 #endif /* DH_SEARCH_CONTEXT_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]