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