output: Introduce pivot tables.
[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   struct mrset *mrset;
83   bool labelsource_varlabel;
84   bool has_value;
85
86   mrset = xzalloc (sizeof *mrset);
87   mrset->type = type;
88   mrset->cat_source = MRSET_VARLABELS;
89
90   labelsource_varlabel = false;
91   has_value = false;
92   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
93     {
94       if (lex_match_id (lexer, "NAME"))
95         {
96           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_id (lexer)
97               || !mrset_is_valid_name (lex_tokcstr (lexer),
98                                        dict_get_encoding (dict), true))
99             goto error;
100
101           free (mrset->name);
102           mrset->name = xstrdup (lex_tokcstr (lexer));
103           lex_get (lexer);
104         }
105       else if (lex_match_id (lexer, "VARIABLES"))
106         {
107           if (!lex_force_match (lexer, T_EQUALS))
108             goto error;
109
110           free (mrset->vars);
111           if (!parse_variables (lexer, dict, &mrset->vars, &mrset->n_vars,
112                                 PV_SAME_TYPE | PV_NO_SCRATCH))
113             goto error;
114
115           if (mrset->n_vars < 2)
116             {
117               msg (SE, _("VARIABLES specified only variable %s on %s, but "
118                          "at least two variables are required."),
119                    var_get_name (mrset->vars[0]), subcommand_name);
120               goto error;
121             }
122         }
123       else if (lex_match_id (lexer, "LABEL"))
124         {
125           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_string (lexer))
126             goto error;
127
128           free (mrset->label);
129           mrset->label = ss_xstrdup (lex_tokss (lexer));
130           lex_get (lexer);
131         }
132       else if (type == MRSET_MD && lex_match_id (lexer, "LABELSOURCE"))
133         {
134           if (!lex_force_match (lexer, T_EQUALS)
135               || !lex_force_match_id (lexer, "VARLABEL"))
136             goto error;
137
138           labelsource_varlabel = true;
139         }
140       else if (type == MRSET_MD && lex_match_id (lexer, "VALUE"))
141         {
142           if (!lex_force_match (lexer, T_EQUALS))
143             goto error;
144
145           has_value = true;
146           if (lex_is_number (lexer))
147             {
148               if (!lex_is_integer (lexer))
149                 {
150                   msg (SE, _("Numeric VALUE must be an integer."));
151                   goto error;
152                 }
153               value_destroy (&mrset->counted, mrset->width);
154               mrset->counted.f = lex_integer (lexer);
155               mrset->width = 0;
156             }
157           else if (lex_is_string (lexer))
158             {
159               size_t width;
160               char *s;
161
162               s = recode_string (dict_get_encoding (dict), "UTF-8",
163                                  lex_tokcstr (lexer), -1);
164               width = strlen (s);
165
166               /* Trim off trailing spaces, but don't trim the string until
167                  it's empty because a width of 0 is a numeric type. */
168               while (width > 1 && s[width - 1] == ' ')
169                 width--;
170
171               value_destroy (&mrset->counted, mrset->width);
172               value_init (&mrset->counted, width);
173               memcpy (value_str_rw (&mrset->counted, width), s, width);
174               mrset->width = width;
175
176               free (s);
177             }
178           else
179             {
180               lex_error (lexer, NULL);
181               goto error;
182             }
183           lex_get (lexer);
184         }
185       else if (type == MRSET_MD && lex_match_id (lexer, "CATEGORYLABELS"))
186         {
187           if (!lex_force_match (lexer, T_EQUALS))
188             goto error;
189
190           if (lex_match_id (lexer, "VARLABELS"))
191             mrset->cat_source = MRSET_VARLABELS;
192           else if (lex_match_id (lexer, "COUNTEDVALUES"))
193             mrset->cat_source = MRSET_COUNTEDVALUES;
194           else
195             {
196               lex_error (lexer, NULL);
197               goto error;
198             }
199         }
200       else
201         {
202           lex_error (lexer, NULL);
203           goto error;
204         }
205     }
206
207   if (mrset->name == NULL)
208     {
209       lex_spec_missing (lexer, subcommand_name, "NAME");
210       goto error;
211     }
212   else if (mrset->n_vars == 0)
213     {
214       lex_spec_missing (lexer, subcommand_name, "VARIABLES");
215       goto error;
216     }
217
218   if (type == MRSET_MD)
219     {
220       /* Check that VALUE is specified and is valid for the VARIABLES. */
221       if (!has_value)
222         {
223           lex_spec_missing (lexer, subcommand_name, "VALUE");
224           goto error;
225         }
226       else if (var_is_alpha (mrset->vars[0]))
227         {
228           if (mrset->width == 0)
229             {
230               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
231                          "VALUE, but the variables specified for this group "
232                          "are numeric."),
233                    mrset->name);
234               goto error;
235             }
236           else {
237             const struct variable *shortest_var;
238             int min_width;
239             size_t i;
240
241             shortest_var = NULL;
242             min_width = INT_MAX;
243             for (i = 0; i < mrset->n_vars; i++)
244               {
245                 int width = var_get_width (mrset->vars[i]);
246                 if (width < min_width)
247                   {
248                     shortest_var = mrset->vars[i];
249                     min_width = width;
250                   }
251               }
252             if (mrset->width > min_width)
253               {
254                 msg (SE, _("VALUE string on MDGROUP subcommand for group "
255                            "%s is %d bytes long, but it must be no longer "
256                            "than the narrowest variable in the group, "
257                            "which is %s with a width of %d bytes."),
258                      mrset->name, mrset->width,
259                      var_get_name (shortest_var), min_width);
260                 goto error;
261               }
262           }
263         }
264       else
265         {
266           if (mrset->width != 0)
267             {
268               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
269                          "VALUE, but the variables specified for this group "
270                          "are numeric."),
271                    mrset->name);
272               goto error;
273             }
274         }
275
276       /* Implement LABELSOURCE=VARLABEL. */
277       if (labelsource_varlabel)
278         {
279           if (mrset->cat_source != MRSET_COUNTEDVALUES)
280             msg (SW, _("MDGROUP subcommand for group %s specifies "
281                        "LABELSOURCE=VARLABEL but not "
282                        "CATEGORYLABELS=COUNTEDVALUES.  "
283                        "Ignoring LABELSOURCE."),
284                  mrset->name);
285           else if (mrset->label)
286             msg (SW, _("MDGROUP subcommand for group %s specifies both LABEL "
287                        "and LABELSOURCE, but only one of these subcommands "
288                        "may be used at a time.  Ignoring LABELSOURCE."),
289                  mrset->name);
290           else
291             {
292               size_t i;
293
294               mrset->label_from_var_label = true;
295               for (i = 0; mrset->label == NULL && i < mrset->n_vars; i++)
296                 {
297                   const char *label = var_get_label (mrset->vars[i]);
298                   if (label != NULL)
299                     {
300                       mrset->label = xstrdup (label);
301                       break;
302                     }
303                 }
304             }
305         }
306
307       /* Warn if categories cannot be distinguished in output. */
308       if (mrset->cat_source == MRSET_VARLABELS)
309         {
310           struct stringi_map seen;
311           size_t i;
312
313           stringi_map_init (&seen);
314           for (i = 0; i < mrset->n_vars; i++)
315             {
316               const struct variable *var = mrset->vars[i];
317               const char *name = var_get_name (var);
318               const char *label = var_get_label (var);
319               if (label != NULL)
320                 {
321                   const char *other_name = stringi_map_find (&seen, label);
322
323                   if (other_name == NULL)
324                     stringi_map_insert (&seen, label, name);
325                   else
326                     msg (SW, _("Variables %s and %s specified as part of "
327                                "multiple dichotomy group %s have the same "
328                                "variable label.  Categories represented by "
329                                "these variables will not be distinguishable "
330                                "in output."),
331                          other_name, name, mrset->name);
332                 }
333             }
334           stringi_map_destroy (&seen);
335         }
336       else
337         {
338           struct stringi_map seen;
339           size_t i;
340
341           stringi_map_init (&seen);
342           for (i = 0; i < mrset->n_vars; i++)
343             {
344               const struct variable *var = mrset->vars[i];
345               const char *name = var_get_name (var);
346               const struct val_labs *val_labs;
347               union value value;
348               const char *label;
349
350               value_clone (&value, &mrset->counted, mrset->width);
351               value_resize (&value, mrset->width, var_get_width (var));
352
353               val_labs = var_get_value_labels (var);
354               label = val_labs_find (val_labs, &value);
355               if (label == NULL)
356                 msg (SW, _("Variable %s specified as part of multiple "
357                            "dichotomy group %s (which has "
358                            "CATEGORYLABELS=COUNTEDVALUES) has no value label "
359                            "for its counted value.  This category will not "
360                            "be distinguishable in output."),
361                      name, mrset->name);
362               else
363                 {
364                   const char *other_name = stringi_map_find (&seen, label);
365
366                   if (other_name == NULL)
367                     stringi_map_insert (&seen, label, name);
368                   else
369                     msg (SW, _("Variables %s and %s specified as part of "
370                                "multiple dichotomy group %s (which has "
371                                "CATEGORYLABELS=COUNTEDVALUES) have the same "
372                                "value label for the group's counted "
373                                "value.  These categories will not be "
374                                "distinguishable in output."),
375                          other_name, name, mrset->name);
376                 }
377             }
378           stringi_map_destroy (&seen);
379         }
380     }
381   else                          /* MCGROUP. */
382     {
383       /* Warn if categories cannot be distinguished in output. */
384       struct category
385         {
386           struct hmap_node hmap_node;
387           union value value;
388           int width;
389           const char *label;
390           const char *var_name;
391           bool warned;
392         };
393
394       struct category *c, *next;
395       struct hmap categories;
396       size_t i;
397
398       hmap_init (&categories);
399       for (i = 0; i < mrset->n_vars; i++)
400         {
401           const struct variable *var = mrset->vars[i];
402           const char *name = var_get_name (var);
403           int width = var_get_width (var);
404           const struct val_labs *val_labs;
405           const struct val_lab *vl;
406
407           val_labs = var_get_value_labels (var);
408           for (vl = val_labs_first (val_labs); vl != NULL;
409                vl = val_labs_next (val_labs, vl))
410             {
411               const union value *value = val_lab_get_value (vl);
412               const char *label = val_lab_get_label (vl);
413               unsigned int hash = value_hash (value, width, 0);
414
415               HMAP_FOR_EACH_WITH_HASH (c, struct category, hmap_node,
416                                        hash, &categories)
417                 {
418                   if (width == c->width
419                       && value_equal (value, &c->value, width))
420                     {
421                       if (!c->warned && utf8_strcasecmp (c->label, label))
422                         {
423                           char *s = data_out (value, var_get_encoding (var),
424                                               var_get_print_format (var));
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 }