3af5d033370e9b05703580eecc4a2a64cf20e63e
[pspp-builds.git] / src / language / dictionary / mrsets.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 2011 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/dictionary.h"
21 #include "data/mrset.h"
22 #include "data/procedure.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       msg (SE, _("Required %s specification missing from %s subcommand."),
209            "NAME", subcommand_name);
210       goto error;
211     }
212   else if (mrset->n_vars == 0)
213     {
214       msg (SE, _("Required %s specification missing from %s subcommand."),
215            "VARIABLES", subcommand_name);
216       goto error;
217     }
218
219   if (type == MRSET_MD)
220     {
221       /* Check that VALUE is specified and is valid for the VARIABLES. */
222       if (!has_value)
223         {
224           msg (SE, _("Required %s specification missing from %s subcommand."),
225                "VALUE", subcommand_name);
226           goto error;
227         }
228       else if (var_is_alpha (mrset->vars[0]))
229         {
230           if (mrset->width == 0)
231             {
232               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
233                          "VALUE, but the variables specified for this group "
234                          "are numeric."),
235                    mrset->name);
236               goto error;
237             }
238           else {
239             const struct variable *shortest_var;
240             int min_width;
241             size_t i;
242
243             shortest_var = NULL;
244             min_width = INT_MAX;
245             for (i = 0; i < mrset->n_vars; i++)
246               {
247                 int width = var_get_width (mrset->vars[i]);
248                 if (width < min_width)
249                   {
250                     shortest_var = mrset->vars[i];
251                     min_width = width;
252                   }
253               }
254             if (mrset->width > min_width)
255               {
256                 msg (SE, _("VALUE string on MDGROUP subcommand for group "
257                            "%s is %d bytes long, but it must be no longer "
258                            "than the narrowest variable in the group, "
259                            "which is %s with a width of %d bytes."),
260                      mrset->name, mrset->width,
261                      var_get_name (shortest_var), min_width);
262                 goto error;
263               }
264           }
265         }
266       else
267         {
268           if (mrset->width != 0)
269             {
270               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
271                          "VALUE, but the variables specified for this group "
272                          "are numeric."),
273                    mrset->name);
274               goto error;
275             }
276         }
277
278       /* Implement LABELSOURCE=VARLABEL. */
279       if (labelsource_varlabel)
280         {
281           if (mrset->cat_source != MRSET_COUNTEDVALUES)
282             msg (SW, _("MDGROUP subcommand for group %s specifies "
283                        "LABELSOURCE=VARLABEL but not "
284                        "CATEGORYLABELS=COUNTEDVALUES.  "
285                        "Ignoring LABELSOURCE."),
286                  mrset->name);
287           else if (mrset->label)
288             msg (SW, _("MDGROUP subcommand for group %s specifies both LABEL "
289                        "and LABELSOURCE, but only one of these subcommands "
290                        "may be used at a time.  Ignoring LABELSOURCE."),
291                  mrset->name);
292           else
293             {
294               size_t i;
295
296               mrset->label_from_var_label = true;
297               for (i = 0; mrset->label == NULL && i < mrset->n_vars; i++)
298                 {
299                   const char *label = var_get_label (mrset->vars[i]);
300                   if (label != NULL)
301                     {
302                       mrset->label = xstrdup (label);
303                       break;
304                     }
305                 }
306             }
307         }
308
309       /* Warn if categories cannot be distinguished in output. */
310       if (mrset->cat_source == MRSET_VARLABELS)
311         {
312           struct stringi_map seen;
313           size_t i;
314
315           stringi_map_init (&seen);
316           for (i = 0; i < mrset->n_vars; i++)
317             {
318               const struct variable *var = mrset->vars[i];
319               const char *name = var_get_name (var);
320               const char *label = var_get_label (var);
321               if (label != NULL)
322                 {
323                   const char *other_name = stringi_map_find (&seen, label);
324
325                   if (other_name == NULL)
326                     stringi_map_insert (&seen, label, name);
327                   else
328                     msg (SW, _("Variables %s and %s specified as part of "
329                                "multiple dichotomy group %s have the same "
330                                "variable label.  Categories represented by "
331                                "these variables will not be distinguishable "
332                                "in output."),
333                          other_name, name, mrset->name);
334                 }
335             }
336           stringi_map_destroy (&seen);
337         }
338       else
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 struct val_labs *val_labs;
349               union value value;
350               const char *label;
351
352               value_clone (&value, &mrset->counted, mrset->width);
353               value_resize (&value, mrset->width, var_get_width (var));
354
355               val_labs = var_get_value_labels (var);
356               label = val_labs_find (val_labs, &value);
357               if (label == NULL)
358                 msg (SW, _("Variable %s specified as part of multiple "
359                            "dichotomy group %s (which has "
360                            "CATEGORYLABELS=COUNTEDVALUES) has no value label "
361                            "for its counted value.  This category will not "
362                            "be distinguishable in output."),
363                      name, mrset->name);
364               else
365                 {
366                   const char *other_name = stringi_map_find (&seen, label);
367
368                   if (other_name == NULL)
369                     stringi_map_insert (&seen, label, name);
370                   else
371                     msg (SW, _("Variables %s and %s specified as part of "
372                                "multiple dichotomy group %s (which has "
373                                "CATEGORYLABELS=COUNTEDVALUES) have the same "
374                                "value label for the the group's counted "
375                                "value.  These categories will not be "
376                                "distinguishable in output."),
377                          other_name, name, mrset->name);
378                 }
379             }
380           stringi_map_destroy (&seen);
381         }
382     }
383   else                          /* MCGROUP. */
384     {
385       /* Warn if categories cannot be distinguished in output. */
386       struct category
387         {
388           struct hmap_node hmap_node;
389           union value value;
390           int width;
391           const char *label;
392           const char *var_name;
393           bool warned;
394         };
395
396       struct category *c, *next;
397       struct hmap categories;
398       size_t i;
399
400       hmap_init (&categories);
401       for (i = 0; i < mrset->n_vars; i++)
402         {
403           const struct variable *var = mrset->vars[i];
404           const char *name = var_get_name (var);
405           int width = var_get_width (var);
406           const struct val_labs *val_labs;
407           const struct val_lab *vl;
408
409           val_labs = var_get_value_labels (var);
410           for (vl = val_labs_first (val_labs); vl != NULL;
411                vl = val_labs_next (val_labs, vl))
412             {
413               const union value *value = val_lab_get_value (vl);
414               const char *label = val_lab_get_label (vl);
415               unsigned int hash = value_hash (value, width, 0);
416
417               HMAP_FOR_EACH_WITH_HASH (c, struct category, hmap_node,
418                                        hash, &categories)
419                 {
420                   if (width == c->width
421                       && value_equal (value, &c->value, width))
422                     {
423                       if (!c->warned && strcasecmp (c->label, label))
424                         {
425                           char *s = data_out (value, var_get_encoding (var),
426                                               var_get_print_format (var));
427                           c->warned = true;
428                           msg (SW, _("Variables specified on MCGROUP should "
429                                      "have the same categories, but %s and %s "
430                                      "(and possibly others) in multiple "
431                                      "category group %s have different "
432                                      "value labels for value %s."),
433                                c->var_name, name, mrset->name, s);
434                           free (s);
435                         }
436                       goto found;
437                     }
438                 }
439
440               c = xmalloc (sizeof *c);
441               value_clone (&c->value, value, width);
442               c->width = width;
443               c->label = label;
444               c->var_name = name;
445               c->warned = false;
446               hmap_insert (&categories, &c->hmap_node, hash);
447
448             found: ;
449             }
450         }
451
452       HMAP_FOR_EACH_SAFE (c, next, struct category, hmap_node, &categories)
453         {
454           value_destroy (&c->value, c->width);
455           hmap_delete (&categories, &c->hmap_node);
456           free (c);
457         }
458       hmap_destroy (&categories);
459     }
460
461   dict_add_mrset (dict, mrset);
462   return true;
463
464 error:
465   mrset_destroy (mrset);
466   return false;
467 }
468
469 static bool
470 parse_mrset_names (struct lexer *lexer, struct dictionary *dict,
471                    struct stringi_set *mrset_names)
472 {
473   if (!lex_force_match_id (lexer, "NAME")
474       || !lex_force_match (lexer, T_EQUALS))
475     return false;
476
477   stringi_set_init (mrset_names);
478   if (lex_match (lexer, T_LBRACK))
479     {
480       while (!lex_match (lexer, T_RBRACK))
481         {
482           if (!lex_force_id (lexer))
483             return false;
484           if (dict_lookup_mrset (dict, lex_tokcstr (lexer)) == NULL)
485             {
486               msg (SE, _("No multiple response set named %s."),
487                    lex_tokcstr (lexer));
488               stringi_set_destroy (mrset_names);
489               return false;
490             }
491           stringi_set_insert (mrset_names, lex_tokcstr (lexer));
492           lex_get (lexer);
493         }
494     }
495   else if (lex_match (lexer, T_ALL))
496     {
497       size_t n_sets = dict_get_n_mrsets (dict);
498       size_t i;
499
500       for (i = 0; i < n_sets; i++)
501         stringi_set_insert (mrset_names, dict_get_mrset (dict, i)->name);
502     }
503
504   return true;
505 }
506
507 static bool
508 parse_delete (struct lexer *lexer, struct dictionary *dict)
509 {
510   const struct stringi_set_node *node;
511   struct stringi_set mrset_names;
512   const char *name;
513
514   if (!parse_mrset_names (lexer, dict, &mrset_names))
515     return false;
516
517   STRINGI_SET_FOR_EACH (name, node, &mrset_names)
518     dict_delete_mrset (dict, name);
519   stringi_set_destroy (&mrset_names);
520
521   return true;
522 }
523
524 static bool
525 parse_display (struct lexer *lexer, struct dictionary *dict)
526 {
527   struct string details, var_names;
528   struct stringi_set mrset_names_set;
529   char **mrset_names;
530   struct tab_table *table;
531   size_t i, n;
532
533   if (!parse_mrset_names (lexer, dict, &mrset_names_set))
534     return false;
535
536   n = stringi_set_count (&mrset_names_set);
537   if (n == 0)
538     {
539       if (dict_get_n_mrsets (dict) == 0)
540         msg (SN, _("The active file dictionary does not contain any multiple "
541                    "response sets."));
542       stringi_set_destroy (&mrset_names_set);
543       return true;
544     }
545
546   table = tab_create (3, n + 1);
547   tab_headers (table, 0, 0, 1, 0);
548   tab_box (table, TAL_1, TAL_1, TAL_1, TAL_1, 0, 0, 2, n);
549   tab_hline (table, TAL_2, 0, 2, 1);
550   tab_title (table, "%s", _("Multiple Response Sets"));
551   tab_text (table, 0, 0, TAB_EMPH | TAB_LEFT, _("Name"));
552   tab_text (table, 1, 0, TAB_EMPH | TAB_LEFT, _("Variables"));
553   tab_text (table, 2, 0, TAB_EMPH | TAB_LEFT, _("Details"));
554
555   ds_init_empty (&details);
556   ds_init_empty (&var_names);
557   mrset_names = stringi_set_get_sorted_array (&mrset_names_set);
558   for (i = 0; i < n; i++)
559     {
560       const struct mrset *mrset = dict_lookup_mrset (dict, mrset_names[i]);
561       const int row = i + 1;
562       size_t j;
563
564       /* Details. */
565       ds_clear (&details);
566       ds_put_format (&details, "%s\n", (mrset->type == MRSET_MD
567                                         ? _("Multiple dichotomy set")
568                                         : _("Multiple category set")));
569       if (mrset->label != NULL)
570         ds_put_format (&details, "%s: %s\n", _("Label"), mrset->label);
571       if (mrset->type == MRSET_MD)
572         {
573           if (mrset->label != NULL || mrset->label_from_var_label)
574             ds_put_format (&details, "%s: %s\n", _("Label source"),
575                            (mrset->label_from_var_label
576                             ? _("First variable label among variables")
577                             : _("Provided by user")));
578           ds_put_format (&details, "%s: ", _("Counted value"));
579           if (mrset->width == 0)
580             ds_put_format (&details, "%.0f\n", mrset->counted.f);
581           else
582             ds_put_format (&details, "`%.*s'\n", mrset->width,
583                            value_str (&mrset->counted, mrset->width));
584           ds_put_format (&details, "%s: %s\n", _("Category label source"),
585                          (mrset->cat_source == MRSET_VARLABELS
586                           ? _("Variable labels")
587                           : _("Value labels of counted value")));
588         }
589
590       /* Variable names. */
591       ds_clear (&var_names);
592       for (j = 0; j < mrset->n_vars; j++)
593         ds_put_format (&var_names, "%s\n", var_get_name (mrset->vars[j]));
594
595       tab_text (table, 0, row, TAB_LEFT, mrset_names[i]);
596       tab_text (table, 1, row, TAB_LEFT, ds_cstr (&var_names));
597       tab_text (table, 2, row, TAB_LEFT, ds_cstr (&details));
598     }
599   free (mrset_names);
600   ds_destroy (&var_names);
601   ds_destroy (&details);
602   stringi_set_destroy (&mrset_names_set);
603
604   tab_submit (table);
605
606   return true;
607 }