/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * e-searching-tokenizer.c * * Copyright (C) 2001 Ximian, Inc. * * Developed by Jon Trowbridge */ /* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ #include #include #include #include #include "e-searching-tokenizer.h" enum { EST_MATCH_SIGNAL, EST_LAST_SIGNAL }; guint e_searching_tokenizer_signals[EST_LAST_SIGNAL] = { 0 }; #define START_MAGIC "<\n>S<\n>" #define END_MAGIC "<\n>E<\n>" static void e_searching_tokenizer_begin (HTMLTokenizer *, gchar *); static void e_searching_tokenizer_end (HTMLTokenizer *); static gchar *e_searching_tokenizer_peek_token (HTMLTokenizer *); static gchar *e_searching_tokenizer_next_token (HTMLTokenizer *); static gboolean e_searching_tokenizer_has_more (HTMLTokenizer *); static HTMLTokenizer *e_searching_tokenizer_clone (HTMLTokenizer *); static const gchar *ignored_tags[] = { "b", "i", NULL }; static const gchar *space_tags[] = { "br", NULL }; GtkObjectClass *parent_class = NULL; /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ typedef enum { MATCH_FAILED = 0, MATCH_COMPLETE, MATCH_START, MATCH_CONTINUES, MATCH_END } MatchInfo; typedef struct _SearchInfo SearchInfo; struct _SearchInfo { gchar *search; gchar *current; gboolean case_sensitive; gboolean allow_space_tags_to_match_whitespace; gint match_size_incr; gchar *match_color; gboolean match_bold; }; struct _ESearchingTokenizerPrivate { gint match_count; SearchInfo *search; GList *pending; GList *trash; gchar *str_primary; gchar *str_secondary; gboolean case_sensitive_primary; gboolean case_sensitive_secondary; }; /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ static SearchInfo * search_info_new (void) { SearchInfo *si; si = g_new0 (SearchInfo, 1); si->case_sensitive = FALSE; si->match_size_incr = 1; si->match_color = g_strdup ("red"); si->match_bold = FALSE; si->allow_space_tags_to_match_whitespace = TRUE; return si; } static void search_info_free (SearchInfo *si) { if (si) { g_free (si->search); g_free (si->match_color); g_free (si); } } static SearchInfo * search_info_clone (SearchInfo *si) { SearchInfo *new_si = NULL; if (si) { new_si = search_info_new (); new_si->search = g_strdup (si->search); new_si->case_sensitive = si->case_sensitive; } return new_si; } static void search_info_set_string (SearchInfo *si, const gchar *str) { g_return_if_fail (si); g_return_if_fail (str); g_free (si->search); si->search = g_strdup (str); si->current = NULL; } static void search_info_set_case_sensitivity (SearchInfo *si, gboolean flag) { g_return_if_fail (si); si->case_sensitive = flag; } static void search_info_set_match_size_increase (SearchInfo *si, gint incr) { g_return_if_fail (si); g_return_if_fail (incr >= 0); si->match_size_incr = incr; } static void search_info_set_match_color (SearchInfo *si, const gchar *color) { g_return_if_fail (si); g_free (si->match_color); si->match_color = g_strdup (color); } static void search_info_set_match_bold (SearchInfo *si, gboolean flag) { g_return_if_fail (si); si->match_bold = flag; } static void search_info_reset (SearchInfo *si) { if (si == NULL) return; si->current = NULL; } /* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */ static const gchar * find_whole (SearchInfo *si, const gchar *haystack, const gchar *needle) { const gchar *h, *n; g_return_val_if_fail (si, NULL); g_return_val_if_fail (haystack && needle, NULL); g_return_val_if_fail (g_utf8_validate (haystack, -1, NULL), NULL); g_return_val_if_fail (g_utf8_validate (needle, -1, NULL), NULL); while (*haystack) { h = haystack; n = needle; while (*h && *n) { gunichar c1 = g_utf8_get_char (h); gunichar c2 = g_utf8_get_char (n); if (!si->case_sensitive) { c1 = g_unichar_tolower (c1); c2 = g_unichar_tolower (c2); } if (c1 != c2) break; h = g_utf8_next_char (h); n = g_utf8_next_char (n); } if (*n == '\0') return haystack; if (*h == '\0') return NULL; haystack = g_utf8_next_char (haystack); } return NULL; } /* This is a really stupid implementation of this function. */ static const gchar * find_head (SearchInfo *si, const gchar *haystack, const gchar *needle) { const gchar *h, *n; g_return_val_if_fail (si, NULL); g_return_val_if_fail (haystack && needle, NULL); g_return_val_if_fail (g_utf8_validate (haystack, -1, NULL), NULL); g_return_val_if_fail (g_utf8_validate (needle, -1, NULL), NULL); while (*haystack) { h = haystack; n = needle; while (*h && *n) { gunichar c1 = g_utf8_get_char (h); gunichar c2 = g_utf8_get_char (n); if (!si->case_sensitive) { c1 = g_unichar_tolower (c1); c2 = g_unichar_tolower (c2); } if (c1 != c2) break; h = g_utf8_next_char (h); n = g_utf8_next_char (n); } if (*h == '\0') return haystack; haystack = g_utf8_next_char (haystack); } return NULL; } static const gchar * find_partial (SearchInfo *si, const gchar *haystack, const gchar *needle) { g_return_val_if_fail (si, NULL); g_return_val_if_fail (haystack && needle, NULL); g_return_val_if_fail (g_utf8_validate (haystack, -1, NULL), NULL); g_return_val_if_fail (g_utf8_validate (needle, -1, NULL), NULL); while (*needle) { gunichar c1 = g_utf8_get_char (haystack); gunichar c2 = g_utf8_get_char (needle); if (!si->case_sensitive) { c1 = g_unichar_tolower (c1); c2 = g_unichar_tolower (c2); } if (c1 != c2) return NULL; needle = g_utf8_next_char (needle); haystack = g_utf8_next_char (haystack); } return haystack; } static gboolean tag_match (const gchar *token, const gchar *tag) { token += 2; /* Skip past TAG_ESCAPE and < */ if (*token == '/') ++token; while (*token && *tag) { gunichar c1 = g_unichar_tolower (g_utf8_get_char (token)); gunichar c2 = g_unichar_tolower (g_utf8_get_char (tag)); if (c1 != c2) return FALSE; token = g_utf8_next_char (token); tag = g_utf8_next_char (tag); } return (*tag == '\0' && *token == '>'); } static MatchInfo search_info_compare (SearchInfo *si, const gchar *token, gint *start_pos, gint *end_pos) { gboolean token_is_tag; const gchar *s; gint i; g_return_val_if_fail (si != NULL, MATCH_FAILED); g_return_val_if_fail (token != NULL, MATCH_FAILED); g_return_val_if_fail (start_pos != NULL, MATCH_FAILED); g_return_val_if_fail (end_pos != NULL, MATCH_FAILED); token_is_tag = (*token == TAG_ESCAPE); /* Try to start a new match. */ if (si->current == NULL) { /* A match can never start on a token. */ if (token_is_tag) return MATCH_FAILED; /* Check to see if the search string is entirely embedded within the token. */ s = find_whole (si, token, si->search); if (s) { *start_pos = s - token; *end_pos = *start_pos + g_utf8_strlen (si->search, -1); return MATCH_COMPLETE; } /* Check to see if the beginning of the search string lies in this token. */ s = find_head (si, token, si->search); if (s) { *start_pos = s - token; si->current = si->search; while (*s) { s = g_utf8_next_char (s); si->current = g_utf8_next_char (si->current); } return MATCH_START; } return MATCH_FAILED; } /* Try to continue a previously-started match. */ /* Deal with tags that we encounter mid-match. */ if (token_is_tag) { /* "Ignored tags" will never mess up a match. */ for (i=0; ignored_tags[i]; ++i) { if (tag_match (token, ignored_tags[i])) return MATCH_CONTINUES; } /* "Space tags" only match whitespace in our ongoing match. */ if (si->allow_space_tags_to_match_whitespace && g_unichar_isspace (g_utf8_get_char (si->current))) { for (i=0; space_tags[i]; ++i) { if (tag_match (token, space_tags[i])) { si->current = g_utf8_next_char (si->current); return MATCH_CONTINUES; } } } /* All other tags derail our match. */ return MATCH_FAILED; } s = find_partial (si, token, si->current); if (s) { if (start_pos) *start_pos = 0; if (end_pos) *end_pos = s - token; return MATCH_END; } s = find_partial (si, si->current, token); if (s) { si->current = (gchar *) s; return MATCH_CONTINUES; } return MATCH_FAILED; } /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ static void e_searching_tokenizer_cleanup (ESearchingTokenizer *st) { g_return_if_fail (st && E_IS_SEARCHING_TOKENIZER (st)); if (st->priv->trash) { g_list_foreach (st->priv->trash, (GFunc) g_free, NULL); g_list_free (st->priv->trash); st->priv->trash = NULL; } if (st->priv->pending) { g_list_foreach (st->priv->pending, (GFunc) g_free, NULL); g_list_free (st->priv->pending); st->priv->pending = NULL; } } static void e_searching_tokenizer_destroy (GtkObject *obj) { ESearchingTokenizer *st = E_SEARCHING_TOKENIZER (obj); e_searching_tokenizer_cleanup (st); search_info_free (st->priv->search); g_free (st->priv->str_primary); g_free (st->priv->str_secondary); g_free (st->priv); st->priv = NULL; if (parent_class->destroy) parent_class->destroy (obj); } static void e_searching_tokenizer_class_init (ESearchingTokenizerClass *klass) { GtkObjectClass *obj_class = (GtkObjectClass *) klass; HTMLTokenizerClass *tok_class = HTML_TOKENIZER_CLASS (klass); e_searching_tokenizer_signals[EST_MATCH_SIGNAL] = gtk_signal_new ("match", GTK_RUN_LAST, obj_class->type, GTK_SIGNAL_OFFSET (ESearchingTokenizerClass, match), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (obj_class, e_searching_tokenizer_signals, EST_LAST_SIGNAL); obj_class->destroy = e_searching_tokenizer_destroy; tok_class->begin = e_searching_tokenizer_begin; tok_class->end = e_searching_tokenizer_end; tok_class->peek_token = e_searching_tokenizer_peek_token; tok_class->next_token = e_searching_tokenizer_next_token; tok_class->has_more = e_searching_tokenizer_has_more; tok_class->clone = e_searching_tokenizer_clone; parent_class = gtk_type_class (HTML_TYPE_TOKENIZER); } static void e_searching_tokenizer_init (ESearchingTokenizer *st) { st->priv = g_new0 (struct _ESearchingTokenizerPrivate, 1); } GtkType e_searching_tokenizer_get_type (void) { static GtkType e_searching_tokenizer_type = 0; if (! e_searching_tokenizer_type) { static GtkTypeInfo e_searching_tokenizer_info = { "ESearchingTokenizer", sizeof (ESearchingTokenizer), sizeof (ESearchingTokenizerClass), (GtkClassInitFunc) e_searching_tokenizer_class_init, (GtkObjectInitFunc) e_searching_tokenizer_init, NULL, NULL, (GtkClassInitFunc) NULL }; e_searching_tokenizer_type = gtk_type_unique (HTML_TYPE_TOKENIZER, &e_searching_tokenizer_info); } return e_searching_tokenizer_type; } HTMLTokenizer * e_searching_tokenizer_new (void) { return (HTMLTokenizer *) gtk_type_new (E_TYPE_SEARCHING_TOKENIZER); } /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ static GList * g_list_remove_head (GList *x) { GList *repl = NULL; if (x) { repl = g_list_remove_link (x, x); g_list_free_1 (x); } return repl; } /* I can't believe that there isn't a better way to do this. */ static GList * g_list_insert_before (GList *list, GList *llink, gpointer data) { gint pos = g_list_position (list, llink); return g_list_insert (list, data, pos); } static gchar * pop_pending (ESearchingTokenizer *st) { gchar *token = NULL; if (st->priv->pending) { token = (gchar *) st->priv->pending->data; st->priv->trash = g_list_prepend (st->priv->trash, token); st->priv->pending = g_list_remove_head (st->priv->pending); } return token; } static inline void add_pending (ESearchingTokenizer *st, gchar *tok) { st->priv->pending = g_list_append (st->priv->pending, tok); } static void add_pending_match_begin (ESearchingTokenizer *st, SearchInfo *si) { gchar *size_str = NULL; gchar *color_str= NULL; if (si->match_size_incr > 0) size_str = g_strdup_printf (" size=+%d", si->match_size_incr); if (si->match_color) color_str = g_strdup_printf (" color=%s", si->match_color); if (size_str || color_str) add_pending (st, g_strdup_printf ("%c", TAG_ESCAPE, size_str ? size_str : "", color_str ? color_str : "")); g_free (size_str); g_free (color_str); if (si->match_bold) add_pending (st, g_strdup_printf ("%c", TAG_ESCAPE)); } static void add_pending_match_end (ESearchingTokenizer *st, SearchInfo *si) { if (si->match_bold) add_pending (st, g_strdup_printf ("%c", TAG_ESCAPE)); if (si->match_size_incr > 0 || si->match_color) add_pending (st, g_strdup_printf ("%c", TAG_ESCAPE)); } static void add_to_trash (ESearchingTokenizer *st, gchar *txt) { st->priv->trash = g_list_prepend (st->priv->trash, txt); } static gchar * get_next_token (ESearchingTokenizer *st) { HTMLTokenizer *ht = HTML_TOKENIZER (st); HTMLTokenizerClass *klass = HTML_TOKENIZER_CLASS (parent_class); return klass->has_more (ht) ? klass->next_token (ht) : NULL; } /* * Move the matched part of the queue into pending, replacing the start and end placeholders by * the appropriate tokens. */ static GList * queue_matched (ESearchingTokenizer *st, SearchInfo *si, GList *q) { GList *qh = q; gboolean post_start = FALSE; while (q != NULL) { GList *q_next = g_list_next (q); if (!strcmp ((gchar *) q->data, START_MAGIC)) { add_pending_match_begin (st, si); post_start = TRUE; } else if (!strcmp ((gchar *) q->data, END_MAGIC)) { add_pending_match_end (st, si); q_next = NULL; } else { gboolean is_tag = *((gchar *)q->data) == TAG_ESCAPE; if (is_tag && post_start) add_pending_match_end (st, si); add_pending (st, g_strdup ((gchar *) q->data)); if (is_tag && post_start) add_pending_match_begin (st, si); } qh = g_list_remove_link (qh, q); g_list_free_1 (q); q = q_next; } return qh; } /* * Strip the start and end placeholders out of the queue. */ static GList * queue_match_failed (ESearchingTokenizer *st, GList *q) { GList *qh = q; /* If we do find the START_MAGIC token in the queue, we want to drop everything up to and including the token immediately following START_MAGIC. */ while (q != NULL && strcmp ((gchar *) q->data, START_MAGIC)) q = g_list_next (q); if (q) { q = g_list_next (q); /* If there is no token following START_MAGIC, something is very wrong. */ if (q == NULL) { g_assert_not_reached (); } } /* Otherwise we just want to just drop the the first token. */ if (q == NULL) q = qh; /* Now move everything up to and including q to pending. */ while (qh && qh != q) { if (strcmp ((gchar *) qh->data, START_MAGIC)) add_pending (st, g_strdup (qh->data)); qh = g_list_remove_head (qh); } if (qh == q) { if (strcmp ((gchar *) qh->data, START_MAGIC)) add_pending (st, g_strdup (qh->data)); qh = g_list_remove_head (qh); } return qh; } static void matched (ESearchingTokenizer *st) { ++st->priv->match_count; gtk_signal_emit (GTK_OBJECT (st), e_searching_tokenizer_signals[EST_MATCH_SIGNAL]); } static void get_pending_tokens (ESearchingTokenizer *st) { GList *queue = NULL; gchar *token = NULL; MatchInfo result; gint start_pos, end_pos; GList *start_after = NULL; /* Get an initial token into the queue. */ token = get_next_token (st); if (token) { queue = g_list_append (queue, token); } while (queue) { GList *q; gboolean finished = FALSE; search_info_reset (st->priv->search); if (start_after) { q = g_list_next (start_after); start_after = NULL; } else { q = queue; } while (q) { GList *q_next = g_list_next (q); token = (gchar *) q->data; result = search_info_compare (st->priv->search, token, &start_pos, &end_pos); switch (result) { case MATCH_FAILED: queue = queue_match_failed (st, queue); finished = TRUE; break; case MATCH_COMPLETE: if (start_pos != 0) add_pending (st, g_strndup (token, start_pos)); add_pending_match_begin (st, st->priv->search); add_pending (st, g_strndup (token+start_pos, end_pos-start_pos)); add_pending_match_end (st, st->priv->search); if (*(token+end_pos)) { queue->data = g_strdup (token+end_pos); add_to_trash (st, (gchar *) queue->data); } else { queue = g_list_remove_head (queue); } matched (st); finished = TRUE; break; case MATCH_START: { gchar *s1 = g_strndup (token, start_pos); gchar *s2 = g_strdup (START_MAGIC); gchar *s3 = g_strdup (token+start_pos); queue = g_list_insert_before (queue, q, s1); queue = g_list_insert_before (queue, q, s2); queue = g_list_insert_before (queue, q, s3); add_to_trash (st, s1); add_to_trash (st, s2); add_to_trash (st, s3); queue = g_list_remove_link (queue, q); finished = FALSE; break; } case MATCH_CONTINUES: /* Do nothing... */ finished = FALSE; break; case MATCH_END: { gchar *s1 = g_strndup (token, end_pos); gchar *s2 = g_strdup (END_MAGIC); gchar *s3 = g_strdup (token+end_pos); queue = g_list_insert_before (queue, q, s1); queue = g_list_insert_before (queue, q, s2); queue = g_list_insert_before (queue, q, s3); add_to_trash (st, s1); add_to_trash (st, s2); add_to_trash (st, s3); queue = g_list_remove_link (queue, q); queue = queue_matched (st, st->priv->search, queue); matched (st); finished = TRUE; break; } default: g_assert_not_reached (); } /* If we reach the end of the queue but we aren't finished, try to pull in another token and stick it onto the end. */ if (q_next == NULL && !finished) { gchar *next_token = get_next_token (st); if (next_token) { queue = g_list_append (queue, next_token); q_next = g_list_last (queue); } } q = finished ? NULL : q_next; } /* while (q) */ if (!finished && queue) { /* ...we add the token at the head of the queue to pending and try again. */ add_pending (st, g_strdup ((gchar *) queue->data)); queue = g_list_remove_head (queue); } } /* while (queue) */ } /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ static void e_searching_tokenizer_begin (HTMLTokenizer *t, gchar *content_type) { ESearchingTokenizer *st = E_SEARCHING_TOKENIZER (t); SearchInfo *si; if (st->priv->search == NULL && (st->priv->str_primary || st->priv->str_secondary)) { st->priv->search = search_info_new (); } si = st->priv->search; if (st->priv->str_primary) { search_info_set_string (si, st->priv->str_primary); search_info_set_case_sensitivity (si, st->priv->case_sensitive_primary); search_info_set_match_color (si, "red"); search_info_set_match_size_increase (si, 1); search_info_set_match_bold (si, TRUE); } else if (st->priv->str_secondary) { search_info_set_string (si, st->priv->str_secondary); search_info_set_case_sensitivity (si, st->priv->case_sensitive_secondary); search_info_set_match_color (si, "purple"); search_info_set_match_size_increase (si, 1); search_info_set_match_bold (si, TRUE); } else { search_info_free (st->priv->search); st->priv->search = NULL; } e_searching_tokenizer_cleanup (st); search_info_reset (st->priv->search); st->priv->match_count = 0; HTML_TOKENIZER_CLASS (parent_class)->begin (t, content_type); } static void e_searching_tokenizer_end (HTMLTokenizer *t) { e_searching_tokenizer_cleanup (E_SEARCHING_TOKENIZER (t)); HTML_TOKENIZER_CLASS (parent_class)->end (t); } static gchar * e_searching_tokenizer_peek_token (HTMLTokenizer *tok) { ESearchingTokenizer *st = E_SEARCHING_TOKENIZER (tok); /* If no search is active, just use the default method. */ if (st->priv->search == NULL) return HTML_TOKENIZER_CLASS (parent_class)->peek_token (tok); if (st->priv->pending == NULL) get_pending_tokens (st); return st->priv->pending ? (gchar *) st->priv->pending->data : NULL; } static gchar * e_searching_tokenizer_next_token (HTMLTokenizer *tok) { ESearchingTokenizer *st = E_SEARCHING_TOKENIZER (tok); /* If no search is active, just use the default method. */ if (st->priv->search == NULL) return HTML_TOKENIZER_CLASS (parent_class)->next_token (tok); if (st->priv->pending == NULL) get_pending_tokens (st); return pop_pending (st); } static gboolean e_searching_tokenizer_has_more (HTMLTokenizer *tok) { ESearchingTokenizer *st = E_SEARCHING_TOKENIZER (tok); /* If no search is active, pending will always be NULL and thus we'll always fall back to using the default method. */ return st->priv->pending || HTML_TOKENIZER_CLASS (parent_class)->has_more (tok); } static HTMLTokenizer * e_searching_tokenizer_clone (HTMLTokenizer *tok) { ESearchingTokenizer *orig_st = E_SEARCHING_TOKENIZER (tok); ESearchingTokenizer *new_st = E_SEARCHING_TOKENIZER (e_searching_tokenizer_new ()); if (new_st->priv->search) { search_info_free (new_st->priv->search); } new_st->priv->search = search_info_clone (orig_st->priv->search); gtk_signal_connect_object (GTK_OBJECT (new_st), "match", GTK_SIGNAL_FUNC (matched), GTK_OBJECT (orig_st)); return HTML_TOKENIZER (new_st); } /* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */ static gboolean only_whitespace (const gchar *p) { gunichar c; g_return_val_if_fail (p, FALSE); while (*p && g_unichar_validate (c = g_utf8_get_char (p))) { if (!g_unichar_isspace (c)) return FALSE; p = g_utf8_next_char (p); } return TRUE; } void e_searching_tokenizer_set_primary_search_string (ESearchingTokenizer *st, const gchar *search_str) { g_return_if_fail (st && E_IS_SEARCHING_TOKENIZER (st)); g_free (st->priv->str_primary); st->priv->str_primary = NULL; if (search_str != NULL && g_utf8_validate (search_str, -1, NULL) && !only_whitespace (search_str)) { st->priv->str_primary = g_strdup (search_str); } } void e_searching_tokenizer_set_primary_case_sensitivity (ESearchingTokenizer *st, gboolean is_case_sensitive) { g_return_if_fail (st && E_IS_SEARCHING_TOKENIZER (st)); st->priv->case_sensitive_primary = is_case_sensitive; } void e_searching_tokenizer_set_secondary_search_string (ESearchingTokenizer *st, const gchar *search_str) { g_return_if_fail (st && E_IS_SEARCHING_TOKENIZER (st)); g_free (st->priv->str_secondary); st->priv->str_secondary = NULL; if (search_str != NULL && g_utf8_validate (search_str, -1, NULL) && !only_whitespace (search_str)) { st->priv->str_secondary = g_strdup (search_str); } } void e_searching_tokenizer_set_secondary_case_sensitivity (ESearchingTokenizer *st, gboolean is_case_sensitive) { g_return_if_fail (st && E_IS_SEARCHING_TOKENIZER (st)); st->priv->case_sensitive_secondary = is_case_sensitive; } gint e_searching_tokenizer_match_count (ESearchingTokenizer *st) { g_return_val_if_fail (st && E_IS_SEARCHING_TOKENIZER (st), -1); return st->priv->match_count; }