1 /*
2 * Copyright (C) 2013-2020 Graeme Gott <graeme@gottcode.org>
3 *
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "query.h"
19
20 #include <sstream>
21
22 #include <climits>
23
24 #include <glib.h>
25
26 using namespace WhiskerMenu;
27
28 //-----------------------------------------------------------------------------
29
is_start_word(const std::string & string,std::string::size_type pos)30 static inline bool is_start_word(const std::string& string, std::string::size_type pos)
31 {
32 return (pos == 0) || g_unichar_isspace(g_utf8_get_char(g_utf8_prev_char(&string.at(pos))));
33 }
34
35 //-----------------------------------------------------------------------------
36
Query(const std::string & query)37 Query::Query(const std::string& query)
38 {
39 set(query);
40 }
41
42 //-----------------------------------------------------------------------------
43
match(const std::string & haystack) const44 unsigned int Query::match(const std::string& haystack) const
45 {
46 // Make sure haystack is longer than query
47 if (m_query.empty() || (m_query.length() > haystack.length()))
48 {
49 return UINT_MAX;
50 }
51
52 // Check if haystack begins with or is query
53 std::string::size_type pos = haystack.find(m_query);
54 if (pos == 0)
55 {
56 return (haystack.length() == m_query.length()) ? 0x4 : 0x8;
57 }
58 // Check if haystack contains query starting at a word boundary
59 else if ((pos != std::string::npos) && is_start_word(haystack, pos))
60 {
61 return 0x10;
62 }
63
64 if (m_query_words.size() > 1)
65 {
66 // Check if haystack contains query as words
67 std::string::size_type search_pos = 0;
68 for (const auto& word : m_query_words)
69 {
70 search_pos = haystack.find(word, search_pos);
71 if ((search_pos == std::string::npos) || !is_start_word(haystack, search_pos))
72 {
73 search_pos = std::string::npos;
74 break;
75 }
76 }
77 if (search_pos != std::string::npos)
78 {
79 return 0x20;
80 }
81
82 // Check if haystack contains query as words in any order
83 decltype(m_query_words.size()) found_words = 0;
84 for (const auto& word : m_query_words)
85 {
86 search_pos = haystack.find(word);
87 if ((search_pos != std::string::npos) && is_start_word(haystack, search_pos))
88 {
89 ++found_words;
90 }
91 else
92 {
93 break;
94 }
95 }
96 if (found_words == m_query_words.size())
97 {
98 return 0x40;
99 }
100 }
101
102 // Check if haystack contains query
103 if (pos != std::string::npos)
104 {
105 return 0x80;
106 }
107
108 return UINT_MAX;
109 }
110
111 //-----------------------------------------------------------------------------
112
match_as_characters(const std::string & haystack) const113 unsigned int Query::match_as_characters(const std::string& haystack) const
114 {
115 // Make sure haystack is longer than query
116 if (m_query.empty() || (m_query.length() > haystack.length()))
117 {
118 return UINT_MAX;
119 }
120
121 bool start_word = true;
122 const gchar* query_startwords_string = m_query.c_str();
123 const gchar* query_string = m_query.c_str();
124 for (const gchar* pos = haystack.c_str(); *pos; pos = g_utf8_next_char(pos))
125 {
126 gunichar c = g_utf8_get_char(pos);
127
128 if (start_word)
129 {
130 // Check if individual letters of query start words in haystack
131 if (c == g_utf8_get_char(query_startwords_string))
132 {
133 query_startwords_string = g_utf8_next_char(query_startwords_string);
134 }
135 start_word = false;
136 }
137 else if (g_unichar_isspace(c))
138 {
139 start_word = true;
140 }
141
142 // Check if individual letters of query are in haystack
143 if (c == g_utf8_get_char(query_string))
144 {
145 query_string = g_utf8_next_char(query_string);
146 }
147 }
148
149 if (!*query_startwords_string)
150 {
151 return 0x100;
152 }
153
154 if (!*query_string)
155 {
156 return 0x200;
157 }
158
159 return UINT_MAX;
160 }
161
162 //-----------------------------------------------------------------------------
163
clear()164 void Query::clear()
165 {
166 m_raw_query.clear();
167 m_query.clear();
168 m_query_words.clear();
169 }
170
171 //-----------------------------------------------------------------------------
172
set(const std::string & query)173 void Query::set(const std::string& query)
174 {
175 m_query.clear();
176 m_query_words.clear();
177
178 m_raw_query = query;
179 if (m_raw_query.empty())
180 {
181 return;
182 }
183
184 gchar* normalized = g_utf8_normalize(m_raw_query.c_str(), -1, G_NORMALIZE_DEFAULT);
185 gchar* utf8 = g_utf8_casefold(normalized, -1);
186 m_query = utf8;
187 g_free(utf8);
188 g_free(normalized);
189
190 std::string buffer;
191 std::stringstream ss(m_query);
192 while (ss >> buffer)
193 {
194 m_query_words.push_back(buffer);
195 }
196 }
197
198 //-----------------------------------------------------------------------------
199