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