output: Add support for captions.
[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 */, NULL));
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 */, NULL));
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                                           NULL));
521 }
522
523 static struct table *
524 describe_value_labels (const struct variable *var)
525 {
526   const struct val_labs *val_labs = var_get_value_labels (var);
527   size_t n_labels = val_labs_count (val_labs);
528   const struct val_lab **labels;
529   struct tab_table *t;
530   size_t i;
531
532   t = tab_create (2, n_labels + 1);
533   tab_box (t, TAL_1, TAL_1, -1, TAL_1, 0, 0, tab_nc (t) - 1, tab_nr (t) - 1);
534
535   tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Value"));
536   tab_text (t, 1, 0, TAB_LEFT | TAT_TITLE, _("Label"));
537
538   tab_hline (t, TAL_1, 0, 1, 1);
539   tab_vline (t, TAL_1, 1, 0, n_labels);
540
541   labels = val_labs_sorted (val_labs);
542   for (i = 0; i < n_labels; i++)
543     {
544       const struct val_lab *vl = labels[i];
545
546       tab_value (t, 0, i + 1, TAB_NONE, &vl->value, var, NULL);
547       tab_text (t, 1, i + 1, TAB_LEFT, val_lab_get_escaped_label (vl));
548     }
549   free (labels);
550
551   return &t->table;
552 }
553
554 /* Puts a description of variable V into table T starting at row
555    R.  The variable will be described in the format given by
556    FLAGS.  Returns the next row available for use in the
557    table. */
558 static struct table *
559 describe_variable (const struct variable *v, int flags)
560 {
561   struct table *table;
562   struct string s;
563
564   ds_init_empty (&s);
565
566   /* Variable label. */
567   if (flags & DF_VARIABLE_LABELS && var_has_label (v))
568     {
569       if (flags & ~(DF_DICT_INDEX | DF_VARIABLE_LABELS))
570         ds_put_format (&s, _("Label: %s\n"), var_get_label (v));
571       else
572         ds_put_format (&s, "%s\n", var_get_label (v));
573     }
574
575   /* Print/write format, or print and write formats. */
576   if (flags & DF_FORMATS) 
577     {
578       const struct fmt_spec *print = var_get_print_format (v);
579       const struct fmt_spec *write = var_get_write_format (v);
580       char str[FMT_STRING_LEN_MAX + 1];
581
582       if (fmt_equal (print, write))
583         ds_put_format (&s, _("Format: %s\n"), fmt_to_string (print, str));
584       else
585         {
586           ds_put_format (&s, _("Print Format: %s\n"),
587                          fmt_to_string (print, str));
588           ds_put_format (&s, _("Write Format: %s\n"),
589                          fmt_to_string (write, str));
590         }
591     }
592
593   /* Measurement level, role, display width, alignment. */
594   if (flags & DF_MEASURE)
595     ds_put_format (&s, _("Measure: %s\n"),
596                    measure_to_string (var_get_measure (v)));
597
598   if (flags & DF_ROLE)
599     ds_put_format (&s, _("Role: %s\n"), var_role_to_string (var_get_role (v)));
600
601
602   if (flags & DF_ALIGNMENT)
603     ds_put_format (&s, _("Display Alignment: %s\n"),
604                    alignment_to_string (var_get_alignment (v)));
605
606   if (flags & DF_WIDTH)
607     ds_put_format (&s, _("Display Width: %d\n"), var_get_display_width (v));
608
609   /* Missing values if any. */
610   if (flags & DF_MISSING_VALUES && var_has_missing_values (v))
611     {
612       const struct missing_values *mv = var_get_missing_values (v);
613       int cnt = 0;
614       int i;
615
616       ds_put_cstr (&s, _("Missing Values: "));
617
618       if (mv_has_range (mv))
619         {
620           double x, y;
621           mv_get_range (mv, &x, &y);
622           if (x == LOWEST)
623             ds_put_format (&s, "LOWEST THRU %.*g", DBL_DIG + 1, y);
624           else if (y == HIGHEST)
625             ds_put_format (&s, "%.*g THRU HIGHEST", DBL_DIG + 1, x);
626           else
627             ds_put_format (&s, "%.*g THRU %.*g",
628                            DBL_DIG + 1, x,
629                            DBL_DIG + 1, y);
630           cnt++;
631         }
632       for (i = 0; i < mv_n_values (mv); i++)
633         {
634           const union value *value = mv_get_value (mv, i);
635           if (cnt++ > 0)
636             ds_put_cstr (&s, "; ");
637           if (var_is_numeric (v))
638             ds_put_format (&s, "%.*g", DBL_DIG + 1, value->f);
639           else
640             {
641               int width = var_get_width (v);
642               int mv_width = MIN (width, MV_MAX_STRING);
643
644               ds_put_byte (&s, '"');
645               memcpy (ds_put_uninit (&s, mv_width),
646                       value_str (value, width), mv_width);
647               ds_put_byte (&s, '"');
648             }
649         }
650       ds_put_byte (&s, '\n');
651     }
652
653   ds_chomp_byte (&s, '\n');
654
655   table = (ds_is_empty (&s)
656            ? NULL
657            : table_from_string (TAB_LEFT, ds_cstr (&s)));
658   ds_destroy (&s);
659
660   /* Value labels. */
661   if (flags & DF_VALUE_LABELS && var_has_value_labels (v))
662     table = table_vpaste (table, table_create_nested (describe_value_labels (v)));
663
664   if (flags & (DF_ATTRIBUTES | DF_AT_ATTRIBUTES))
665     {
666       struct attrset *attrs = var_get_attributes (v);
667
668       if (count_attributes (attrs, flags))
669         table = table_vpaste (
670           table, table_create_nested (describe_attributes (attrs, flags)));
671     }
672
673   if (table == NULL)
674     table = table_from_string (TAB_LEFT, "");
675
676   table = table_hpaste (table_from_string (0, var_get_name (v)),
677                         table_stomp (table));
678   if (flags & DF_DICT_INDEX)
679     {
680       char s[INT_BUFSIZE_BOUND (size_t)];
681
682       sprintf (s, "%zu", var_get_dict_index (v) + 1);
683       table = table_hpaste (table, table_from_string (0, s));
684     }
685
686   return table;
687 }
688
689 /* Display a list of vectors.  If SORTED is nonzero then they are
690    sorted alphabetically. */
691 static void
692 display_vectors (const struct dictionary *dict, int sorted)
693 {
694   const struct vector **vl;
695   int i;
696   struct tab_table *t;
697   size_t nvec;
698   size_t nrow;
699   size_t row;
700
701   nvec = dict_get_vector_cnt (dict);
702   if (nvec == 0)
703     {
704       msg (SW, _("No vectors defined."));
705       return;
706     }
707
708   vl = xnmalloc (nvec, sizeof *vl);
709   nrow = 0;
710   for (i = 0; i < nvec; i++)
711     {
712       vl[i] = dict_get_vector (dict, i);
713       nrow += vector_get_var_cnt (vl[i]);
714     }
715   if (sorted)
716     qsort (vl, nvec, sizeof *vl, compare_vector_ptrs_by_name);
717
718   t = tab_create (4, nrow + 1);
719   tab_headers (t, 0, 0, 1, 0);
720   tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, nrow);
721   tab_box (t, -1, -1, -1, TAL_1, 0, 0, 3, nrow);
722   tab_hline (t, TAL_2, 0, 3, 1);
723   tab_text (t, 0, 0, TAT_TITLE | TAB_LEFT, _("Vector"));
724   tab_text (t, 1, 0, TAT_TITLE | TAB_LEFT, _("Position"));
725   tab_text (t, 2, 0, TAT_TITLE | TAB_LEFT, _("Variable"));
726   tab_text (t, 3, 0, TAT_TITLE | TAB_LEFT, _("Print Format"));
727
728   row = 1;
729   for (i = 0; i < nvec; i++)
730     {
731       const struct vector *vec = vl[i];
732       size_t j;
733
734       tab_joint_text (t, 0, row, 0, row + vector_get_var_cnt (vec) - 1,
735                       TAB_LEFT, vector_get_name (vl[i]));
736
737       for (j = 0; j < vector_get_var_cnt (vec); j++)
738         {
739           struct variable *var = vector_get_var (vec, j);
740           char fmt_string[FMT_STRING_LEN_MAX + 1];
741           fmt_to_string (var_get_print_format (var), fmt_string);
742
743           tab_text_format (t, 1, row, TAB_RIGHT, "%zu", j + 1);
744           tab_text (t, 2, row, TAB_LEFT, var_get_name (var));
745           tab_text (t, 3, row, TAB_LEFT, fmt_string);
746           row++;
747         }
748       tab_hline (t, TAL_1, 0, 3, row);
749     }
750
751   tab_submit (t);
752
753   free (vl);
754 }
755 \f
756 /* Encoding analysis. */
757
758 static const char *encoding_names[] = {
759   /* These encodings are from http://encoding.spec.whatwg.org/, as retrieved
760      February 2014.  Encodings not supported by glibc and encodings relevant
761      only to HTML have been removed. */
762   "utf-8",
763   "windows-1252",
764   "iso-8859-2",
765   "iso-8859-3",
766   "iso-8859-4",
767   "iso-8859-5",
768   "iso-8859-6",
769   "iso-8859-7",
770   "iso-8859-8",
771   "iso-8859-10",
772   "iso-8859-13",
773   "iso-8859-14",
774   "iso-8859-16",
775   "macintosh",
776   "windows-874",
777   "windows-1250",
778   "windows-1251",
779   "windows-1253",
780   "windows-1254",
781   "windows-1255",
782   "windows-1256",
783   "windows-1257",
784   "windows-1258",
785   "koi8-r",
786   "koi8-u",
787   "ibm866",
788   "gb18030",
789   "big5",
790   "euc-jp",
791   "iso-2022-jp",
792   "shift_jis",
793   "euc-kr",
794
795   /* Added by user request. */
796   "ibm850",
797   "din_66003",
798 };
799 #define N_ENCODING_NAMES (sizeof encoding_names / sizeof *encoding_names)
800
801 struct encoding
802   {
803     uint64_t encodings;
804     char **utf8_strings;
805     unsigned int hash;
806   };
807
808 static char **
809 recode_strings (struct pool *pool,
810                 char **strings, bool *ids, size_t n,
811                 const char *encoding)
812 {
813   char **utf8_strings;
814   size_t i;
815
816   utf8_strings = pool_alloc (pool, n * sizeof *utf8_strings);
817   for (i = 0; i < n; i++)
818     {
819       struct substring utf8;
820       int error;
821
822       error = recode_pedantically ("UTF-8", encoding, ss_cstr (strings[i]),
823                                    pool, &utf8);
824       if (!error)
825         {
826           ss_rtrim (&utf8, ss_cstr (" "));
827           utf8.string[utf8.length] = '\0';
828
829           if (ids[i] && !id_is_plausible (utf8.string, false))
830             error = EINVAL;
831         }
832
833       if (error)
834         return NULL;
835
836       utf8_strings[i] = utf8.string;
837     }
838
839   return utf8_strings;
840 }
841
842 static struct encoding *
843 find_duplicate_encoding (struct encoding *encodings, size_t n_encodings,
844                          char **utf8_strings, size_t n_strings,
845                          unsigned int hash)
846 {
847   struct encoding *e;
848
849   for (e = encodings; e < &encodings[n_encodings]; e++)
850     {
851       int i;
852
853       if (e->hash != hash)
854         goto next_encoding;
855
856       for (i = 0; i < n_strings; i++)
857         if (strcmp (utf8_strings[i], e->utf8_strings[i]))
858           goto next_encoding;
859
860       return e;
861     next_encoding:;
862     }
863
864   return NULL;
865 }
866
867 static bool
868 all_equal (const struct encoding *encodings, size_t n_encodings,
869            size_t string_idx)
870 {
871   const char *s0;
872   size_t i;
873
874   s0 = encodings[0].utf8_strings[string_idx];
875   for (i = 1; i < n_encodings; i++)
876     if (strcmp (s0, encodings[i].utf8_strings[string_idx]))
877       return false;
878
879   return true;
880 }
881
882 static int
883 equal_prefix (const struct encoding *encodings, size_t n_encodings,
884               size_t string_idx)
885 {
886   const char *s0;
887   size_t prefix;
888   size_t i;
889
890   s0 = encodings[0].utf8_strings[string_idx];
891   prefix = strlen (s0);
892   for (i = 1; i < n_encodings; i++)
893     {
894       const char *si = encodings[i].utf8_strings[string_idx];
895       size_t j;
896
897       for (j = 0; j < prefix; j++)
898         if (s0[j] != si[j])
899           {
900             prefix = j;
901             if (!prefix)
902               return 0;
903             break;
904           }
905     }
906
907   while (prefix > 0 && s0[prefix - 1] != ' ')
908     prefix--;
909   return prefix;
910 }
911
912 static int
913 equal_suffix (const struct encoding *encodings, size_t n_encodings,
914               size_t string_idx)
915 {
916   const char *s0;
917   size_t s0_len;
918   size_t suffix;
919   size_t i;
920
921   s0 = encodings[0].utf8_strings[string_idx];
922   s0_len = strlen (s0);
923   suffix = s0_len;
924   for (i = 1; i < n_encodings; i++)
925     {
926       const char *si = encodings[i].utf8_strings[string_idx];
927       size_t si_len = strlen (si);
928       size_t j;
929
930       if (si_len < suffix)
931         suffix = si_len;
932       for (j = 0; j < suffix; j++)
933         if (s0[s0_len - j - 1] != si[si_len - j - 1])
934           {
935             suffix = j;
936             if (!suffix)
937               return 0;
938             break;
939           }
940     }
941
942   while (suffix > 0 && s0[s0_len - suffix] != ' ')
943     suffix--;
944   return suffix;
945 }
946
947 static void
948 report_encodings (const struct file_handle *h, const struct sfm_reader *r)
949 {
950   char **titles;
951   char **strings;
952   bool *ids;
953   struct encoding encodings[N_ENCODING_NAMES];
954   size_t n_encodings, n_strings, n_unique_strings;
955   size_t i, j;
956   struct tab_table *t;
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   t = tab_create (2, n_encodings + 1);
1002   tab_title (t, _("Usable encodings for %s."), fh_get_name (h));
1003   tab_caption (t, _("Encodings that can successfully read %s (by specifying "
1004                     "the encoding name on the GET command's ENCODING "
1005                     "subcommand).  Encodings that yield identical text are "
1006                     "listed together."), fh_get_name (h));
1007   tab_headers (t, 1, 0, 1, 0);
1008   tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 1, n_encodings);
1009   tab_hline (t, TAL_1, 0, 1, 1);
1010   tab_text (t, 0, 0, TAB_RIGHT, "#");
1011   tab_text (t, 1, 0, TAB_LEFT, _("Encodings"));
1012   for (i = 0; i < n_encodings; i++)
1013     {
1014       struct string s;
1015
1016       ds_init_empty (&s);
1017       for (j = 0; j < 64; j++)
1018         if (encodings[i].encodings & (UINT64_C (1) << j))
1019           ds_put_format (&s, "%s, ", encoding_names[j]);
1020       ds_chomp (&s, ss_cstr (", "));
1021
1022       tab_text_format (t, 0, i + 1, TAB_RIGHT, "%zu", i + 1);
1023       tab_text (t, 1, i + 1, TAB_LEFT, ds_cstr (&s));
1024       ds_destroy (&s);
1025     }
1026   tab_submit (t);
1027
1028   n_unique_strings = 0;
1029   for (i = 0; i < n_strings; i++)
1030     if (!all_equal (encodings, n_encodings, i))
1031       n_unique_strings++;
1032   if (!n_unique_strings)
1033     {
1034       pool_destroy (pool);
1035       return;
1036     }
1037
1038   t = tab_create (3, (n_encodings * n_unique_strings) + 1);
1039   tab_title (t, _("%s encoded text strings."), fh_get_name (h));
1040   tab_caption (t, _("Text strings in the file dictionary that the previously "
1041                     "listed encodings interpret differently, along with the "
1042                     "interpretations."));
1043   tab_headers (t, 1, 0, 1, 0);
1044   tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 2, n_encodings * n_unique_strings);
1045   tab_hline (t, TAL_1, 0, 2, 1);
1046
1047   tab_text (t, 0, 0, TAB_LEFT, _("Purpose"));
1048   tab_text (t, 1, 0, TAB_RIGHT, "#");
1049   tab_text (t, 2, 0, TAB_LEFT, _("Text"));
1050
1051   row = 1;
1052   for (i = 0; i < n_strings; i++)
1053     if (!all_equal (encodings, n_encodings, i))
1054       {
1055         int prefix = equal_prefix (encodings, n_encodings, i);
1056         int suffix = equal_suffix (encodings, n_encodings, i);
1057
1058         tab_joint_text (t, 0, row, 0, row + n_encodings - 1,
1059                         TAB_LEFT, titles[i]);
1060         tab_hline (t, TAL_1, 0, 2, row);
1061         for (j = 0; j < n_encodings; j++)
1062           {
1063             const char *s = encodings[j].utf8_strings[i] + prefix;
1064
1065             tab_text_format (t, 1, row, TAB_RIGHT, "%zu", j + 1);
1066             if (prefix || suffix)
1067               {
1068                 size_t len = strlen (s) - suffix;
1069                 struct string entry;
1070
1071                 ds_init_empty (&entry);
1072                 if (prefix)
1073                   ds_put_cstr (&entry, "...");
1074                 ds_put_substring (&entry, ss_buffer (s, len));
1075                 if (suffix)
1076                   ds_put_cstr (&entry, "...");
1077                 tab_text (t, 2, row, TAB_LEFT, ds_cstr (&entry));
1078               }
1079             else
1080               tab_text (t, 2, row, TAB_LEFT, s);
1081             row++;
1082           }
1083       }
1084   tab_submit (t);
1085
1086   pool_destroy (pool);
1087 }
1088
1089 static unsigned int
1090 dict_display_mask (const struct dictionary *d)
1091 {
1092   size_t n_vars = dict_get_var_cnt (d);
1093   unsigned int mask;
1094   size_t i;
1095
1096   mask = DF_ALL & ~(DF_MEASURE | DF_ROLE | DF_ALIGNMENT | DF_WIDTH);
1097   for (i = 0; i < n_vars; i++)
1098     {
1099       const struct variable *v = dict_get_var (d, i);
1100       enum val_type val = var_get_type (v);
1101       int width = var_get_width (v);
1102
1103       if (var_get_measure (v) != var_default_measure (val))
1104         mask |= DF_MEASURE;
1105
1106       if (var_get_role (v) != ROLE_INPUT)
1107         mask |= DF_ROLE;
1108
1109       if (var_get_alignment (v) != var_default_alignment (val))
1110         mask |= DF_ALIGNMENT;
1111
1112       if (var_get_display_width (v) != var_default_display_width (width))
1113         mask |= DF_WIDTH;
1114     }
1115
1116   return mask;
1117 }