621516b9ecdc82eaaceaf480d5f958142dc353a1
[pspp] / src / language / dictionary / mrsets.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
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 #include <config.h>
18
19 #include "data/data-out.h"
20 #include "data/dataset.h"
21 #include "data/dictionary.h"
22 #include "data/mrset.h"
23 #include "data/value-labels.h"
24 #include "data/variable.h"
25 #include "language/command.h"
26 #include "language/lexer/lexer.h"
27 #include "language/lexer/variable-parser.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/hmap.h"
30 #include "libpspp/i18n.h"
31 #include "libpspp/message.h"
32 #include "libpspp/str.h"
33 #include "libpspp/stringi-map.h"
34 #include "libpspp/stringi-set.h"
35 #include "output/tab.h"
36
37 #include "gl/xalloc.h"
38
39 #include "gettext.h"
40 #define _(msgid) gettext (msgid)
41
42 static bool parse_group (struct lexer *, struct dictionary *, enum mrset_type);
43 static bool parse_delete (struct lexer *, struct dictionary *);
44 static bool parse_display (struct lexer *, struct dictionary *);
45
46 int
47 cmd_mrsets (struct lexer *lexer, struct dataset *ds)
48 {
49   struct dictionary *dict = dataset_dict (ds);
50
51   while (lex_match (lexer, T_SLASH))
52     {
53       bool ok;
54
55       if (lex_match_id (lexer, "MDGROUP"))
56         ok = parse_group (lexer, dict, MRSET_MD);
57       else if (lex_match_id (lexer, "MCGROUP"))
58         ok = parse_group (lexer, dict, MRSET_MC);
59       else if (lex_match_id (lexer, "DELETE"))
60         ok = parse_delete (lexer, dict);
61       else if (lex_match_id (lexer, "DISPLAY"))
62         ok = parse_display (lexer, dict);
63       else
64         {
65           ok = false;
66           lex_error (lexer, NULL);
67         }
68
69       if (!ok)
70         return CMD_FAILURE;
71     }
72
73   return CMD_SUCCESS;
74 }
75
76 static bool
77 parse_group (struct lexer *lexer, struct dictionary *dict,
78              enum mrset_type type)
79 {
80   const char *subcommand_name = type == MRSET_MD ? "MDGROUP" : "MCGROUP";
81   struct mrset *mrset;
82   bool labelsource_varlabel;
83   bool has_value;
84
85   mrset = xzalloc (sizeof *mrset);
86   mrset->type = type;
87   mrset->cat_source = MRSET_VARLABELS;
88
89   labelsource_varlabel = false;
90   has_value = false;
91   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
92     {
93       if (lex_match_id (lexer, "NAME"))
94         {
95           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_id (lexer)
96               || !mrset_is_valid_name (lex_tokcstr (lexer),
97                                        dict_get_encoding (dict), true))
98             goto error;
99
100           free (mrset->name);
101           mrset->name = xstrdup (lex_tokcstr (lexer));
102           lex_get (lexer);
103         }
104       else if (lex_match_id (lexer, "VARIABLES"))
105         {
106           if (!lex_force_match (lexer, T_EQUALS))
107             goto error;
108
109           free (mrset->vars);
110           if (!parse_variables (lexer, dict, &mrset->vars, &mrset->n_vars,
111                                 PV_SAME_TYPE | PV_NO_SCRATCH))
112             goto error;
113
114           if (mrset->n_vars < 2)
115             {
116               msg (SE, _("VARIABLES specified only variable %s on %s, but "
117                          "at least two variables are required."),
118                    var_get_name (mrset->vars[0]), subcommand_name);
119               goto error;
120             }
121         }
122       else if (lex_match_id (lexer, "LABEL"))
123         {
124           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_string (lexer))
125             goto error;
126
127           free (mrset->label);
128           mrset->label = ss_xstrdup (lex_tokss (lexer));
129           lex_get (lexer);
130         }
131       else if (type == MRSET_MD && lex_match_id (lexer, "LABELSOURCE"))
132         {
133           if (!lex_force_match (lexer, T_EQUALS)
134               || !lex_force_match_id (lexer, "VARLABEL"))
135             goto error;
136
137           labelsource_varlabel = true;
138         }
139       else if (type == MRSET_MD && lex_match_id (lexer, "VALUE"))
140         {
141           if (!lex_force_match (lexer, T_EQUALS))
142             goto error;
143
144           has_value = true;
145           if (lex_is_number (lexer))
146             {
147               if (!lex_is_integer (lexer))
148                 {
149                   msg (SE, _("Numeric VALUE must be an integer."));
150                   goto error;
151                 }
152               value_destroy (&mrset->counted, mrset->width);
153               mrset->counted.f = lex_integer (lexer);
154               mrset->width = 0;
155             }
156           else if (lex_is_string (lexer))
157             {
158               size_t width;
159               char *s;
160
161               s = recode_string (dict_get_encoding (dict), "UTF-8",
162                                  lex_tokcstr (lexer), -1);
163               width = strlen (s);
164
165               /* Trim off trailing spaces, but don't trim the string until
166                  it's empty because a width of 0 is a numeric type. */
167               while (width > 1 && s[width - 1] == ' ')
168                 width--;
169
170               value_destroy (&mrset->counted, mrset->width);
171               value_init (&mrset->counted, width);
172               memcpy (value_str_rw (&mrset->counted, width), s, width);
173               mrset->width = width;
174
175               free (s);
176             }
177           else
178             {
179               lex_error (lexer, NULL);
180               goto error;
181             }
182           lex_get (lexer);
183         }
184       else if (type == MRSET_MD && lex_match_id (lexer, "CATEGORYLABELS"))
185         {
186           if (!lex_force_match (lexer, T_EQUALS))
187             goto error;
188
189           if (lex_match_id (lexer, "VARLABELS"))
190             mrset->cat_source = MRSET_VARLABELS;
191           else if (lex_match_id (lexer, "COUNTEDVALUES"))
192             mrset->cat_source = MRSET_COUNTEDVALUES;
193           else
194             {
195               lex_error (lexer, NULL);
196               goto error;
197             }
198         }
199       else
200         {
201           lex_error (lexer, NULL);
202           goto error;
203         }
204     }
205
206   if (mrset->name == NULL)
207     {
208       lex_spec_missing (lexer, subcommand_name, "NAME");
209       goto error;
210     }
211   else if (mrset->n_vars == 0)
212     {
213       lex_spec_missing (lexer, subcommand_name, "VARIABLES");
214       goto error;
215     }
216
217   if (type == MRSET_MD)
218     {
219       /* Check that VALUE is specified and is valid for the VARIABLES. */
220       if (!has_value)
221         {
222           lex_spec_missing (lexer, subcommand_name, "VALUE");
223           goto error;
224         }
225       else if (var_is_alpha (mrset->vars[0]))
226         {
227           if (mrset->width == 0)
228             {
229               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
230                          "VALUE, but the variables specified for this group "
231                          "are numeric."),
232                    mrset->name);
233               goto error;
234             }
235           else {
236             const struct variable *shortest_var;
237             int min_width;
238             size_t i;
239
240             shortest_var = NULL;
241             min_width = INT_MAX;
242             for (i = 0; i < mrset->n_vars; i++)
243               {
244                 int width = var_get_width (mrset->vars[i]);
245                 if (width < min_width)
246                   {
247                     shortest_var = mrset->vars[i];
248                     min_width = width;
249                   }
250               }
251             if (mrset->width > min_width)
252               {
253                 msg (SE, _("VALUE string on MDGROUP subcommand for group "
254                            "%s is %d bytes long, but it must be no longer "
255                            "than the narrowest variable in the group, "
256                            "which is %s with a width of %d bytes."),
257                      mrset->name, mrset->width,
258                      var_get_name (shortest_var), min_width);
259                 goto error;
260               }
261           }
262         }
263       else
264         {
265           if (mrset->width != 0)
266             {
267               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
268                          "VALUE, but the variables specified for this group "
269                          "are numeric."),
270                    mrset->name);
271               goto error;
272             }
273         }
274
275       /* Implement LABELSOURCE=VARLABEL. */
276       if (labelsource_varlabel)
277         {
278           if (mrset->cat_source != MRSET_COUNTEDVALUES)
279             msg (SW, _("MDGROUP subcommand for group %s specifies "
280                        "LABELSOURCE=VARLABEL but not "
281                        "CATEGORYLABELS=COUNTEDVALUES.  "
282                        "Ignoring LABELSOURCE."),
283                  mrset->name);
284           else if (mrset->label)
285             msg (SW, _("MDGROUP subcommand for group %s specifies both LABEL "
286                        "and LABELSOURCE, but only one of these subcommands "
287                        "may be used at a time.  Ignoring LABELSOURCE."),
288                  mrset->name);
289           else
290             {
291               size_t i;
292
293               mrset->label_from_var_label = true;
294               for (i = 0; mrset->label == NULL && i < mrset->n_vars; i++)
295                 {
296                   const char *label = var_get_label (mrset->vars[i]);
297                   if (label != NULL)
298                     {
299                       mrset->label = xstrdup (label);
300                       break;
301                     }
302                 }
303             }
304         }
305
306       /* Warn if categories cannot be distinguished in output. */
307       if (mrset->cat_source == MRSET_VARLABELS)
308         {
309           struct stringi_map seen;
310           size_t i;
311
312           stringi_map_init (&seen);
313           for (i = 0; i < mrset->n_vars; i++)
314             {
315               const struct variable *var = mrset->vars[i];
316               const char *name = var_get_name (var);
317               const char *label = var_get_label (var);
318               if (label != NULL)
319                 {
320                   const char *other_name = stringi_map_find (&seen, label);
321
322                   if (other_name == NULL)
323                     stringi_map_insert (&seen, label, name);
324                   else
325                     msg (SW, _("Variables %s and %s specified as part of "
326                                "multiple dichotomy group %s have the same "
327                                "variable label.  Categories represented by "
328                                "these variables will not be distinguishable "
329                                "in output."),
330                          other_name, name, mrset->name);
331                 }
332             }
333           stringi_map_destroy (&seen);
334         }
335       else
336         {
337           struct stringi_map seen;
338           size_t i;
339
340           stringi_map_init (&seen);
341           for (i = 0; i < mrset->n_vars; i++)
342             {
343               const struct variable *var = mrset->vars[i];
344               const char *name = var_get_name (var);
345               const struct val_labs *val_labs;
346               union value value;
347               const char *label;
348
349               value_clone (&value, &mrset->counted, mrset->width);
350               value_resize (&value, mrset->width, var_get_width (var));
351
352               val_labs = var_get_value_labels (var);
353               label = val_labs_find (val_labs, &value);
354               if (label == NULL)
355                 msg (SW, _("Variable %s specified as part of multiple "
356                            "dichotomy group %s (which has "
357                            "CATEGORYLABELS=COUNTEDVALUES) has no value label "
358                            "for its counted value.  This category will not "
359                            "be distinguishable in output."),
360                      name, mrset->name);
361               else
362                 {
363                   const char *other_name = stringi_map_find (&seen, label);
364
365                   if (other_name == NULL)
366                     stringi_map_insert (&seen, label, name);
367                   else
368                     msg (SW, _("Variables %s and %s specified as part of "
369                                "multiple dichotomy group %s (which has "
370                                "CATEGORYLABELS=COUNTEDVALUES) have the same "
371                                "value label for the group's counted "
372                                "value.  These categories will not be "
373                                "distinguishable in output."),
374                          other_name, name, mrset->name);
375                 }
376             }
377           stringi_map_destroy (&seen);
378         }
379     }
380   else                          /* MCGROUP. */
381     {
382       /* Warn if categories cannot be distinguished in output. */
383       struct category
384         {
385           struct hmap_node hmap_node;
386           union value value;
387           int width;
388           const char *label;
389           const char *var_name;
390           bool warned;
391         };
392
393       struct category *c, *next;
394       struct hmap categories;
395       size_t i;
396
397       hmap_init (&categories);
398       for (i = 0; i < mrset->n_vars; i++)
399         {
400           const struct variable *var = mrset->vars[i];
401           const char *name = var_get_name (var);
402           int width = var_get_width (var);
403           const struct val_labs *val_labs;
404           const struct val_lab *vl;
405
406           val_labs = var_get_value_labels (var);
407           for (vl = val_labs_first (val_labs); vl != NULL;
408                vl = val_labs_next (val_labs, vl))
409             {
410               const union value *value = val_lab_get_value (vl);
411               const char *label = val_lab_get_label (vl);
412               unsigned int hash = value_hash (value, width, 0);
413
414               HMAP_FOR_EACH_WITH_HASH (c, struct category, hmap_node,
415                                        hash, &categories)
416                 {
417                   if (width == c->width
418                       && value_equal (value, &c->value, width))
419                     {
420                       if (!c->warned && utf8_strcasecmp (c->label, label))
421                         {
422                           char *s = data_out (value, var_get_encoding (var),
423                                               var_get_print_format (var));
424                           c->warned = true;
425                           msg (SW, _("Variables specified on MCGROUP should "
426                                      "have the same categories, but %s and %s "
427                                      "(and possibly others) in multiple "
428                                      "category group %s have different "
429                                      "value labels for value %s."),
430                                c->var_name, name, mrset->name, s);
431                           free (s);
432                         }
433                       goto found;
434                     }
435                 }
436
437               c = xmalloc (sizeof *c);
438               value_clone (&c->value, value, width);
439               c->width = width;
440               c->label = label;
441               c->var_name = name;
442               c->warned = false;
443               hmap_insert (&categories, &c->hmap_node, hash);
444
445             found: ;
446             }
447         }
448
449       HMAP_FOR_EACH_SAFE (c, next, struct category, hmap_node, &categories)
450         {
451           value_destroy (&c->value, c->width);
452           hmap_delete (&categories, &c->hmap_node);
453           free (c);
454         }
455       hmap_destroy (&categories);
456     }
457
458   dict_add_mrset (dict, mrset);
459   return true;
460
461 error:
462   mrset_destroy (mrset);
463   return false;
464 }
465
466 static bool
467 parse_mrset_names (struct lexer *lexer, struct dictionary *dict,
468                    struct stringi_set *mrset_names)
469 {
470   if (!lex_force_match_id (lexer, "NAME")
471       || !lex_force_match (lexer, T_EQUALS))
472     return false;
473
474   stringi_set_init (mrset_names);
475   if (lex_match (lexer, T_LBRACK))
476     {
477       while (!lex_match (lexer, T_RBRACK))
478         {
479           if (!lex_force_id (lexer))
480             return false;
481           if (dict_lookup_mrset (dict, lex_tokcstr (lexer)) == NULL)
482             {
483               msg (SE, _("No multiple response set named %s."),
484                    lex_tokcstr (lexer));
485               stringi_set_destroy (mrset_names);
486               return false;
487             }
488           stringi_set_insert (mrset_names, lex_tokcstr (lexer));
489           lex_get (lexer);
490         }
491     }
492   else if (lex_match (lexer, T_ALL))
493     {
494       size_t n_sets = dict_get_n_mrsets (dict);
495       size_t i;
496
497       for (i = 0; i < n_sets; i++)
498         stringi_set_insert (mrset_names, dict_get_mrset (dict, i)->name);
499     }
500
501   return true;
502 }
503
504 static bool
505 parse_delete (struct lexer *lexer, struct dictionary *dict)
506 {
507   const struct stringi_set_node *node;
508   struct stringi_set mrset_names;
509   const char *name;
510
511   if (!parse_mrset_names (lexer, dict, &mrset_names))
512     return false;
513
514   STRINGI_SET_FOR_EACH (name, node, &mrset_names)
515     dict_delete_mrset (dict, name);
516   stringi_set_destroy (&mrset_names);
517
518   return true;
519 }
520
521 static bool
522 parse_display (struct lexer *lexer, struct dictionary *dict)
523 {
524   struct string details, var_names;
525   struct stringi_set mrset_names_set;
526   char **mrset_names;
527   struct tab_table *table;
528   size_t i, n;
529
530   if (!parse_mrset_names (lexer, dict, &mrset_names_set))
531     return false;
532
533   n = stringi_set_count (&mrset_names_set);
534   if (n == 0)
535     {
536       if (dict_get_n_mrsets (dict) == 0)
537         msg (SN, _("The active dataset dictionary does not contain any "
538                    "multiple response sets."));
539       stringi_set_destroy (&mrset_names_set);
540       return true;
541     }
542
543   table = tab_create (3, n + 1);
544   tab_headers (table, 0, 0, 1, 0);
545   tab_box (table, TAL_1, TAL_1, TAL_1, TAL_1, 0, 0, 2, n);
546   tab_hline (table, TAL_2, 0, 2, 1);
547   tab_title (table, "%s", _("Multiple Response Sets"));
548   tab_text (table, 0, 0, TAB_EMPH | TAB_LEFT, _("Name"));
549   tab_text (table, 1, 0, TAB_EMPH | TAB_LEFT, _("Variables"));
550   tab_text (table, 2, 0, TAB_EMPH | TAB_LEFT, _("Details"));
551
552   ds_init_empty (&details);
553   ds_init_empty (&var_names);
554   mrset_names = stringi_set_get_sorted_array (&mrset_names_set);
555   for (i = 0; i < n; i++)
556     {
557       const struct mrset *mrset = dict_lookup_mrset (dict, mrset_names[i]);
558       const int row = i + 1;
559       size_t j;
560
561       /* Details. */
562       ds_clear (&details);
563       ds_put_format (&details, "%s\n", (mrset->type == MRSET_MD
564                                         ? _("Multiple dichotomy set")
565                                         : _("Multiple category set")));
566       if (mrset->label != NULL)
567         ds_put_format (&details, "%s: %s\n", _("Label"), mrset->label);
568       if (mrset->type == MRSET_MD)
569         {
570           if (mrset->label != NULL || mrset->label_from_var_label)
571             ds_put_format (&details, "%s: %s\n", _("Label source"),
572                            (mrset->label_from_var_label
573                             ? _("First variable label among variables")
574                             : _("Provided by user")));
575           ds_put_format (&details, "%s: ", _("Counted value"));
576           if (mrset->width == 0)
577             ds_put_format (&details, "%.0f\n", mrset->counted.f);
578           else
579             {
580               const uint8_t *raw = value_str (&mrset->counted, mrset->width);
581               char *utf8 = recode_string ("UTF-8", dict_get_encoding (dict),
582                                           CHAR_CAST (const char *, raw),
583                                           mrset->width);
584               ds_put_format (&details, "`%s'\n", utf8);
585               free (utf8);
586             }
587           ds_put_format (&details, "%s: %s\n", _("Category label source"),
588                          (mrset->cat_source == MRSET_VARLABELS
589                           ? _("Variable labels")
590                           : _("Value labels of counted value")));
591         }
592
593       /* Variable names. */
594       ds_clear (&var_names);
595       for (j = 0; j < mrset->n_vars; j++)
596         ds_put_format (&var_names, "%s\n", var_get_name (mrset->vars[j]));
597
598       tab_text (table, 0, row, TAB_LEFT, mrset_names[i]);
599       tab_text (table, 1, row, TAB_LEFT, ds_cstr (&var_names));
600       tab_text (table, 2, row, TAB_LEFT, ds_cstr (&details));
601     }
602   free (mrset_names);
603   ds_destroy (&var_names);
604   ds_destroy (&details);
605   stringi_set_destroy (&mrset_names_set);
606
607   tab_submit (table);
608
609   return true;
610 }