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