1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009, 2011, 2012, 2015, 2020  Free Software Foundation
3 
4    This program 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 3 of the License, or
7    (at your option) any later version.
8 
9    This program 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 program.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 
18 /* This module implements the "Find" dialog; a dialog box to locate cases
19 which match particular strings */
20 
21 #include <config.h>
22 
23 #include <ctype.h>
24 #include <gtk/gtk.h>
25 #include <regex.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <math.h>
29 
30 #include "data/data-in.h"
31 #include "data/datasheet.h"
32 #include "data/format.h"
33 #include "data/value.h"
34 #include "libpspp/cast.h"
35 #include "libpspp/message.h"
36 #include "ui/gui/builder-wrapper.h"
37 #include "ui/gui/dict-display.h"
38 #include "ui/gui/find-dialog.h"
39 #include "ui/gui/helper.h"
40 #include "ui/gui/psppire-data-store.h"
41 #include "ui/gui/psppire-data-window.h"
42 #include "ui/gui/psppire-dialog.h"
43 #include "ui/gui/psppire-selector.h"
44 #include <ssw-sheet.h>
45 
46 #include "gl/xalloc.h"
47 
48 #include <gettext.h>
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
51 
52 
53 struct find_dialog
54 {
55   GtkBuilder *xml;
56   PsppireDict *dict;
57   struct datasheet *data;
58   PsppireDataWindow *de;
59   GtkWidget *variable_entry;
60   GtkWidget *value_entry;
61   GtkWidget *value_labels_checkbox;
62   GtkWidget *match_regexp_checkbox;
63   GtkWidget *match_substring_checkbox;
64 };
65 
66 static void
67 find_value (const struct find_dialog *fd, casenumber current_row,
68 	   casenumber *row, int *column);
69 
70 
71 /* A callback which occurs whenever the "Refresh" button is clicked,
72    and when the dialog pops up.
73    It restores the dialog to its default state.
74 */
75 static void
refresh(GObject * obj,const struct find_dialog * fd)76 refresh (GObject *obj, const struct find_dialog *fd)
77 {
78   gtk_toggle_button_set_active
79     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
80      FALSE);
81 
82   gtk_toggle_button_set_active
83     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
84      FALSE);
85 
86   gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
87   gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
88 
89   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
90 				FALSE);
91 
92   gtk_toggle_button_set_active
93     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
94 
95 
96   gtk_toggle_button_set_active
97     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
98 }
99 
100 /* Callback on the "Find" button */
101 static void
do_find(GObject * obj,const struct find_dialog * fd)102 do_find (GObject *obj, const struct find_dialog *fd)
103 {
104   SswSheet *sheet = SSW_SHEET (fd->de->data_editor->data_sheet);
105   casenumber x = -1;
106   gint column = -1;
107   gint unused;
108   gint row = 0;
109   ssw_sheet_get_active_cell (sheet, &unused, &row);
110 
111   find_value (fd, row, &x, &column);
112 
113   if (x != -1)
114     {
115       gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
116 				     PSPPIRE_DATA_EDITOR_DATA_VIEW);
117 
118       ssw_sheet_scroll_to (sheet, column, x);
119       ssw_sheet_set_active_cell (sheet, column, x, NULL);
120     }
121 }
122 
123 /* Callback on the selector.
124    It gets invoked whenever a variable is selected */
125 static void
on_select(GtkEntry * entry,gpointer data)126 on_select (GtkEntry *entry, gpointer data)
127 {
128   struct find_dialog *fd = data;
129   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
130   struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
131   gboolean search_labels ;
132 
133   g_return_if_fail (var);
134 
135   gtk_widget_set_sensitive (fd->value_labels_checkbox,
136 			    var_has_value_labels (var));
137 
138   search_labels =
139     gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
140 
141   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
142 			    var_is_alpha (var) || search_labels);
143 
144 
145   gtk_widget_set_sensitive (fd->match_substring_checkbox,
146 			    var_is_alpha (var) || search_labels);
147 }
148 
149 /* Callback on the selector.
150    It gets invoked whenever a variable is unselected */
151 static void
on_deselect(GtkEntry * entry,gpointer data)152 on_deselect (GtkEntry *entry, gpointer data)
153 {
154   struct find_dialog *fd = data;
155 
156   gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
157   gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
158   gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
159 }
160 
161 static void
value_labels_toggled(GtkToggleButton * tb,gpointer data)162 value_labels_toggled (GtkToggleButton *tb, gpointer data)
163 {
164   struct find_dialog *fd = data;
165 
166   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
167   const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
168 
169   gboolean active = gtk_toggle_button_get_active  (tb) ;
170 
171   gtk_widget_set_sensitive (fd->match_substring_checkbox,
172 			    active || (var && var_is_alpha (var)));
173 
174   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
175 			      active || (var && var_is_alpha (var)));
176 }
177 
178 /* Pops up the Find dialog box
179  */
180 void
find_dialog(PsppireDataWindow * de)181 find_dialog (PsppireDataWindow *de)
182 {
183   struct find_dialog fd;
184 
185   GtkWidget *dialog ;
186   GtkWidget *source ;
187   GtkWidget *selector;
188   GtkWidget *find_button;
189 
190   GtkWidget *buttonbox;
191 
192   PsppireDataStore *ds ;
193 
194   fd.xml = builder_new ("find.ui");
195   fd.de = de;
196 
197   find_button = gtk_button_new_with_label (_("Find"));
198   gtk_widget_show (find_button);
199 
200   buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
201 
202   psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
203   gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
204 
205   dialog = get_widget_assert (fd.xml, "find-dialog");
206   source = get_widget_assert (fd.xml, "find-variable-treeview");
207   selector = get_widget_assert (fd.xml, "find-selector");
208 
209   g_object_get (de->data_editor,
210 		"dictionary", &fd.dict,
211 		"data-store", &ds,
212 		NULL);
213 
214   fd.data = ds->datasheet;
215 
216   fd.variable_entry        = get_widget_assert (fd.xml, "find-variable-entry");
217   fd.value_entry           = get_widget_assert (fd.xml, "find-value-entry");
218   fd.value_labels_checkbox =
219     get_widget_assert (fd.xml,
220 		       "find-value-labels-checkbutton");
221 
222   fd.match_regexp_checkbox =
223     get_widget_assert (fd.xml,
224 		       "find-match-regexp-checkbutton");
225 
226   fd.match_substring_checkbox =
227     get_widget_assert (fd.xml,
228 		       "find-match-substring-checkbutton");
229 
230 
231 
232   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
233 
234 
235   g_object_set (source, "model", fd.dict,
236 	"selection-mode", GTK_SELECTION_SINGLE,
237 	NULL);
238 
239 
240   psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
241 				    is_currently_in_entry);
242 
243   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &fd);
244 
245   g_signal_connect (find_button, "clicked", G_CALLBACK (do_find),  &fd);
246 
247   g_signal_connect (selector, "selected",
248 		    G_CALLBACK (on_select),  &fd);
249 
250   g_signal_connect (selector, "de-selected",
251 		    G_CALLBACK (on_deselect),  &fd);
252 
253   g_signal_connect (fd.value_labels_checkbox, "toggled",
254 		    G_CALLBACK (value_labels_toggled),  &fd);
255 
256 
257   psppire_dialog_run (PSPPIRE_DIALOG (dialog));
258 
259   g_object_unref (fd.xml);
260 }
261 
262 
263 /* Iterators */
264 
265 static void
forward(casenumber * i,struct datasheet * data UNUSED)266 forward (casenumber *i, struct datasheet *data UNUSED)
267 {
268   ++*i;
269 }
270 
271 
272 static void
forward_wrap(casenumber * i,struct datasheet * data)273 forward_wrap (casenumber *i, struct datasheet *data)
274 {
275   if (++*i >=  datasheet_get_n_rows (data)) *i = 0;
276 }
277 
278 static void
backward(casenumber * i,struct datasheet * data UNUSED)279 backward (casenumber *i, struct datasheet *data UNUSED)
280 {
281   --*i;
282 }
283 
284 
285 static void
backward_wrap(casenumber * i,struct datasheet * data)286 backward_wrap (casenumber *i, struct datasheet *data)
287 {
288   if (--*i < 0)
289     *i = datasheet_get_n_rows (data) - 1;
290 }
291 
292 
293 /* Current plus one */
294 static casenumber
cp1(casenumber current,struct datasheet * data)295 cp1 (casenumber current, struct datasheet *data)
296 {
297   return current + 1;
298 }
299 
300 /* Current plus one, circular */
301 static casenumber
cp1c(casenumber current,struct datasheet * data)302 cp1c (casenumber current, struct datasheet *data)
303 {
304   casenumber next = current;
305 
306   forward_wrap (&next, data);
307 
308   return next;
309 }
310 
311 
312 /* Current minus one */
313 static casenumber
cm1(casenumber current,struct datasheet * data)314 cm1 (casenumber current, struct datasheet *data)
315 {
316   if (current == 0)
317     return datasheet_get_n_rows (data) - 1;
318 
319   return current - 1;
320 }
321 
322 /* Current minus one, circular */
323 static casenumber
cm1c(casenumber current,struct datasheet * data)324 cm1c (casenumber current, struct datasheet *data)
325 {
326   casenumber next = current;
327 
328   backward_wrap (&next, data);
329 
330   return next;
331 }
332 
333 
334 static casenumber
last(casenumber current,struct datasheet * data)335 last (casenumber current, struct datasheet *data)
336 {
337   return datasheet_get_n_rows (data) ;
338 }
339 
340 static casenumber
minus1(casenumber current,struct datasheet * data)341 minus1 (casenumber current, struct datasheet *data)
342 {
343   return -1;
344 }
345 
346 /* An type to facilitate iterating through casenumbers */
347 struct casenum_iterator
348 {
349   /* returns the first case to access */
350   casenumber (*start) (casenumber, struct datasheet *);
351 
352   /* Returns one past the last case to access */
353   casenumber (*end) (casenumber, struct datasheet *);
354 
355   /* Sets the first arg to the next case to access */
356   void (*next) (casenumber *, struct datasheet *);
357 };
358 
359 enum iteration_type{
360   FORWARD = 0,
361   FORWARD_WRAP,
362   REVERSE,
363   REVERSE_WRAP,
364   n_iterators
365 };
366 
367 static const struct casenum_iterator ip[n_iterators] =
368   {
369    /* Forward iterator (linear) */
370     {cp1, last, forward},
371 
372     /* Forward iterator (circular) */
373     {cp1c, cm1, forward_wrap},
374 
375     /* Reverse iterator (linear) */
376     {cm1, minus1, backward},
377 
378     /* Reverse iterator (circular */
379     {cm1c, cp1, backward_wrap}
380   };
381 
382 
383 
384 /* A factory returning an iterator according to the dialog box's settings */
385 static const struct casenum_iterator *
get_iteration_params(const struct find_dialog * fd)386 get_iteration_params (const struct find_dialog *fd)
387 {
388   gboolean wrap = gtk_toggle_button_get_active
389     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
390 
391   gboolean reverse = gtk_toggle_button_get_active
392     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
393 
394   if (wrap)
395     {
396       if (reverse)
397 	return &ip[REVERSE_WRAP];
398       else
399 	return &ip[FORWARD_WRAP];
400     }
401   else
402     {
403       if (reverse)
404 	return &ip[REVERSE];
405       else
406 	return &ip[FORWARD];
407     }
408 }
409 
410 
411 enum string_cmp_flags
412   {
413     STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
414 			      values */
415     STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
416 
417     STR_CMP_LABELS = 0x04  /* Match against the values' labels instead
418 			      of the data */
419   };
420 
421 
422 /* An abstract base type for comparing union values against a reference */
423 struct comparator
424 {
425   const struct variable *var;
426   enum string_cmp_flags flags;
427 
428   bool (*compare) (const struct comparator *,
429 		   const union value *);
430 
431   void (*destroy) (struct comparator *);
432 };
433 
434 
435 /* A comparator which operates on the numerical values,
436    rounded to the number of decimal places indicated by
437    the variable's format.  */
438 struct numeric_comparator
439 {
440   struct comparator parent;
441   double rounded_ref;
442 };
443 
444 /* A comparator which matches string values or parts thereof */
445 struct string_comparator
446 {
447   struct comparator parent;
448   const char *pattern;
449 };
450 
451 /* A comparator to match string values against a POSIX.2 regular expression */
452 struct regexp_comparator
453 {
454   struct comparator parent;
455   regex_t re;
456 };
457 
458 /* Returns 10 raised to the power of X.
459    X must be a non-negative integer.  */
460 static inline int
int_pow10(int x)461 int_pow10 (int x)
462 {
463   int ret = 1;
464   assert (x >= 0);
465   while (x--)
466     ret *= 10;
467 
468   return ret;
469 }
470 
471 static bool
value_compare(const struct comparator * cmptr,const union value * v)472 value_compare (const struct comparator *cmptr,
473 	       const union value *v)
474 {
475   const struct numeric_comparator *nc = (const struct numeric_comparator *) cmptr;
476   const struct fmt_spec *fs = var_get_print_format (cmptr->var);
477 
478   double c = nearbyint (v->f * int_pow10 (fs->d));
479 
480   return c == nc->rounded_ref;
481 }
482 
483 
484 /* Return true if the label of VAL matches the reference string*/
485 static bool
string_label_compare(const struct comparator * cmptr,const union value * val)486 string_label_compare (const struct comparator *cmptr,
487 		const union value *val)
488 {
489   const struct string_comparator *ssc =
490     (const struct string_comparator *) cmptr;
491 
492   int width;
493 
494   const char *text = var_lookup_value_label (cmptr->var, val);
495   if (text == NULL)
496     return false;
497 
498   width = strlen (text);
499 
500   assert (cmptr->flags & STR_CMP_LABELS);
501 
502   g_return_val_if_fail (width > 0, false);
503 
504   if (cmptr->flags & STR_CMP_SUBSTR)
505     return (NULL != g_strstr_len (text, width, ssc->pattern));
506   else
507     return (0 == strncmp (text, ssc->pattern, width));
508 }
509 
510 /* Return true if VAL matches the reference string*/
511 static bool
string_value_compare(const struct comparator * cmptr,const union value * val)512 string_value_compare (const struct comparator *cmptr,
513 		      const union value *val)
514 {
515   bool found;
516   char *text;
517   const struct string_comparator *ssc =
518     (const struct string_comparator *) cmptr;
519 
520   int width = var_get_width (cmptr->var);
521   g_return_val_if_fail (width > 0, false);
522   assert (! (cmptr->flags & STR_CMP_LABELS));
523 
524   text = value_to_text (*val, cmptr->var);
525 
526   if (cmptr->flags & STR_CMP_SUBSTR)
527     found =  (NULL != g_strstr_len (text, width, ssc->pattern));
528   else
529     found = (0 == strncmp (text, ssc->pattern, width));
530 
531   free (text);
532   return found;
533 }
534 
535 
536 
537 /* Return true if VAL matched the regexp */
538 static bool
regexp_value_compare(const struct comparator * cmptr,const union value * val)539 regexp_value_compare (const struct comparator *cmptr,
540 		const union value *val)
541 {
542   char *text;
543   bool retval;
544   const struct regexp_comparator *rec =
545     (const struct regexp_comparator *) cmptr;
546 
547   int width = var_get_width (cmptr->var);
548 
549   assert  (! (cmptr->flags & STR_CMP_LABELS));
550 
551   g_return_val_if_fail (width > 0, false);
552 
553   text = value_to_text (*val, cmptr->var);
554   /* We must remove trailing whitespace, otherwise $ will not match where
555      one would expect */
556   g_strchomp (text);
557 
558   retval = (0 == regexec (&rec->re, text, 0, 0, 0));
559 
560   g_free (text);
561 
562   return retval;
563 }
564 
565 /* Return true if the label of VAL matched the regexp */
566 static bool
regexp_label_compare(const struct comparator * cmptr,const union value * val)567 regexp_label_compare (const struct comparator *cmptr,
568 		      const union value *val)
569 {
570   const char *text;
571   const struct regexp_comparator *rec =
572     (const struct regexp_comparator *) cmptr;
573 
574   int width ;
575 
576   assert (cmptr->flags & STR_CMP_LABELS);
577 
578   text = var_lookup_value_label (cmptr->var, val);
579   width = strlen (text);
580 
581   g_return_val_if_fail (width > 0, false);
582 
583   return (0 == regexec (&rec->re, text, 0, 0, 0));
584 }
585 
586 
587 
588 static void
regexp_destroy(struct comparator * cmptr)589 regexp_destroy (struct comparator *cmptr)
590 {
591   struct regexp_comparator *rec
592     = UP_CAST (cmptr, struct regexp_comparator, parent);
593 
594   regfree (&rec->re);
595 }
596 
597 static struct comparator *
numeric_comparator_create(const struct variable * var,const char * target)598 numeric_comparator_create (const struct variable *var, const char *target)
599 {
600   struct numeric_comparator *nc = xzalloc (sizeof (*nc));
601   struct comparator *cmptr = &nc->parent;
602 
603   cmptr->flags = 0;
604   cmptr->var = var;
605   cmptr->compare  = value_compare;
606   const struct fmt_spec *fs = var_get_write_format (var);
607 
608   union value val;
609   text_to_value (target, var, &val);
610   nc->rounded_ref = nearbyint (val.f * int_pow10 (fs->d));
611   value_destroy (&val, var_get_width (var));
612 
613   return cmptr;
614 }
615 
616 static struct comparator *
string_comparator_create(const struct variable * var,const char * target,enum string_cmp_flags flags)617 string_comparator_create (const struct variable *var, const char *target,
618 			  enum string_cmp_flags flags)
619 {
620   struct string_comparator *ssc = xzalloc (sizeof (*ssc));
621   struct comparator *cmptr = &ssc->parent;
622 
623   cmptr->flags = flags;
624   cmptr->var = var;
625 
626   if (flags & STR_CMP_LABELS)
627     cmptr->compare = string_label_compare;
628   else
629     cmptr->compare = string_value_compare;
630 
631   ssc->pattern = target;
632 
633   return cmptr;
634 }
635 
636 
637 static struct comparator *
regexp_comparator_create(const struct variable * var,const char * target,enum string_cmp_flags flags)638 regexp_comparator_create (const struct variable *var, const char *target,
639 			  enum string_cmp_flags flags)
640 {
641   int code;
642   struct regexp_comparator *rec = xzalloc (sizeof (*rec));
643   struct comparator *cmptr = &rec->parent;
644 
645   cmptr->flags = flags;
646   cmptr->var = var;
647   cmptr->compare  = (flags & STR_CMP_LABELS)
648     ? regexp_label_compare : regexp_value_compare ;
649 
650   cmptr->destroy  = regexp_destroy;
651 
652   code = regcomp (&rec->re, target, 0);
653   if (code != 0)
654     {
655       char *errbuf = NULL;
656       size_t errbuf_size = regerror (code, &rec->re, errbuf,  0);
657 
658       errbuf = xmalloc (errbuf_size);
659 
660       regerror (code, &rec->re, errbuf, errbuf_size);
661 
662       msg (ME, _("Bad regular expression: %s"), errbuf);
663 
664       free (cmptr);
665       free (errbuf);
666       return NULL;
667     }
668 
669   return cmptr;
670 }
671 
672 
673 /* Compare V against CMPTR's reference */
674 static bool
comparator_compare(const struct comparator * cmptr,const union value * v)675 comparator_compare (const struct comparator *cmptr,
676 		    const union value *v)
677 {
678   return cmptr->compare (cmptr, v);
679 }
680 
681 /* Destroy CMPTR */
682 static void
comparator_destroy(struct comparator * cmptr)683 comparator_destroy (struct comparator *cmptr)
684 {
685   if (! cmptr)
686     return ;
687 
688   if (cmptr->destroy)
689     cmptr->destroy (cmptr);
690 
691   free (cmptr);
692 }
693 
694 
695 static struct comparator *
comparator_factory(const struct variable * var,const char * str,enum string_cmp_flags flags)696 comparator_factory (const struct variable *var, const char *str,
697 		    enum string_cmp_flags flags)
698 {
699   if (flags & STR_CMP_REGEXP)
700     return regexp_comparator_create (var, str, flags);
701 
702   if (flags & (STR_CMP_SUBSTR | STR_CMP_LABELS))
703     return string_comparator_create (var, str, flags);
704 
705   return numeric_comparator_create (var, str);
706 }
707 
708 
709 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
710    After the function returns, *ROW contains the row and *COLUMN the column.
711    If no such case is found, then *ROW will be set to -1
712  */
713 static void
find_value(const struct find_dialog * fd,casenumber current_row,casenumber * row,int * column)714 find_value (const struct find_dialog *fd, casenumber current_row,
715 	   casenumber *row, int *column)
716 {
717   int width;
718   const struct variable *var;
719   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
720   const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
721 
722   enum string_cmp_flags flags = 0;
723 
724   var = dict_lookup_var (fd->dict->dict, var_name);
725   if (! var)
726     return ;
727 
728   width = var_get_width (var);
729 
730   *column = var_get_dict_index (var);
731   *row = -1;
732 
733   if (gtk_toggle_button_get_active
734        (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
735     flags |= STR_CMP_SUBSTR;
736 
737   if (gtk_toggle_button_get_active
738        (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
739     flags |= STR_CMP_REGEXP;
740 
741   if (gtk_toggle_button_get_active
742        (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
743     flags |= STR_CMP_LABELS;
744 
745   {
746     union value val;
747     casenumber i;
748     const struct casenum_iterator *ip = get_iteration_params (fd);
749     struct comparator *cmptr =
750       comparator_factory (var, target_string, flags);
751 
752     value_init (&val, width);
753     if (! cmptr)
754       goto finish;
755 
756     for (i = ip->start (current_row, fd->data);
757 	 i != ip->end (current_row, fd->data);
758 	 ip->next (&i, fd->data))
759       {
760 	datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
761 
762 	if (comparator_compare (cmptr, &val))
763 	  {
764 	    *row = i;
765 	    break;
766 	  }
767       }
768 
769   finish:
770     comparator_destroy (cmptr);
771     value_destroy (&val, width);
772   }
773 }
774