DISPLAY MACROS: New command.
[pspp] / src / language / commands / sys-file-info.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011, 2012, 2013, 2014 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 #include <config.h>
17
18 #include <ctype.h>
19 #include <errno.h>
20 #include <float.h>
21 #include <stdlib.h>
22
23 #include "data/any-reader.h"
24 #include "data/attributes.h"
25 #include "data/casereader.h"
26 #include "data/dataset.h"
27 #include "data/dictionary.h"
28 #include "data/file-handle-def.h"
29 #include "data/format.h"
30 #include "data/missing-values.h"
31 #include "data/value-labels.h"
32 #include "data/variable.h"
33 #include "data/vector.h"
34 #include "language/command.h"
35 #include "language/commands/file-handle.h"
36 #include "language/lexer/lexer.h"
37 #include "language/lexer/macro.h"
38 #include "language/lexer/variable-parser.h"
39 #include "libpspp/array.h"
40 #include "libpspp/hash-functions.h"
41 #include "libpspp/i18n.h"
42 #include "libpspp/message.h"
43 #include "libpspp/misc.h"
44 #include "libpspp/pool.h"
45 #include "libpspp/string-array.h"
46 #include "output/pivot-table.h"
47 #include "output/output-item.h"
48
49 #include "gl/count-one-bits.h"
50 #include "gl/localcharset.h"
51 #include "gl/intprops.h"
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
54
55 #include "gettext.h"
56 #define _(msgid) gettext (msgid)
57 #define N_(msgid) (msgid)
58
59 /* Information to include in displaying a dictionary. */
60 enum
61   {
62     /* Variable table. */
63     DF_NAME              = 1 << 0,
64     DF_POSITION          = 1 << 1,
65     DF_LABEL             = 1 << 2,
66     DF_MEASUREMENT_LEVEL = 1 << 3,
67     DF_ROLE              = 1 << 4,
68     DF_WIDTH             = 1 << 5,
69     DF_ALIGNMENT         = 1 << 6,
70     DF_PRINT_FORMAT      = 1 << 7,
71     DF_WRITE_FORMAT      = 1 << 8,
72     DF_MISSING_VALUES    = 1 << 9,
73 #define DF_ALL_VARIABLE ((1 << 10) - 1)
74
75     /* Value labels table. */
76     DF_VALUE_LABELS      = 1 << 10,
77
78     /* Attribute table. */
79     DF_AT_ATTRIBUTES     = 1 << 11, /* Attributes whose names begin with @. */
80     DF_ATTRIBUTES        = 1 << 12, /* All other attributes. */
81   };
82
83 static void display_variables (const struct variable **, size_t, int flags);
84 static void display_value_labels (const struct variable **, size_t);
85 static void display_attributes (const struct attrset *,
86                                 const struct variable **, size_t, int flags);
87
88 static void report_encodings (const struct file_handle *, struct pool *,
89                               char **titles, bool *ids,
90                               char **strings, size_t n_strings);
91
92 static char *get_documents_as_string (const struct dictionary *);
93
94 static void
95 add_row (struct pivot_table *table, const char *attribute,
96          struct pivot_value *value)
97 {
98   int row = pivot_category_create_leaf (table->dimensions[0]->root,
99                                         pivot_value_new_text (attribute));
100   if (value)
101     pivot_table_put1 (table, row, value);
102 }
103
104 /* SYSFILE INFO utility. */
105 int
106 cmd_sysfile_info (struct lexer *lexer, struct dataset *ds)
107 {
108   struct any_reader *any_reader;
109   struct file_handle *h;
110   struct dictionary *d;
111   struct casereader *reader;
112   struct any_read_info info;
113   char *encoding;
114
115   h = NULL;
116   encoding = NULL;
117   for (;;)
118     {
119       lex_match (lexer, T_SLASH);
120
121       if (lex_match_id (lexer, "FILE") || lex_is_string (lexer))
122         {
123           lex_match (lexer, T_EQUALS);
124
125           fh_unref (h);
126           h = fh_parse (lexer, FH_REF_FILE, NULL);
127           if (h == NULL)
128             goto error;
129         }
130       else if (lex_match_id (lexer, "ENCODING"))
131         {
132           lex_match (lexer, T_EQUALS);
133
134           if (!lex_force_string (lexer))
135             goto error;
136
137           free (encoding);
138           encoding = ss_xstrdup (lex_tokss (lexer));
139
140           lex_get (lexer);
141         }
142       else
143         break;
144     }
145
146   if (h == NULL)
147     {
148       lex_sbc_missing (lexer, "FILE");
149       goto error;
150     }
151
152   any_reader = any_reader_open (h);
153   if (!any_reader)
154     {
155       free (encoding);
156       return CMD_FAILURE;
157     }
158
159   if (encoding && !strcasecmp (encoding, "detect"))
160     {
161       char **titles, **strings;
162       struct pool *pool;
163       size_t n_strings;
164       bool *ids;
165
166       pool = pool_create ();
167       n_strings = any_reader_get_strings (any_reader, pool,
168                                           &titles, &ids, &strings);
169       any_reader_close (any_reader);
170
171       report_encodings (h, pool, titles, ids, strings, n_strings);
172       fh_unref (h);
173       pool_destroy (pool);
174       free (encoding);
175
176       return CMD_SUCCESS;
177     }
178
179   reader = any_reader_decode (any_reader, encoding, &d, &info);
180   if (!reader)
181     goto error;
182   casereader_destroy (reader);
183
184   struct pivot_table *table = pivot_table_create (N_("File Information"));
185   pivot_dimension_create (table, PIVOT_AXIS_ROW, N_("Attribute"));
186
187   add_row (table, N_("File"),
188            pivot_value_new_user_text (fh_get_file_name (h), -1));
189
190   const char *label = dict_get_label (d);
191   add_row (table, N_("Label"),
192            label ? pivot_value_new_user_text (label, -1) : NULL);
193
194   add_row (table, N_("Created"),
195            pivot_value_new_user_text_nocopy (
196              xasprintf ("%s %s by %s", info.creation_date,
197                         info.creation_time, info.product)));
198
199   if (info.product_ext)
200     add_row (table, N_("Product"),
201              pivot_value_new_user_text (info.product_ext, -1));
202
203   add_row (table, N_("Integer Format"),
204            pivot_value_new_text (
205              info.integer_format == INTEGER_MSB_FIRST ? N_("Big Endian")
206              : info.integer_format == INTEGER_LSB_FIRST ? N_("Little Endian")
207              : N_("Unknown")));
208
209   add_row (table, N_("Real Format"),
210            pivot_value_new_text (
211              info.float_format == FLOAT_IEEE_DOUBLE_LE ? N_("IEEE 754 LE.")
212              : info.float_format == FLOAT_IEEE_DOUBLE_BE ? N_("IEEE 754 BE.")
213              : info.float_format == FLOAT_VAX_D ? N_("VAX D.")
214              : info.float_format == FLOAT_VAX_G ? N_("VAX G.")
215              : info.float_format == FLOAT_Z_LONG ? N_("IBM 390 Hex Long.")
216              : N_("Unknown")));
217
218   add_row (table, N_("Variables"),
219            pivot_value_new_integer (dict_get_n_vars (d)));
220
221   add_row (table, N_("Cases"),
222            (info.n_cases == -1
223             ? pivot_value_new_text (N_("Unknown"))
224             : pivot_value_new_integer (info.n_cases)));
225
226   add_row (table, N_("Type"),
227            pivot_value_new_text (info.klass->name));
228
229   struct variable *weight_var = dict_get_weight (d);
230   add_row (table, N_("Weight"),
231            (weight_var
232             ? pivot_value_new_variable (weight_var)
233             : pivot_value_new_text (N_("Not weighted"))));
234
235   add_row (table, N_("Compression"),
236            (info.compression == ANY_COMP_NONE
237             ? pivot_value_new_text (N_("None"))
238             : pivot_value_new_user_text (
239               info.compression == ANY_COMP_SIMPLE ? "SAV" : "ZSAV", -1)));
240
241   add_row (table, N_("Encoding"),
242            pivot_value_new_user_text (dict_get_encoding (d), -1));
243
244   if (dict_get_document_n_lines (d) > 0)
245     add_row (table, N_("Documents"),
246              pivot_value_new_user_text_nocopy (get_documents_as_string (d)));
247
248   pivot_table_submit (table);
249
250   size_t n_vars = dict_get_n_vars (d);
251   const struct variable **vars = xnmalloc (n_vars, sizeof *vars);
252   for (size_t i = 0; i < dict_get_n_vars (d); i++)
253     vars[i] = dict_get_var (d, i);
254   display_variables (vars, n_vars, DF_ALL_VARIABLE);
255   display_value_labels (vars, n_vars);
256   display_attributes (dict_get_attributes (dataset_dict (ds)),
257                       vars, n_vars, DF_ATTRIBUTES);
258   free (vars);
259
260   dict_unref (d);
261
262   fh_unref (h);
263   free (encoding);
264   any_read_info_destroy (&info);
265   return CMD_SUCCESS;
266
267 error:
268   fh_unref (h);
269   free (encoding);
270   return CMD_FAILURE;
271 }
272 \f
273 /* DISPLAY utility. */
274
275 static void display_documents (const struct dictionary *dict);
276 static void display_vectors (const struct dictionary *dict, int sorted);
277
278 int
279 cmd_display (struct lexer *lexer, struct dataset *ds)
280 {
281   /* Variables to display. */
282   size_t n;
283   const struct variable **vl;
284
285   if (lex_match_id (lexer, "DOCUMENTS"))
286     display_documents (dataset_dict (ds));
287   else if (lex_match_id (lexer, "FILE"))
288     {
289       if (!lex_force_match_id (lexer, "LABEL"))
290         return CMD_FAILURE;
291
292       const char *label = dict_get_label (dataset_dict (ds));
293
294       struct pivot_table *table = pivot_table_create (N_("File Label"));
295       pivot_dimension_create (table, PIVOT_AXIS_ROW, N_("Label"),
296                               N_("Label"));
297       pivot_table_put1 (table, 0,
298                         (label ? pivot_value_new_user_text (label, -1)
299                          : pivot_value_new_text (N_("(none)"))));
300       pivot_table_submit (table);
301     }
302   else
303     {
304       int flags;
305
306       bool sorted = lex_match_id (lexer, "SORTED");
307
308       if (lex_match_id (lexer, "VECTORS"))
309         {
310           display_vectors (dataset_dict(ds), sorted);
311           return CMD_SUCCESS;
312         }
313       else if (lex_match_id (lexer, "SCRATCH"))
314         {
315           dict_get_vars (dataset_dict (ds), &vl, &n, DC_ORDINARY);
316           flags = DF_NAME;
317         }
318       else
319         {
320           struct subcommand
321             {
322               const char *name;
323               int flags;
324             };
325           static const struct subcommand subcommands[] =
326             {
327               {"@ATTRIBUTES", DF_ATTRIBUTES | DF_AT_ATTRIBUTES},
328               {"ATTRIBUTES", DF_ATTRIBUTES},
329               {"DICTIONARY", (DF_NAME | DF_POSITION | DF_LABEL
330                               | DF_MEASUREMENT_LEVEL | DF_ROLE | DF_WIDTH
331                               | DF_ALIGNMENT | DF_PRINT_FORMAT
332                               | DF_WRITE_FORMAT | DF_MISSING_VALUES
333                               | DF_VALUE_LABELS)},
334               {"INDEX", DF_NAME | DF_POSITION},
335               {"LABELS", DF_NAME | DF_POSITION | DF_LABEL},
336               {"NAMES", DF_NAME},
337               {"VARIABLES", (DF_NAME | DF_POSITION | DF_PRINT_FORMAT
338                              | DF_WRITE_FORMAT | DF_MISSING_VALUES)},
339               {NULL, 0},
340             };
341           const struct subcommand *sbc;
342           struct dictionary *dict = dataset_dict (ds);
343
344           flags = 0;
345           for (sbc = subcommands; sbc->name != NULL; sbc++)
346             if (lex_match_id (lexer, sbc->name))
347               {
348                 flags = sbc->flags;
349                 break;
350               }
351
352           lex_match (lexer, T_SLASH);
353           lex_match_id (lexer, "VARIABLES");
354           lex_match (lexer, T_EQUALS);
355
356           if (lex_token (lexer) != T_ENDCMD)
357             {
358               if (!parse_variables_const (lexer, dict, &vl, &n, PV_NONE))
359                 {
360                   free (vl);
361                   return CMD_FAILURE;
362                 }
363             }
364           else
365             dict_get_vars (dict, &vl, &n, 0);
366         }
367
368       if (n > 0)
369         {
370           sort (vl, n, sizeof *vl, (sorted
371                                     ? compare_var_ptrs_by_name
372                                     : compare_var_ptrs_by_dict_index), NULL);
373
374           int variable_flags = flags & DF_ALL_VARIABLE;
375           if (variable_flags)
376             display_variables (vl, n, variable_flags);
377
378           if (flags & DF_VALUE_LABELS)
379             display_value_labels (vl, n);
380
381           int attribute_flags = flags & (DF_ATTRIBUTES | DF_AT_ATTRIBUTES);
382           if (attribute_flags)
383             display_attributes (dict_get_attributes (dataset_dict (ds)),
384                                 vl, n, attribute_flags);
385         }
386       else
387         msg (SN, _("No variables to display."));
388
389       free (vl);
390     }
391
392   return CMD_SUCCESS;
393 }
394
395 static int
396 compare_macros_by_name (const void *a_, const void *b_, const void *aux UNUSED)
397 {
398   const struct macro *const *ap = a_;
399   const struct macro *const *bp = b_;
400   const struct macro *a = *ap;
401   const struct macro *b = *bp;
402
403   return utf8_strcasecmp (a->name, b->name);
404 }
405
406 int
407 cmd_display_macros (struct lexer *lexer, struct dataset *ds UNUSED)
408 {
409   const struct macro_set *set = lex_get_macros (lexer);
410
411   if (hmap_is_empty (&set->macros))
412     {
413       msg (SN, _("No macros to display."));
414       return CMD_SUCCESS;
415     }
416
417   const struct macro **macros = xnmalloc (hmap_count (&set->macros),
418                                           sizeof *macros);
419   size_t n = 0;
420   const struct macro *m;
421   HMAP_FOR_EACH (m, struct macro, hmap_node, &set->macros)
422     macros[n++] = m;
423   assert (n == hmap_count (&set->macros));
424   sort (macros, n, sizeof *macros, compare_macros_by_name, NULL);
425
426   struct pivot_table *table = pivot_table_create (N_("Macros"));
427
428   struct pivot_dimension *attributes = pivot_dimension_create (
429     table, PIVOT_AXIS_COLUMN, N_("Attributes"));
430   pivot_category_create_leaf (attributes->root,
431                               pivot_value_new_text (N_("Source Location")));
432
433   struct pivot_dimension *names = pivot_dimension_create (
434     table, PIVOT_AXIS_ROW, N_("Name"));
435   names->root->show_label = true;
436
437   for (size_t i = 0; i < n; i++)
438     {
439       const struct macro *m = macros[i];
440
441       pivot_category_create_leaf (names->root,
442                                   pivot_value_new_user_text (m->name, -1));
443
444       struct string location = DS_EMPTY_INITIALIZER;
445       msg_location_format (m->location, &location);
446       pivot_table_put2 (
447         table, 0, i,
448         pivot_value_new_user_text_nocopy (ds_steal_cstr (&location)));
449     }
450
451   pivot_table_submit (table);
452
453   free (macros);
454
455   return CMD_SUCCESS;
456 }
457
458 static char *
459 get_documents_as_string (const struct dictionary *dict)
460 {
461   const struct string_array *documents = dict_get_documents (dict);
462   struct string s = DS_EMPTY_INITIALIZER;
463   for (size_t i = 0; i < documents->n; i++)
464     {
465       if (i)
466         ds_put_byte (&s, '\n');
467       ds_put_cstr (&s, documents->strings[i]);
468     }
469   return ds_steal_cstr (&s);
470 }
471
472 static void
473 display_documents (const struct dictionary *dict)
474 {
475   struct pivot_table *table = pivot_table_create (N_("Documents"));
476   struct pivot_dimension *d = pivot_dimension_create (
477     table, PIVOT_AXIS_COLUMN, N_("Documents"), N_("Document"));
478   d->hide_all_labels = true;
479
480   if (!dict_get_documents (dict)->n)
481     pivot_table_put1 (table, 0, pivot_value_new_text (N_("(none)")));
482   else
483     {
484       char *docs = get_documents_as_string (dict);
485       pivot_table_put1 (table, 0, pivot_value_new_user_text_nocopy (docs));
486     }
487
488   pivot_table_submit (table);
489 }
490
491 static void
492 display_variables (const struct variable **vl, size_t n, int flags)
493 {
494   struct pivot_table *table = pivot_table_create (N_("Variables"));
495
496   struct pivot_dimension *attributes = pivot_dimension_create (
497     table, PIVOT_AXIS_COLUMN, N_("Attributes"));
498
499   struct heading
500     {
501       int flag;
502       const char *title;
503     };
504   static const struct heading headings[] = {
505     { DF_POSITION, N_("Position") },
506     { DF_LABEL, N_("Label") },
507     { DF_MEASUREMENT_LEVEL, N_("Measurement Level") },
508     { DF_ROLE, N_("Role") },
509     { DF_WIDTH, N_("Width") },
510     { DF_ALIGNMENT, N_("Alignment") },
511     { DF_PRINT_FORMAT, N_("Print Format") },
512     { DF_WRITE_FORMAT, N_("Write Format") },
513     { DF_MISSING_VALUES, N_("Missing Values") },
514   };
515   for (size_t i = 0; i < sizeof headings / sizeof *headings; i++)
516     if (flags & headings[i].flag)
517       pivot_category_create_leaf (attributes->root,
518                                   pivot_value_new_text (headings[i].title));
519
520   struct pivot_dimension *names = pivot_dimension_create (
521     table, PIVOT_AXIS_ROW, N_("Name"));
522   names->root->show_label = true;
523
524   for (size_t i = 0; i < n; i++)
525     {
526       const struct variable *v = vl[i];
527
528       struct pivot_value *name = pivot_value_new_variable (v);
529       name->variable.show = SETTINGS_VALUE_SHOW_VALUE;
530       int row = pivot_category_create_leaf (names->root, name);
531
532       int x = 0;
533       if (flags & DF_POSITION)
534         pivot_table_put2 (table, x++, row, pivot_value_new_integer (
535                             var_get_dict_index (v) + 1));
536
537       if (flags & DF_LABEL)
538         {
539           const char *label = var_get_label (v);
540           if (label)
541             pivot_table_put2 (table, x, row,
542                               pivot_value_new_user_text (label, -1));
543           x++;
544         }
545
546       if (flags & DF_MEASUREMENT_LEVEL)
547         pivot_table_put2 (
548           table, x++, row,
549           pivot_value_new_text (measure_to_string (var_get_measure (v))));
550
551       if (flags & DF_ROLE)
552         pivot_table_put2 (
553           table, x++, row,
554           pivot_value_new_text (var_role_to_string (var_get_role (v))));
555
556       if (flags & DF_WIDTH)
557         pivot_table_put2 (
558           table, x++, row,
559           pivot_value_new_integer (var_get_display_width (v)));
560
561       if (flags & DF_ALIGNMENT)
562         pivot_table_put2 (
563           table, x++, row,
564           pivot_value_new_text (alignment_to_string (
565                                   var_get_alignment (v))));
566
567       if (flags & DF_PRINT_FORMAT)
568         {
569           const struct fmt_spec *print = var_get_print_format (v);
570           char s[FMT_STRING_LEN_MAX + 1];
571
572           pivot_table_put2 (
573             table, x++, row,
574             pivot_value_new_user_text (fmt_to_string (print, s), -1));
575         }
576
577       if (flags & DF_WRITE_FORMAT)
578         {
579           const struct fmt_spec *write = var_get_write_format (v);
580           char s[FMT_STRING_LEN_MAX + 1];
581
582           pivot_table_put2 (
583             table, x++, row,
584             pivot_value_new_user_text (fmt_to_string (write, s), -1));
585         }
586
587       if (flags & DF_MISSING_VALUES)
588         {
589           char *s = mv_to_string (var_get_missing_values (v),
590                                   var_get_encoding (v));
591           if (s)
592             pivot_table_put2 (
593               table, x, row,
594               pivot_value_new_user_text_nocopy (s));
595
596           x++;
597         }
598     }
599
600   pivot_table_submit (table);
601 }
602
603 static bool
604 any_value_labels (const struct variable **vars, size_t n_vars)
605 {
606   for (size_t i = 0; i < n_vars; i++)
607     if (val_labs_count (var_get_value_labels (vars[i])))
608       return true;
609   return false;
610 }
611
612 static void
613 display_value_labels (const struct variable **vars, size_t n_vars)
614 {
615   if (!any_value_labels (vars, n_vars))
616     return;
617
618   struct pivot_table *table = pivot_table_create (N_("Value Labels"));
619
620   pivot_dimension_create (table, PIVOT_AXIS_COLUMN,
621                           N_("Label"), N_("Label"));
622
623   struct pivot_dimension *values = pivot_dimension_create (
624     table, PIVOT_AXIS_ROW, N_("Variable Value"));
625   values->root->show_label = true;
626
627   struct pivot_footnote *missing_footnote = pivot_table_create_footnote (
628     table, pivot_value_new_text (N_("User-missing value")));
629
630   for (size_t i = 0; i < n_vars; i++)
631     {
632       const struct val_labs *val_labs = var_get_value_labels (vars[i]);
633       size_t n_labels = val_labs_count (val_labs);
634       if (!n_labels)
635         continue;
636
637       struct pivot_category *group = pivot_category_create_group__ (
638         values->root, pivot_value_new_variable (vars[i]));
639
640       const struct val_lab **labels = val_labs_sorted (val_labs);
641       for (size_t j = 0; j < n_labels; j++)
642         {
643           const struct val_lab *vl = labels[j];
644
645           struct pivot_value *value = pivot_value_new_var_value (
646             vars[i], &vl->value);
647           if (value->type == PIVOT_VALUE_NUMERIC)
648             value->numeric.show = SETTINGS_VALUE_SHOW_VALUE;
649           else
650             value->string.show = SETTINGS_VALUE_SHOW_VALUE;
651           if (var_is_value_missing (vars[i], &vl->value) == MV_USER)
652             pivot_value_add_footnote (value, missing_footnote);
653           int row = pivot_category_create_leaf (group, value);
654
655           struct pivot_value *label = pivot_value_new_var_value (
656             vars[i], &vl->value);
657           char *escaped_label = xstrdup (val_lab_get_escaped_label (vl));
658           if (label->type == PIVOT_VALUE_NUMERIC)
659             {
660               free (label->numeric.value_label);
661               label->numeric.value_label = escaped_label;
662               label->numeric.show = SETTINGS_VALUE_SHOW_LABEL;
663             }
664           else
665             {
666               free (label->string.value_label);
667               label->string.value_label = escaped_label;
668               label->string.show = SETTINGS_VALUE_SHOW_LABEL;
669             }
670           pivot_table_put2 (table, 0, row, label);
671         }
672       free (labels);
673     }
674   pivot_table_submit (table);
675 }
676 \f
677 static bool
678 is_at_name (const char *name)
679 {
680   return name[0] == '@' || (name[0] == '$' && name[1] == '@');
681 }
682
683 static size_t
684 count_attributes (const struct attrset *set, int flags)
685 {
686   struct attrset_iterator i;
687   struct attribute *attr;
688   size_t n_attrs;
689
690   n_attrs = 0;
691   for (attr = attrset_first (set, &i); attr != NULL;
692        attr = attrset_next (set, &i))
693     if (flags & DF_AT_ATTRIBUTES || !is_at_name (attribute_get_name (attr)))
694       n_attrs += attribute_get_n_values (attr);
695   return n_attrs;
696 }
697
698 static void
699 display_attrset (struct pivot_table *table, struct pivot_value *set_name,
700                  const struct attrset *set, int flags)
701 {
702   size_t n_total = count_attributes (set, flags);
703   if (!n_total)
704     {
705       pivot_value_destroy (set_name);
706       return;
707     }
708
709   struct pivot_category *group = pivot_category_create_group__ (
710     table->dimensions[1]->root, set_name);
711
712   size_t n_attrs = attrset_count (set);
713   struct attribute **attrs = attrset_sorted (set);
714   for (size_t i = 0; i < n_attrs; i++)
715     {
716       const struct attribute *attr = attrs[i];
717       const char *name = attribute_get_name (attr);
718
719       if (!(flags & DF_AT_ATTRIBUTES) && is_at_name (name))
720         continue;
721
722       size_t n_values = attribute_get_n_values (attr);
723       for (size_t j = 0; j < n_values; j++)
724         {
725           int row = pivot_category_create_leaf (
726             group,
727             (n_values > 1
728              ? pivot_value_new_user_text_nocopy (xasprintf (
729                                                    "%s[%zu]", name, j + 1))
730              : pivot_value_new_user_text (name, -1)));
731           pivot_table_put2 (table, 0, row,
732                             pivot_value_new_user_text (
733                               attribute_get_value (attr, j), -1));
734         }
735     }
736   free (attrs);
737 }
738
739 static void
740 display_attributes (const struct attrset *dict_attrset,
741                     const struct variable **vars, size_t n_vars, int flags)
742 {
743   struct pivot_table *table = pivot_table_create (
744     N_("Variable and Dataset Attributes"));
745
746   pivot_dimension_create (table, PIVOT_AXIS_COLUMN,
747                           N_("Value"), N_("Value"));
748
749   struct pivot_dimension *variables = pivot_dimension_create (
750     table, PIVOT_AXIS_ROW, N_("Variable and Name"));
751   variables->root->show_label = true;
752
753   display_attrset (table, pivot_value_new_text (N_("(dataset)")),
754                    dict_attrset, flags);
755   for (size_t i = 0; i < n_vars; i++)
756     display_attrset (table, pivot_value_new_variable (vars[i]),
757                      var_get_attributes (vars[i]), flags);
758
759   if (pivot_table_is_empty (table))
760     pivot_table_unref (table);
761   else
762     pivot_table_submit (table);
763 }
764
765 /* Display a list of vectors.  If SORTED is nonzero then they are
766    sorted alphabetically. */
767 static void
768 display_vectors (const struct dictionary *dict, int sorted)
769 {
770   size_t n_vectors = dict_get_n_vectors (dict);
771   if (n_vectors == 0)
772     {
773       msg (SN, _("No vectors defined."));
774       return;
775     }
776
777   const struct vector **vectors = xnmalloc (n_vectors, sizeof *vectors);
778   for (size_t i = 0; i < n_vectors; i++)
779     vectors[i] = dict_get_vector (dict, i);
780   if (sorted)
781     qsort (vectors, n_vectors, sizeof *vectors, compare_vector_ptrs_by_name);
782
783   struct pivot_table *table = pivot_table_create (N_("Vectors"));
784   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Attributes"),
785                           N_("Variable"), N_("Print Format"));
786   struct pivot_dimension *vector_dim = pivot_dimension_create (
787     table, PIVOT_AXIS_ROW, N_("Vector and Position"));
788   vector_dim->root->show_label = true;
789
790   for (size_t i = 0; i < n_vectors; i++)
791     {
792       const struct vector *vec = vectors[i];
793
794       struct pivot_category *group = pivot_category_create_group__ (
795         vector_dim->root, pivot_value_new_user_text (
796           vector_get_name (vectors[i]), -1));
797
798       for (size_t j = 0; j < vector_get_n_vars (vec); j++)
799         {
800           struct variable *var = vector_get_var (vec, j);
801
802           int row = pivot_category_create_leaf (
803             group, pivot_value_new_integer (j + 1));
804
805           pivot_table_put2 (table, 0, row, pivot_value_new_variable (var));
806           char fmt_string[FMT_STRING_LEN_MAX + 1];
807           fmt_to_string (var_get_print_format (var), fmt_string);
808           pivot_table_put2 (table, 1, row,
809                             pivot_value_new_user_text (fmt_string, -1));
810         }
811     }
812
813   pivot_table_submit (table);
814
815   free (vectors);
816 }
817 \f
818 /* Encoding analysis. */
819
820 static const char *encoding_names[] = {
821   /* These encodings are from http://encoding.spec.whatwg.org/, as retrieved
822      February 2014.  Encodings not supported by glibc and encodings relevant
823      only to HTML have been removed. */
824   "utf-8",
825   "windows-1252",
826   "iso-8859-2",
827   "iso-8859-3",
828   "iso-8859-4",
829   "iso-8859-5",
830   "iso-8859-6",
831   "iso-8859-7",
832   "iso-8859-8",
833   "iso-8859-10",
834   "iso-8859-13",
835   "iso-8859-14",
836   "iso-8859-16",
837   "macintosh",
838   "windows-874",
839   "windows-1250",
840   "windows-1251",
841   "windows-1253",
842   "windows-1254",
843   "windows-1255",
844   "windows-1256",
845   "windows-1257",
846   "windows-1258",
847   "koi8-r",
848   "koi8-u",
849   "ibm866",
850   "gb18030",
851   "big5",
852   "euc-jp",
853   "iso-2022-jp",
854   "shift_jis",
855   "euc-kr",
856
857   /* Added by user request. */
858   "ibm850",
859   "din_66003",
860 };
861 #define N_ENCODING_NAMES (sizeof encoding_names / sizeof *encoding_names)
862
863 struct encoding
864   {
865     uint64_t encodings;
866     char **utf8_strings;
867     unsigned int hash;
868   };
869
870 static char **
871 recode_strings (struct pool *pool,
872                 char **strings, bool *ids, size_t n,
873                 const char *encoding)
874 {
875   char **utf8_strings;
876   size_t i;
877
878   utf8_strings = pool_alloc (pool, n * sizeof *utf8_strings);
879   for (i = 0; i < n; i++)
880     {
881       struct substring utf8;
882       int error;
883
884       error = recode_pedantically ("UTF-8", encoding, ss_cstr (strings[i]),
885                                    pool, &utf8);
886       if (!error)
887         {
888           ss_rtrim (&utf8, ss_cstr (" "));
889           utf8.string[utf8.length] = '\0';
890
891           if (ids[i] && !id_is_plausible (utf8.string))
892             error = EINVAL;
893         }
894
895       if (error)
896         return NULL;
897
898       utf8_strings[i] = utf8.string;
899     }
900
901   return utf8_strings;
902 }
903
904 static struct encoding *
905 find_duplicate_encoding (struct encoding *encodings, size_t n_encodings,
906                          char **utf8_strings, size_t n_strings,
907                          unsigned int hash)
908 {
909   struct encoding *e;
910
911   for (e = encodings; e < &encodings[n_encodings]; e++)
912     {
913       int i;
914
915       if (e->hash != hash)
916         goto next_encoding;
917
918       for (i = 0; i < n_strings; i++)
919         if (strcmp (utf8_strings[i], e->utf8_strings[i]))
920           goto next_encoding;
921
922       return e;
923     next_encoding:;
924     }
925
926   return NULL;
927 }
928
929 static bool
930 all_equal (const struct encoding *encodings, size_t n_encodings,
931            size_t string_idx)
932 {
933   const char *s0;
934   size_t i;
935
936   s0 = encodings[0].utf8_strings[string_idx];
937   for (i = 1; i < n_encodings; i++)
938     if (strcmp (s0, encodings[i].utf8_strings[string_idx]))
939       return false;
940
941   return true;
942 }
943
944 static int
945 equal_prefix (const struct encoding *encodings, size_t n_encodings,
946               size_t string_idx)
947 {
948   const char *s0;
949   size_t prefix;
950   size_t i;
951
952   s0 = encodings[0].utf8_strings[string_idx];
953   prefix = strlen (s0);
954   for (i = 1; i < n_encodings; i++)
955     {
956       const char *si = encodings[i].utf8_strings[string_idx];
957       size_t j;
958
959       for (j = 0; j < prefix; j++)
960         if (s0[j] != si[j])
961           {
962             prefix = j;
963             if (!prefix)
964               return 0;
965             break;
966           }
967     }
968
969   while (prefix > 0 && s0[prefix - 1] != ' ')
970     prefix--;
971   return prefix;
972 }
973
974 static int
975 equal_suffix (const struct encoding *encodings, size_t n_encodings,
976               size_t string_idx)
977 {
978   const char *s0;
979   size_t s0_len;
980   size_t suffix;
981   size_t i;
982
983   s0 = encodings[0].utf8_strings[string_idx];
984   s0_len = strlen (s0);
985   suffix = s0_len;
986   for (i = 1; i < n_encodings; i++)
987     {
988       const char *si = encodings[i].utf8_strings[string_idx];
989       size_t si_len = strlen (si);
990       size_t j;
991
992       if (si_len < suffix)
993         suffix = si_len;
994       for (j = 0; j < suffix; j++)
995         if (s0[s0_len - j - 1] != si[si_len - j - 1])
996           {
997             suffix = j;
998             if (!suffix)
999               return 0;
1000             break;
1001           }
1002     }
1003
1004   while (suffix > 0 && s0[s0_len - suffix] != ' ')
1005     suffix--;
1006   return suffix;
1007 }
1008
1009 static void
1010 report_encodings (const struct file_handle *h, struct pool *pool,
1011                   char **titles, bool *ids, char **strings, size_t n_strings)
1012 {
1013   struct encoding encodings[N_ENCODING_NAMES];
1014   size_t n_encodings, n_unique_strings;
1015
1016   n_encodings = 0;
1017   for (size_t i = 0; i < N_ENCODING_NAMES; i++)
1018     {
1019       char **utf8_strings;
1020       struct encoding *e;
1021       unsigned int hash;
1022
1023       utf8_strings = recode_strings (pool, strings, ids, n_strings,
1024                                      encoding_names[i]);
1025       if (!utf8_strings)
1026         continue;
1027
1028       /* Hash utf8_strings. */
1029       hash = 0;
1030       for (size_t j = 0; j < n_strings; j++)
1031         hash = hash_string (utf8_strings[j], hash);
1032
1033       /* If there's a duplicate encoding, just mark it. */
1034       e = find_duplicate_encoding (encodings, n_encodings,
1035                                    utf8_strings, n_strings, hash);
1036       if (e)
1037         {
1038           e->encodings |= UINT64_C (1) << i;
1039           continue;
1040         }
1041
1042       e = &encodings[n_encodings++];
1043       e->encodings = UINT64_C (1) << i;
1044       e->utf8_strings = utf8_strings;
1045       e->hash = hash;
1046     }
1047   if (!n_encodings)
1048     {
1049       msg (SW, _("No valid encodings found."));
1050       return;
1051     }
1052
1053   /* Table of valid encodings. */
1054   struct pivot_table *table = pivot_table_create__ (
1055     pivot_value_new_text_format (N_("Usable encodings for %s."),
1056                                  fh_get_name (h)), "Usable Encodings");
1057   pivot_table_set_caption (
1058     table, pivot_value_new_text_format (
1059       N_("Encodings that can successfully read %s (by specifying the encoding "
1060          "name on the GET command's ENCODING subcommand).  Encodings that "
1061          "yield identical text are listed together."),
1062       fh_get_name (h)));
1063
1064   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Encodings"),
1065                           N_("Encodings"));
1066   struct pivot_dimension *number = pivot_dimension_create__ (
1067     table, PIVOT_AXIS_ROW, pivot_value_new_user_text ("#", -1));
1068   number->root->show_label = true;
1069
1070   for (size_t i = 0; i < n_encodings; i++)
1071     {
1072       struct string s = DS_EMPTY_INITIALIZER;
1073       for (size_t j = 0; j < 64; j++)
1074         if (encodings[i].encodings & (UINT64_C (1) << j))
1075           ds_put_format (&s, "%s, ", encoding_names[j]);
1076       ds_chomp (&s, ss_cstr (", "));
1077
1078       int row = pivot_category_create_leaf (number->root,
1079                                             pivot_value_new_integer (i + 1));
1080       pivot_table_put2 (
1081         table, 0, row, pivot_value_new_user_text_nocopy (ds_steal_cstr (&s)));
1082     }
1083   pivot_table_submit (table);
1084
1085   n_unique_strings = 0;
1086   for (size_t i = 0; i < n_strings; i++)
1087     if (!all_equal (encodings, n_encodings, i))
1088       n_unique_strings++;
1089   if (!n_unique_strings)
1090     return;
1091
1092   /* Table of alternative interpretations. */
1093   table = pivot_table_create__ (
1094     pivot_value_new_text_format (N_("%s Encoded Text Strings"),
1095                                  fh_get_name (h)),
1096     "Alternate Encoded Text Strings");
1097   pivot_table_set_caption (
1098     table, pivot_value_new_text (
1099       N_("Text strings in the file dictionary that the previously listed "
1100          "encodings interpret differently, along with the interpretations.")));
1101
1102   pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Text"), N_("Text"));
1103
1104   number = pivot_dimension_create__ (table, PIVOT_AXIS_ROW,
1105                                      pivot_value_new_user_text ("#", -1));
1106   number->root->show_label = true;
1107   for (size_t i = 0; i < n_encodings; i++)
1108     pivot_category_create_leaf (number->root,
1109                                 pivot_value_new_integer (i + 1));
1110
1111   struct pivot_dimension *purpose = pivot_dimension_create (
1112     table, PIVOT_AXIS_ROW, N_("Purpose"));
1113   purpose->root->show_label = true;
1114
1115   for (size_t i = 0; i < n_strings; i++)
1116     if (!all_equal (encodings, n_encodings, i))
1117       {
1118         int prefix = equal_prefix (encodings, n_encodings, i);
1119         int suffix = equal_suffix (encodings, n_encodings, i);
1120
1121         int purpose_idx = pivot_category_create_leaf (
1122           purpose->root, pivot_value_new_user_text (titles[i], -1));
1123
1124         for (size_t j = 0; j < n_encodings; j++)
1125           {
1126             const char *s = encodings[j].utf8_strings[i] + prefix;
1127
1128             if (prefix || suffix)
1129               {
1130                 size_t len = strlen (s) - suffix;
1131                 struct string entry;
1132
1133                 ds_init_empty (&entry);
1134                 if (prefix)
1135                   ds_put_cstr (&entry, "...");
1136                 ds_put_substring (&entry, ss_buffer (s, len));
1137                 if (suffix)
1138                   ds_put_cstr (&entry, "...");
1139
1140                 pivot_table_put3 (table, 0, j, purpose_idx,
1141                                   pivot_value_new_user_text_nocopy (
1142                                     ds_steal_cstr (&entry)));
1143               }
1144             else
1145               pivot_table_put3 (table, 0, j, purpose_idx,
1146                                 pivot_value_new_user_text (s, -1));
1147           }
1148       }
1149
1150   pivot_table_submit (table);
1151 }