Replace numerous instances of xzalloc with XZALLOC
[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/pivot-table.h"
36
37 #include "gl/xalloc.h"
38
39 #include "gettext.h"
40 #define N_(msgid) msgid
41 #define _(msgid) gettext (msgid)
42
43 static bool parse_group (struct lexer *, struct dictionary *, enum mrset_type);
44 static bool parse_delete (struct lexer *, struct dictionary *);
45 static bool parse_display (struct lexer *, struct dictionary *);
46
47 int
48 cmd_mrsets (struct lexer *lexer, struct dataset *ds)
49 {
50   struct dictionary *dict = dataset_dict (ds);
51
52   while (lex_match (lexer, T_SLASH))
53     {
54       bool ok;
55
56       if (lex_match_id (lexer, "MDGROUP"))
57         ok = parse_group (lexer, dict, MRSET_MD);
58       else if (lex_match_id (lexer, "MCGROUP"))
59         ok = parse_group (lexer, dict, MRSET_MC);
60       else if (lex_match_id (lexer, "DELETE"))
61         ok = parse_delete (lexer, dict);
62       else if (lex_match_id (lexer, "DISPLAY"))
63         ok = parse_display (lexer, dict);
64       else
65         {
66           ok = false;
67           lex_error (lexer, NULL);
68         }
69
70       if (!ok)
71         return CMD_FAILURE;
72     }
73
74   return CMD_SUCCESS;
75 }
76
77 static bool
78 parse_group (struct lexer *lexer, struct dictionary *dict,
79              enum mrset_type type)
80 {
81   const char *subcommand_name = type == MRSET_MD ? "MDGROUP" : "MCGROUP";
82   bool labelsource_varlabel;
83   bool has_value;
84
85   struct mrset *mrset = XZALLOC (struct 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 (mrset->counted.s, 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                                               settings_get_fmt_settings ());
425                           c->warned = true;
426                           msg (SW, _("Variables specified on MCGROUP should "
427                                      "have the same categories, but %s and %s "
428                                      "(and possibly others) in multiple "
429                                      "category group %s have different "
430                                      "value labels for value %s."),
431                                c->var_name, name, mrset->name, s);
432                           free (s);
433                         }
434                       goto found;
435                     }
436                 }
437
438               c = xmalloc (sizeof *c);
439               value_clone (&c->value, value, width);
440               c->width = width;
441               c->label = label;
442               c->var_name = name;
443               c->warned = false;
444               hmap_insert (&categories, &c->hmap_node, hash);
445
446             found: ;
447             }
448         }
449
450       HMAP_FOR_EACH_SAFE (c, next, struct category, hmap_node, &categories)
451         {
452           value_destroy (&c->value, c->width);
453           hmap_delete (&categories, &c->hmap_node);
454           free (c);
455         }
456       hmap_destroy (&categories);
457     }
458
459   dict_add_mrset (dict, mrset);
460   return true;
461
462 error:
463   mrset_destroy (mrset);
464   return false;
465 }
466
467 static bool
468 parse_mrset_names (struct lexer *lexer, struct dictionary *dict,
469                    struct stringi_set *mrset_names)
470 {
471   if (!lex_force_match_id (lexer, "NAME")
472       || !lex_force_match (lexer, T_EQUALS))
473     return false;
474
475   stringi_set_init (mrset_names);
476   if (lex_match (lexer, T_LBRACK))
477     {
478       while (!lex_match (lexer, T_RBRACK))
479         {
480           if (!lex_force_id (lexer))
481             return false;
482           if (dict_lookup_mrset (dict, lex_tokcstr (lexer)) == NULL)
483             {
484               msg (SE, _("No multiple response set named %s."),
485                    lex_tokcstr (lexer));
486               stringi_set_destroy (mrset_names);
487               return false;
488             }
489           stringi_set_insert (mrset_names, lex_tokcstr (lexer));
490           lex_get (lexer);
491         }
492     }
493   else if (lex_match (lexer, T_ALL))
494     {
495       size_t n_sets = dict_get_n_mrsets (dict);
496       size_t i;
497
498       for (i = 0; i < n_sets; i++)
499         stringi_set_insert (mrset_names, dict_get_mrset (dict, i)->name);
500     }
501
502   return true;
503 }
504
505 static bool
506 parse_delete (struct lexer *lexer, struct dictionary *dict)
507 {
508   const struct stringi_set_node *node;
509   struct stringi_set mrset_names;
510   const char *name;
511
512   if (!parse_mrset_names (lexer, dict, &mrset_names))
513     return false;
514
515   STRINGI_SET_FOR_EACH (name, node, &mrset_names)
516     dict_delete_mrset (dict, name);
517   stringi_set_destroy (&mrset_names);
518
519   return true;
520 }
521
522 static bool
523 parse_display (struct lexer *lexer, struct dictionary *dict)
524 {
525   struct stringi_set mrset_names_set;
526   if (!parse_mrset_names (lexer, dict, &mrset_names_set))
527     return false;
528
529   size_t n = stringi_set_count (&mrset_names_set);
530   if (n == 0)
531     {
532       if (dict_get_n_mrsets (dict) == 0)
533         msg (SN, _("The active dataset dictionary does not contain any "
534                    "multiple response sets."));
535       stringi_set_destroy (&mrset_names_set);
536       return true;
537     }
538
539   struct pivot_table *table = pivot_table_create (
540     N_("Multiple Response Sets"));
541
542   pivot_dimension_create (
543     table, PIVOT_AXIS_COLUMN, N_("Attributes"),
544     N_("Label"), N_("Encoding"), N_("Counted Value"), N_("Member Variables"));
545
546   struct pivot_dimension *mrsets = pivot_dimension_create (
547     table, PIVOT_AXIS_ROW, N_("Name"));
548   mrsets->root->show_label = true;
549
550   char **mrset_names = stringi_set_get_sorted_array (&mrset_names_set);
551   for (size_t i = 0; i < n; i++)
552     {
553       const struct mrset *mrset = dict_lookup_mrset (dict, mrset_names[i]);
554
555       int row = pivot_category_create_leaf (
556         mrsets->root, pivot_value_new_user_text (mrset->name, -1));
557
558       if (mrset->label != NULL)
559         pivot_table_put2 (table, 0, row,
560                           pivot_value_new_user_text (mrset->label, -1));
561
562       pivot_table_put2 (table, 1, row,
563                         pivot_value_new_text (mrset->type == MRSET_MD
564                                               ? _("Dichotomies")
565                                               : _("Categories")));
566
567       if (mrset->type == MRSET_MD)
568         pivot_table_put2 (table, 2, row,
569                           pivot_value_new_value (
570                             &mrset->counted, mrset->width,
571                             &F_8_0, dict_get_encoding (dict)));
572
573       /* Variable names. */
574       struct string var_names = DS_EMPTY_INITIALIZER;
575       for (size_t j = 0; j < mrset->n_vars; j++)
576         ds_put_format (&var_names, "%s\n", var_get_name (mrset->vars[j]));
577       ds_chomp_byte (&var_names, '\n');
578       pivot_table_put2 (table, 3, row,
579                         pivot_value_new_user_text_nocopy (
580                           ds_steal_cstr (&var_names)));
581     }
582   free (mrset_names);
583   stringi_set_destroy (&mrset_names_set);
584
585   pivot_table_submit (table);
586
587   return true;
588 }