File: | content/textsearch.c |
Warning: | line 438, column 2 Value stored to 'type' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk> |
3 | * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> |
4 | * |
5 | * This file is part of NetSurf, http://www.netsurf-browser.org/ |
6 | * |
7 | * NetSurf is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; version 2 of the License. |
10 | * |
11 | * NetSurf is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | /** |
21 | * \file |
22 | * Free text search |
23 | */ |
24 | |
25 | #include <stdbool.h> |
26 | #include <stddef.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | |
30 | #include "utils/errors.h" |
31 | #include "utils/utils.h" |
32 | #include "utils/ascii.h" |
33 | #include "netsurf/types.h" |
34 | #include "desktop/selection.h" |
35 | |
36 | #include "content/content.h" |
37 | #include "content/content_protected.h" |
38 | #include "content/hlcache.h" |
39 | #include "content/textsearch.h" |
40 | |
41 | /** |
42 | * search match |
43 | */ |
44 | struct list_entry { |
45 | /** |
46 | * previous match |
47 | */ |
48 | struct list_entry *prev; |
49 | |
50 | /** |
51 | * next match |
52 | */ |
53 | struct list_entry *next; |
54 | |
55 | /** |
56 | * start position of match |
57 | */ |
58 | unsigned start_idx; |
59 | |
60 | /** |
61 | * end of match |
62 | */ |
63 | unsigned end_idx; |
64 | |
65 | /** |
66 | * content opaque start pointer |
67 | */ |
68 | struct box *start_box; |
69 | |
70 | /** |
71 | * content opaque end pointer |
72 | */ |
73 | struct box *end_box; |
74 | |
75 | /** |
76 | * content specific selection object |
77 | */ |
78 | struct selection *sel; |
79 | }; |
80 | |
81 | /** |
82 | * The context for a free text search |
83 | */ |
84 | struct textsearch_context { |
85 | |
86 | /** |
87 | * content search was performed upon |
88 | */ |
89 | struct content *c; |
90 | |
91 | /** |
92 | * opaque pointer passed to constructor. |
93 | */ |
94 | void *gui_p; |
95 | |
96 | /** |
97 | * List of matches |
98 | */ |
99 | struct list_entry *found; |
100 | |
101 | /** |
102 | * current selected match |
103 | */ |
104 | struct list_entry *current; /* first for select all */ |
105 | |
106 | /** |
107 | * query string search results are for |
108 | */ |
109 | char *string; |
110 | bool_Bool prev_case_sens; |
111 | bool_Bool newsearch; |
112 | }; |
113 | |
114 | |
115 | /** |
116 | * broadcast textsearch message |
117 | */ |
118 | static inline void |
119 | textsearch_broadcast(struct textsearch_context *textsearch, |
120 | int type, |
121 | bool_Bool state, |
122 | const char *string) |
123 | { |
124 | union content_msg_data msg_data; |
125 | msg_data.textsearch.type = type; |
126 | msg_data.textsearch.ctx = textsearch->gui_p; |
127 | msg_data.textsearch.state = state; |
128 | msg_data.textsearch.string = string; |
129 | content_broadcast(textsearch->c, CONTENT_MSG_TEXTSEARCH, &msg_data); |
130 | } |
131 | |
132 | |
133 | /** |
134 | * Release the memory used by the list of matches, |
135 | * deleting selection objects too |
136 | */ |
137 | static void free_matches(struct textsearch_context *textsearch) |
138 | { |
139 | struct list_entry *cur; |
140 | struct list_entry *nxt; |
141 | |
142 | cur = textsearch->found->next; |
143 | |
144 | /* |
145 | * empty the list before clearing and deleting the selections |
146 | * because the the clearing may update the toolkit immediately, |
147 | * causing nested accesses to the list |
148 | */ |
149 | |
150 | textsearch->found->prev = NULL((void*)0); |
151 | textsearch->found->next = NULL((void*)0); |
152 | |
153 | for (; cur; cur = nxt) { |
154 | nxt = cur->next; |
155 | if (cur->sel) { |
156 | selection_destroy(cur->sel); |
157 | } |
158 | free(cur); |
159 | } |
160 | } |
161 | |
162 | |
163 | /** |
164 | * Specifies whether all matches or just the current match should |
165 | * be highlighted in the search text. |
166 | */ |
167 | static void search_show_all(bool_Bool all, struct textsearch_context *context) |
168 | { |
169 | struct list_entry *a; |
170 | |
171 | for (a = context->found->next; a; a = a->next) { |
172 | bool_Bool add = true1; |
173 | if (!all && a != context->current) { |
174 | add = false0; |
175 | if (a->sel) { |
176 | selection_destroy(a->sel); |
177 | a->sel = NULL((void*)0); |
178 | } |
179 | } |
180 | |
181 | if (add && !a->sel) { |
182 | |
183 | a->sel = selection_create(context->c); |
184 | if (a->sel != NULL((void*)0)) { |
185 | selection_init(a->sel); |
186 | selection_set_position(a->sel, |
187 | a->start_idx, |
188 | a->end_idx); |
189 | } |
190 | } |
191 | } |
192 | } |
193 | |
194 | |
195 | /** |
196 | * Search for a string in a content. |
197 | * |
198 | * \param context The search context. |
199 | * \param string the string to search for |
200 | * \param string_len length of search string |
201 | * \param flags flags to control the search. |
202 | */ |
203 | static nserror |
204 | search_text(struct textsearch_context *context, |
205 | const char *string, |
206 | int string_len, |
207 | search_flags_t flags) |
208 | { |
209 | struct rect bounds; |
210 | union content_msg_data msg_data; |
211 | bool_Bool case_sensitive, forwards, showall; |
212 | nserror res = NSERROR_OK; |
213 | |
214 | case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ? |
215 | true1 : false0; |
216 | forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true1 : false0; |
217 | showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true1 : false0; |
218 | |
219 | if (context->c == NULL((void*)0)) { |
220 | return res; |
221 | } |
222 | |
223 | /* check if we need to start a new search or continue an old one */ |
224 | if ((context->newsearch) || |
225 | (context->prev_case_sens != case_sensitive)) { |
226 | |
227 | if (context->string != NULL((void*)0)) { |
228 | free(context->string); |
229 | } |
230 | |
231 | context->current = NULL((void*)0); |
232 | free_matches(context); |
233 | |
234 | context->string = malloc(string_len + 1); |
235 | if (context->string != NULL((void*)0)) { |
236 | memcpy(context->string, string, string_len); |
237 | context->string[string_len] = '\0'; |
238 | } |
239 | |
240 | /* indicate find operation starting */ |
241 | textsearch_broadcast(context, CONTENT_TEXTSEARCH_FIND, true1, NULL((void*)0)); |
242 | |
243 | |
244 | /* call content find handler */ |
245 | res = context->c->handler->textsearch_find(context->c, |
246 | context, |
247 | string, |
248 | string_len, |
249 | case_sensitive); |
250 | |
251 | /* indicate find operation finished */ |
252 | textsearch_broadcast(context, CONTENT_TEXTSEARCH_FIND, false0, NULL((void*)0)); |
253 | |
254 | if (res != NSERROR_OK) { |
255 | free_matches(context); |
256 | return res; |
257 | } |
258 | |
259 | context->prev_case_sens = case_sensitive; |
260 | |
261 | /* new search, beginning at the top of the page */ |
262 | context->current = context->found->next; |
263 | context->newsearch = false0; |
264 | |
265 | } else if (context->current != NULL((void*)0)) { |
266 | /* continued search in the direction specified */ |
267 | if (forwards) { |
268 | if (context->current->next) |
269 | context->current = context->current->next; |
270 | } else { |
271 | if (context->current->prev) |
272 | context->current = context->current->prev; |
273 | } |
274 | } |
275 | |
276 | /* update match state */ |
277 | textsearch_broadcast(context, |
278 | CONTENT_TEXTSEARCH_MATCH, |
279 | (context->current != NULL((void*)0)), |
280 | NULL((void*)0)); |
281 | |
282 | search_show_all(showall, context); |
283 | |
284 | /* update back state */ |
285 | textsearch_broadcast(context, |
286 | CONTENT_TEXTSEARCH_BACK, |
287 | ((context->current != NULL((void*)0)) && |
288 | (context->current->prev != NULL((void*)0))), |
289 | NULL((void*)0)); |
290 | |
291 | /* update forward state */ |
292 | textsearch_broadcast(context, |
293 | CONTENT_TEXTSEARCH_FORWARD, |
294 | ((context->current != NULL((void*)0)) && |
295 | (context->current->next != NULL((void*)0))), |
296 | NULL((void*)0)); |
297 | |
298 | |
299 | if (context->current == NULL((void*)0)) { |
300 | /* no current match */ |
301 | return res; |
302 | } |
303 | |
304 | /* call content match bounds handler */ |
305 | res = context->c->handler->textsearch_bounds(context->c, |
306 | context->current->start_idx, |
307 | context->current->end_idx, |
308 | context->current->start_box, |
309 | context->current->end_box, |
310 | &bounds); |
311 | if (res == NSERROR_OK) { |
312 | msg_data.scroll.area = true1; |
313 | msg_data.scroll.x0 = bounds.x0; |
314 | msg_data.scroll.y0 = bounds.y0; |
315 | msg_data.scroll.x1 = bounds.x1; |
316 | msg_data.scroll.y1 = bounds.y1; |
317 | content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); |
318 | } |
319 | |
320 | return res; |
321 | } |
322 | |
323 | |
324 | /** |
325 | * Begins/continues the search process |
326 | * |
327 | * \note that this may be called many times for a single search. |
328 | * |
329 | * \param context The search context in use. |
330 | * \param flags The flags forward/back etc |
331 | * \param string The string to match |
332 | */ |
333 | static nserror |
334 | content_textsearch_step(struct textsearch_context *textsearch, |
335 | search_flags_t flags, |
336 | const char *string) |
337 | { |
338 | int string_len; |
339 | int i = 0; |
340 | nserror res = NSERROR_OK; |
341 | |
342 | assert(textsearch != NULL)((textsearch != ((void*)0)) ? (void) (0) : __assert_fail ("textsearch != NULL" , "content/textsearch.c", 342, __extension__ __PRETTY_FUNCTION__ )); |
343 | |
344 | /* broadcast recent query string */ |
345 | textsearch_broadcast(textsearch, |
346 | CONTENT_TEXTSEARCH_RECENT, |
347 | false0, |
348 | string); |
349 | |
350 | string_len = strlen(string); |
351 | for (i = 0; i < string_len; i++) { |
352 | if (string[i] != '#' && string[i] != '*') |
353 | break; |
354 | } |
355 | |
356 | if (i < string_len) { |
357 | res = search_text(textsearch, string, string_len, flags); |
358 | } else { |
359 | union content_msg_data msg_data; |
360 | |
361 | free_matches(textsearch); |
362 | |
363 | /* update match state */ |
364 | textsearch_broadcast(textsearch, |
365 | CONTENT_TEXTSEARCH_MATCH, |
366 | true1, |
367 | NULL((void*)0)); |
368 | |
369 | /* update back state */ |
370 | textsearch_broadcast(textsearch, |
371 | CONTENT_TEXTSEARCH_BACK, |
372 | false0, |
373 | NULL((void*)0)); |
374 | |
375 | /* update forward state */ |
376 | textsearch_broadcast(textsearch, |
377 | CONTENT_TEXTSEARCH_FORWARD, |
378 | false0, |
379 | NULL((void*)0)); |
380 | |
381 | /* clear scroll */ |
382 | msg_data.scroll.area = false0; |
383 | msg_data.scroll.x0 = 0; |
384 | msg_data.scroll.y0 = 0; |
385 | content_broadcast(textsearch->c, |
386 | CONTENT_MSG_SCROLL, |
387 | &msg_data); |
388 | } |
389 | |
390 | return res; |
391 | } |
392 | |
393 | |
394 | /** |
395 | * Terminate a search. |
396 | * |
397 | * \param c content to clear |
398 | */ |
399 | static nserror content_textsearch__clear(struct content *c) |
400 | { |
401 | free(c->textsearch.string); |
402 | c->textsearch.string = NULL((void*)0); |
403 | |
404 | if (c->textsearch.context != NULL((void*)0)) { |
405 | content_textsearch_destroy(c->textsearch.context); |
406 | c->textsearch.context = NULL((void*)0); |
407 | } |
408 | return NSERROR_OK; |
409 | } |
410 | |
411 | |
412 | /** |
413 | * create a search_context |
414 | * |
415 | * \param c The content the search_context is connected to |
416 | * \param context A context pointer passed to the provider routines. |
417 | * \param search_out A pointer to recive the new text search context |
418 | * \return NSERROR_OK on success and \a search_out updated else error code |
419 | */ |
420 | static nserror |
421 | content_textsearch_create(struct content *c, |
422 | void *gui_data, |
423 | struct textsearch_context **textsearch_out) |
424 | { |
425 | struct textsearch_context *context; |
426 | struct list_entry *search_head; |
427 | content_type type; |
428 | |
429 | if ((c->handler->textsearch_find == NULL((void*)0)) || |
430 | (c->handler->textsearch_bounds == NULL((void*)0))) { |
431 | /* |
432 | * content has no free text find handler so searching |
433 | * is unsupported. |
434 | */ |
435 | return NSERROR_NOT_IMPLEMENTED; |
436 | } |
437 | |
438 | type = c->handler->type(); |
Value stored to 'type' is never read | |
439 | |
440 | context = malloc(sizeof(struct textsearch_context)); |
441 | if (context == NULL((void*)0)) { |
442 | return NSERROR_NOMEM; |
443 | } |
444 | |
445 | search_head = malloc(sizeof(struct list_entry)); |
446 | if (search_head == NULL((void*)0)) { |
447 | free(context); |
448 | return NSERROR_NOMEM; |
449 | } |
450 | |
451 | search_head->start_idx = 0; |
452 | search_head->end_idx = 0; |
453 | search_head->start_box = NULL((void*)0); |
454 | search_head->end_box = NULL((void*)0); |
455 | search_head->sel = NULL((void*)0); |
456 | search_head->prev = NULL((void*)0); |
457 | search_head->next = NULL((void*)0); |
458 | |
459 | context->found = search_head; |
460 | context->current = NULL((void*)0); |
461 | context->string = NULL((void*)0); |
462 | context->prev_case_sens = false0; |
463 | context->newsearch = true1; |
464 | context->c = c; |
465 | context->gui_p = gui_data; |
466 | |
467 | *textsearch_out = context; |
468 | |
469 | return NSERROR_OK; |
470 | } |
471 | |
472 | |
473 | /* exported interface, documented in content/textsearch.h */ |
474 | const char * |
475 | content_textsearch_find_pattern(const char *string, |
476 | int s_len, |
477 | const char *pattern, |
478 | int p_len, |
479 | bool_Bool case_sens, |
480 | unsigned int *m_len) |
481 | { |
482 | struct { const char *ss, *s, *p; bool_Bool first; } context[16]; |
483 | const char *ep = pattern + p_len; |
484 | const char *es = string + s_len; |
485 | const char *p = pattern - 1; /* a virtual '*' before the pattern */ |
486 | const char *ss = string; |
487 | const char *s = string; |
488 | bool_Bool first = true1; |
489 | int top = 0; |
490 | |
491 | while (p < ep) { |
492 | bool_Bool matches; |
493 | if (p < pattern || *p == '*') { |
494 | char ch; |
495 | |
496 | /* skip any further asterisks; one is the same as many |
497 | */ |
498 | do p++; while (p < ep && *p == '*'); |
499 | |
500 | /* if we're at the end of the pattern, yes, it matches |
501 | */ |
502 | if (p >= ep) break; |
503 | |
504 | /* anything matches a # so continue matching from |
505 | here, and stack a context that will try to match |
506 | the wildcard against the next character */ |
507 | |
508 | ch = *p; |
509 | if (ch != '#') { |
510 | /* scan forwards until we find a match for |
511 | this char */ |
512 | if (!case_sens) ch = ascii_to_upper(ch); |
513 | while (s < es) { |
514 | if (case_sens) { |
515 | if (*s == ch) break; |
516 | } else if (ascii_to_upper(*s) == ch) |
517 | break; |
518 | s++; |
519 | } |
520 | } |
521 | |
522 | if (s < es) { |
523 | /* remember where we are in case the match |
524 | fails; we may then resume */ |
525 | if (top < (int)NOF_ELEMENTS(context)(sizeof(context)/sizeof(*(context)))) { |
526 | context[top].ss = ss; |
527 | context[top].s = s + 1; |
528 | context[top].p = p - 1; |
529 | /* ptr to last asterisk */ |
530 | context[top].first = first; |
531 | top++; |
532 | } |
533 | |
534 | if (first) { |
535 | ss = s; |
536 | /* remember first non-'*' char */ |
537 | first = false0; |
538 | } |
539 | |
540 | matches = true1; |
541 | } else { |
542 | matches = false0; |
543 | } |
544 | |
545 | } else if (s < es) { |
546 | char ch = *p; |
547 | if (ch == '#') |
548 | matches = true1; |
549 | else { |
550 | if (case_sens) |
551 | matches = (*s == ch); |
552 | else |
553 | matches = (ascii_to_upper(*s) == ascii_to_upper(ch)); |
554 | } |
555 | if (matches && first) { |
556 | ss = s; /* remember first non-'*' char */ |
557 | first = false0; |
558 | } |
559 | } else { |
560 | matches = false0; |
561 | } |
562 | |
563 | if (matches) { |
564 | p++; s++; |
565 | } else { |
566 | /* doesn't match, |
567 | * resume with stacked context if we have one */ |
568 | if (--top < 0) |
569 | return NULL((void*)0); /* no match, give up */ |
570 | |
571 | ss = context[top].ss; |
572 | s = context[top].s; |
573 | p = context[top].p; |
574 | first = context[top].first; |
575 | } |
576 | } |
577 | |
578 | /* end of pattern reached */ |
579 | *m_len = max(s - ss, 1)(((s - ss)>(1))?(s - ss):(1)); |
580 | return ss; |
581 | } |
582 | |
583 | |
584 | /* exported interface, documented in content/textsearch.h */ |
585 | nserror |
586 | content_textsearch_add_match(struct textsearch_context *context, |
587 | unsigned start_idx, |
588 | unsigned end_idx, |
589 | struct box *start_box, |
590 | struct box *end_box) |
591 | { |
592 | struct list_entry *entry; |
593 | |
594 | /* found string in box => add to list */ |
595 | entry = calloc(1, sizeof(*entry)); |
596 | if (entry == NULL((void*)0)) { |
597 | return NSERROR_NOMEM; |
598 | } |
599 | |
600 | entry->start_idx = start_idx; |
601 | entry->end_idx = end_idx; |
602 | entry->start_box = start_box; |
603 | entry->end_box = end_box; |
604 | entry->sel = NULL((void*)0); |
605 | |
606 | entry->next = NULL((void*)0); |
607 | entry->prev = context->found->prev; |
608 | |
609 | if (context->found->prev == NULL((void*)0)) { |
610 | context->found->next = entry; |
611 | } else { |
612 | context->found->prev->next = entry; |
613 | } |
614 | |
615 | context->found->prev = entry; |
616 | |
617 | return NSERROR_OK; |
618 | } |
619 | |
620 | |
621 | /* exported interface, documented in content/textsearch.h */ |
622 | bool_Bool |
623 | content_textsearch_ishighlighted(struct textsearch_context *textsearch, |
624 | unsigned start_offset, |
625 | unsigned end_offset, |
626 | unsigned *start_idx, |
627 | unsigned *end_idx) |
628 | { |
629 | struct list_entry *cur; |
630 | |
631 | for (cur = textsearch->found->next; cur != NULL((void*)0); cur = cur->next) { |
632 | if (cur->sel && |
633 | selection_highlighted(cur->sel, |
634 | start_offset, |
635 | end_offset, |
636 | start_idx, |
637 | end_idx)) { |
638 | return true1; |
639 | } |
640 | } |
641 | |
642 | return false0; |
643 | } |
644 | |
645 | |
646 | /* exported interface, documented in content/textsearch.h */ |
647 | nserror content_textsearch_destroy(struct textsearch_context *textsearch) |
648 | { |
649 | assert(textsearch != NULL)((textsearch != ((void*)0)) ? (void) (0) : __assert_fail ("textsearch != NULL" , "content/textsearch.c", 649, __extension__ __PRETTY_FUNCTION__ )); |
650 | |
651 | if (textsearch->string != NULL((void*)0)) { |
652 | /* broadcast recent query string */ |
653 | textsearch_broadcast(textsearch, |
654 | CONTENT_TEXTSEARCH_RECENT, |
655 | false0, |
656 | textsearch->string); |
657 | |
658 | free(textsearch->string); |
659 | } |
660 | |
661 | /* update back state */ |
662 | textsearch_broadcast(textsearch, |
663 | CONTENT_TEXTSEARCH_BACK, |
664 | true1, |
665 | NULL((void*)0)); |
666 | |
667 | /* update forward state */ |
668 | textsearch_broadcast(textsearch, |
669 | CONTENT_TEXTSEARCH_FORWARD, |
670 | true1, |
671 | NULL((void*)0)); |
672 | |
673 | free_matches(textsearch); |
674 | free(textsearch); |
675 | |
676 | return NSERROR_OK; |
677 | } |
678 | |
679 | |
680 | /* exported interface, documented in content/content.h */ |
681 | nserror |
682 | content_textsearch(struct hlcache_handle *h, |
683 | void *context, |
684 | search_flags_t flags, |
685 | const char *string) |
686 | { |
687 | struct content *c = hlcache_handle_get_content(h); |
688 | nserror res; |
689 | |
690 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/textsearch.c", 690, __extension__ __PRETTY_FUNCTION__ )); |
691 | |
692 | if (string != NULL((void*)0) && |
693 | c->textsearch.string != NULL((void*)0) && |
694 | c->textsearch.context != NULL((void*)0) && |
695 | strcmp(string, c->textsearch.string) == 0) { |
696 | /* Continue prev. search */ |
697 | content_textsearch_step(c->textsearch.context, flags, string); |
698 | |
699 | } else if (string != NULL((void*)0)) { |
700 | /* New search */ |
701 | free(c->textsearch.string); |
702 | c->textsearch.string = strdup(string); |
703 | if (c->textsearch.string == NULL((void*)0)) { |
704 | return NSERROR_NOMEM; |
705 | } |
706 | |
707 | if (c->textsearch.context != NULL((void*)0)) { |
708 | content_textsearch_destroy(c->textsearch.context); |
709 | c->textsearch.context = NULL((void*)0); |
710 | } |
711 | |
712 | res = content_textsearch_create(c, |
713 | context, |
714 | &c->textsearch.context); |
715 | if (res != NSERROR_OK) { |
716 | return res; |
717 | } |
718 | |
719 | content_textsearch_step(c->textsearch.context, flags, string); |
720 | |
721 | } else { |
722 | /* Clear search */ |
723 | content_textsearch__clear(c); |
724 | |
725 | free(c->textsearch.string); |
726 | c->textsearch.string = NULL((void*)0); |
727 | } |
728 | |
729 | return NSERROR_OK; |
730 | } |
731 | |
732 | |
733 | /* exported interface, documented in content/content.h */ |
734 | nserror content_textsearch_clear(struct hlcache_handle *h) |
735 | { |
736 | struct content *c = hlcache_handle_get_content(h); |
737 | assert(c != 0)((c != 0) ? (void) (0) : __assert_fail ("c != 0", "content/textsearch.c" , 737, __extension__ __PRETTY_FUNCTION__)); |
738 | |
739 | return(content_textsearch__clear(c)); |
740 | } |