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