SYSFILE INFO: Add ENCODING subcommand.
[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 <float.h>
21 #include <stdlib.h>
22
23 #include "data/attributes.h"
24 #include "data/casereader.h"
25 #include "data/dataset.h"
26 #include "data/dictionary.h"
27 #include "data/file-handle-def.h"
28 #include "data/format.h"
29 #include "data/missing-values.h"
30 #include "data/sys-file-reader.h"
31 #include "data/value-labels.h"
32 #include "data/variable.h"
33 #include "data/vector.h"
34 #include "language/command.h"
35 #include "language/data-io/file-handle.h"
36 #include "language/lexer/lexer.h"
37 #include "language/lexer/variable-parser.h"
38 #include "libpspp/array.h"
39 #include "libpspp/message.h"
40 #include "libpspp/misc.h"
41 #include "libpspp/string-array.h"
42 #include "output/tab.h"
43
44 #include "gl/minmax.h"
45 #include "gl/xalloc.h"
46
47 #include "gettext.h"
48 #define _(msgid) gettext (msgid)
49
50 /* Information to include in displaying a dictionary. */
51 enum 
52   {
53     DF_DICT_INDEX       = 1 << 0,
54     DF_FORMATS          = 1 << 1,
55     DF_VALUE_LABELS     = 1 << 2,
56     DF_VARIABLE_LABELS  = 1 << 3,
57     DF_MISSING_VALUES   = 1 << 4,
58     DF_AT_ATTRIBUTES    = 1 << 5, /* Attributes whose names begin with @. */
59     DF_ATTRIBUTES       = 1 << 6, /* All other attributes. */
60     DF_MISC             = 1 << 7,
61     DF_ALL              = (1 << 8) - 1
62   };
63
64 static int describe_variable (const struct variable *v, struct tab_table *t,
65                               int r, int pc, int flags);
66
67 /* SYSFILE INFO utility. */
68 int
69 cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED)
70 {
71   struct file_handle *h;
72   struct dictionary *d;
73   struct tab_table *t;
74   struct casereader *reader;
75   struct sfm_read_info info;
76   char *encoding;
77   int r, i;
78
79   h = NULL;
80   encoding = NULL;
81   for (;;)
82     {
83       lex_match (lexer, T_SLASH);
84
85       if (lex_match_id (lexer, "FILE") || lex_is_string (lexer))
86         {
87           lex_match (lexer, T_EQUALS);
88
89           fh_unref (h);
90           h = fh_parse (lexer, FH_REF_FILE, NULL);
91           if (h == NULL)
92             goto error;
93         }
94       else if (lex_match_id (lexer, "ENCODING"))
95         {
96           lex_match (lexer, T_EQUALS);
97
98           if (!lex_force_string (lexer))
99             goto error;
100
101           free (encoding);
102           encoding = ss_xstrdup (lex_tokss (lexer));
103
104           lex_get (lexer);
105         }
106       else
107         break;
108     }
109
110   if (h == NULL)
111     {
112       lex_sbc_missing ("FILE");
113       goto error;
114     }
115
116   reader = sfm_open_reader (h, encoding, &d, &info);
117   if (!reader)
118     goto error;
119
120   casereader_destroy (reader);
121
122   t = tab_create (2, 11 + (info.product_ext != NULL));
123   r = 0;
124   tab_vline (t, TAL_GAP, 1, 0, 8);
125
126   tab_text (t, 0, r, TAB_LEFT, _("File:"));
127   tab_text (t, 1, r++, TAB_LEFT, fh_get_file_name (h));
128
129   tab_text (t, 0, r, TAB_LEFT, _("Label:"));
130   {
131     const char *label = dict_get_label (d);
132     if (label == NULL)
133       label = _("No label.");
134     tab_text (t, 1, r++, TAB_LEFT, label);
135   }
136
137   tab_text (t, 0, r, TAB_LEFT, _("Created:"));
138   tab_text_format (t, 1, r++, TAB_LEFT, "%s %s by %s",
139                    info.creation_date, info.creation_time, info.product);
140
141   if (info.product_ext)
142     {
143       tab_text (t, 0, r, TAB_LEFT, _("Product:"));
144       tab_text (t, 1, r++, TAB_LEFT, info.product_ext);
145     }
146
147   tab_text (t, 0, r, TAB_LEFT, _("Integer Format:"));
148   tab_text (t, 1, r++, TAB_LEFT,
149             info.integer_format == INTEGER_MSB_FIRST ? _("Big Endian")
150             : info.integer_format == INTEGER_LSB_FIRST ? _("Little Endian")
151             : _("Unknown"));
152
153   tab_text (t, 0, r, TAB_LEFT, _("Real Format:"));
154   tab_text (t, 1, r++, TAB_LEFT,
155             info.float_format == FLOAT_IEEE_DOUBLE_LE ? _("IEEE 754 LE.")
156             : info.float_format == FLOAT_IEEE_DOUBLE_BE ? _("IEEE 754 BE.")
157             : info.float_format == FLOAT_VAX_D ? _("VAX D.")
158             : info.float_format == FLOAT_VAX_G ? _("VAX G.")
159             : info.float_format == FLOAT_Z_LONG ? _("IBM 390 Hex Long.")
160             : _("Unknown"));
161
162   tab_text (t, 0, r, TAB_LEFT, _("Variables:"));
163   tab_text_format (t, 1, r++, TAB_LEFT, "%zu", dict_get_var_cnt (d));
164
165   tab_text (t, 0, r, TAB_LEFT, _("Cases:"));
166   if (info.case_cnt == -1)
167     tab_text (t, 1, r, TAB_LEFT, _("Unknown"));
168   else
169     tab_text_format (t, 1, r, TAB_LEFT, "%ld", (long int) info.case_cnt);
170   r++;
171
172   tab_text (t, 0, r, TAB_LEFT, _("Type:"));
173   tab_text (t, 1, r++, TAB_LEFT, _("System File"));
174
175   tab_text (t, 0, r, TAB_LEFT, _("Weight:"));
176   {
177     struct variable *weight_var = dict_get_weight (d);
178     tab_text (t, 1, r++, TAB_LEFT,
179               (weight_var != NULL
180                ? var_get_name (weight_var) : _("Not weighted.")));
181   }
182
183   tab_text (t, 0, r, TAB_LEFT, _("Compression:"));
184   tab_text_format (t, 1, r++, TAB_LEFT,
185                    info.compression == SFM_COMP_NONE ? _("None")
186                    : info.compression == SFM_COMP_SIMPLE ? "SAV"
187                    : "ZSAV");
188
189   tab_text (t, 0, r, TAB_LEFT, _("Charset:"));
190   tab_text (t, 1, r++, TAB_LEFT, dict_get_encoding (d));
191
192   tab_submit (t);
193
194   t = tab_create (4, 1 + 2 * dict_get_var_cnt (d));
195   tab_headers (t, 0, 0, 1, 0);
196   tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
197   tab_joint_text (t, 1, 0, 2, 0, TAB_LEFT | TAT_TITLE, _("Description"));
198   tab_text (t, 3, 0, TAB_LEFT | TAT_TITLE, _("Position"));
199   tab_hline (t, TAL_2, 0, 3, 1);
200   for (r = 1, i = 0; i < dict_get_var_cnt (d); i++)
201     r = describe_variable (dict_get_var (d, i), t, r, 3,
202                            DF_ALL & ~DF_AT_ATTRIBUTES);
203
204   tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, r);
205   tab_vline (t, TAL_1, 1, 0, r);
206   tab_vline (t, TAL_1, 3, 0, r);
207
208   tab_resize (t, -1, r);
209   tab_submit (t);
210
211   dict_destroy (d);
212
213   fh_unref (h);
214   sfm_read_info_destroy (&info);
215   return CMD_SUCCESS;
216
217 error:
218   fh_unref (h);
219   free (encoding);
220   return CMD_FAILURE;
221 }
222 \f
223 /* DISPLAY utility. */
224
225 static void display_macros (void);
226 static void display_documents (const struct dictionary *dict);
227 static void display_variables (const struct variable **, size_t, int);
228 static void display_vectors (const struct dictionary *dict, int sorted);
229 static void display_data_file_attributes (struct attrset *, int flags);
230
231 int
232 cmd_display (struct lexer *lexer, struct dataset *ds)
233 {
234   /* Whether to sort the list of variables alphabetically. */
235   int sorted;
236
237   /* Variables to display. */
238   size_t n;
239   const struct variable **vl;
240
241   if (lex_match_id (lexer, "MACROS"))
242     display_macros ();
243   else if (lex_match_id (lexer, "DOCUMENTS"))
244     display_documents (dataset_dict (ds));
245   else if (lex_match_id (lexer, "FILE"))
246     {
247       if (!lex_force_match_id (lexer, "LABEL"))
248         return CMD_FAILURE;
249       if (dict_get_label (dataset_dict (ds)) == NULL)
250         tab_output_text (TAB_LEFT,
251                          _("The active dataset does not have a file label."));
252       else
253         tab_output_text_format (TAB_LEFT, _("File label: %s"),
254                                 dict_get_label (dataset_dict (ds)));
255     }
256   else
257     {
258       int flags;
259
260       sorted = lex_match_id (lexer, "SORTED");
261
262       if (lex_match_id (lexer, "VECTORS"))
263         {
264           display_vectors (dataset_dict(ds), sorted);
265           return CMD_SUCCESS;
266         }
267       else if (lex_match_id (lexer, "SCRATCH")) 
268         {
269           dict_get_vars (dataset_dict (ds), &vl, &n, DC_ORDINARY);
270           flags = 0;
271         }
272       else 
273         {
274           struct subcommand 
275             {
276               const char *name;
277               int flags;
278             };
279           static const struct subcommand subcommands[] = 
280             {
281               {"@ATTRIBUTES", DF_ATTRIBUTES | DF_AT_ATTRIBUTES},
282               {"ATTRIBUTES", DF_ATTRIBUTES},
283               {"DICTIONARY", DF_ALL & ~DF_AT_ATTRIBUTES},
284               {"INDEX", DF_DICT_INDEX},
285               {"LABELS", DF_DICT_INDEX | DF_VARIABLE_LABELS},
286               {"NAMES", 0},
287               {"VARIABLES",
288                DF_DICT_INDEX | DF_FORMATS | DF_MISSING_VALUES | DF_MISC},
289               {NULL, 0},
290             };
291           const struct subcommand *sbc;
292
293           flags = 0;
294           for (sbc = subcommands; sbc->name != NULL; sbc++)
295             if (lex_match_id (lexer, sbc->name))
296               {
297                 flags = sbc->flags;
298                 break;
299               }
300
301           lex_match (lexer, T_SLASH);
302           lex_match_id (lexer, "VARIABLES");
303           lex_match (lexer, T_EQUALS);
304
305           if (lex_token (lexer) != T_ENDCMD)
306             {
307               if (!parse_variables_const (lexer, dataset_dict (ds), &vl, &n,
308                                           PV_NONE))
309                 {
310                   free (vl);
311                   return CMD_FAILURE;
312                 }
313             }
314           else
315             dict_get_vars (dataset_dict (ds), &vl, &n, 0);
316         }
317
318       if (n > 0) 
319         {
320           sort (vl, n, sizeof *vl,
321                 (sorted
322                  ? compare_var_ptrs_by_name
323                  : compare_var_ptrs_by_dict_index), NULL);
324           display_variables (vl, n, flags);
325         }
326       else
327         msg (SW, _("No variables to display."));
328       free (vl);
329
330       if (flags & (DF_ATTRIBUTES | DF_AT_ATTRIBUTES))
331         display_data_file_attributes (dict_get_attributes (dataset_dict (ds)),
332                                       flags);
333     }
334
335   return CMD_SUCCESS;
336 }
337
338 static void
339 display_macros (void)
340 {
341   tab_output_text (TAB_LEFT, _("Macros not supported."));
342 }
343
344 static void
345 display_documents (const struct dictionary *dict)
346 {
347   const struct string_array *documents = dict_get_documents (dict);
348
349   if (string_array_is_empty (documents))
350     tab_output_text (TAB_LEFT, _("The active dataset dictionary does not "
351                                  "contain any documents."));
352   else
353     {
354       size_t i;
355
356       tab_output_text (TAB_LEFT | TAT_TITLE,
357                        _("Documents in the active dataset:"));
358       for (i = 0; i < dict_get_document_line_cnt (dict); i++)
359         tab_output_text (TAB_LEFT | TAB_FIX, dict_get_document_line (dict, i));
360     }
361 }
362
363 static void
364 display_variables (const struct variable **vl, size_t n, int flags)
365 {
366   struct tab_table *t;
367   int nc;                       /* Number of columns. */
368   int pc;                       /* `Position column' */
369   int r;                        /* Current row. */
370   size_t i;
371
372   /* One column for the name,
373      two columns for general description,
374      one column for dictionary index. */
375   nc = 1;
376   if (flags & ~DF_DICT_INDEX)
377     nc += 2;
378   pc = nc;
379   if (flags & DF_DICT_INDEX)
380     nc++;
381
382   t = tab_create (nc, n + 5);
383   tab_headers (t, 0, 0, 1, 0);
384   tab_hline (t, TAL_2, 0, nc - 1, 1);
385   tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
386   if (flags & ~DF_DICT_INDEX) 
387     tab_joint_text (t, 1, 0, 2, 0, TAB_LEFT | TAT_TITLE,
388                     (flags & ~(DF_DICT_INDEX | DF_VARIABLE_LABELS)
389                      ? _("Description") : _("Label")));
390   if (flags & DF_DICT_INDEX)
391     tab_text (t, pc, 0, TAB_LEFT | TAT_TITLE, _("Position"));
392
393   r = 1;
394   for (i = 0; i < n; i++)
395     r = describe_variable (vl[i], t, r, pc, flags);
396   tab_hline (t, flags & ~DF_DICT_INDEX ? TAL_2 : TAL_1, 0, nc - 1, 1);
397   if (flags)
398     {
399       tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, nc - 1, r - 1);
400       tab_vline (t, TAL_1, 1, 0, r - 1);
401     }
402   if (flags & ~DF_DICT_INDEX)
403     tab_vline (t, TAL_1, nc - 1, 0, r - 1);
404   tab_resize (t, -1, r);
405   tab_submit (t);
406 }
407 \f
408 static bool
409 is_at_name (const char *name) 
410 {
411   return name[0] == '@' || (name[0] == '$' && name[1] == '@');
412 }
413
414 static size_t
415 count_attributes (const struct attrset *set, int flags) 
416 {
417   struct attrset_iterator i;
418   struct attribute *attr;
419   size_t n_attrs;
420   
421   n_attrs = 0;
422   for (attr = attrset_first (set, &i); attr != NULL;
423        attr = attrset_next (set, &i)) 
424     if (flags & DF_AT_ATTRIBUTES || !is_at_name (attribute_get_name (attr)))
425       n_attrs += attribute_get_n_values (attr);
426   return n_attrs;
427 }
428
429 static void
430 display_attributes (struct tab_table *t, const struct attrset *set, int flags,
431                     int c, int r)
432 {
433   struct attribute **attrs;
434   size_t n_attrs;
435   size_t i;
436
437   n_attrs = attrset_count (set);
438   attrs = attrset_sorted (set);
439   for (i = 0; i < n_attrs; i++)
440     {
441       const struct attribute *attr = attrs[i];
442       const char *name = attribute_get_name (attr);
443       size_t n_values;
444       size_t j;
445
446       if (!(flags & DF_AT_ATTRIBUTES) && is_at_name (name))
447         continue;
448
449       n_values = attribute_get_n_values (attr);
450       for (j = 0; j < n_values; j++)
451         {
452           if (n_values > 1)
453             tab_text_format (t, c, r, TAB_LEFT, "%s[%zu]", name, j + 1);
454           else
455             tab_text (t, c, r, TAB_LEFT, name);
456           tab_text (t, c + 1, r, TAB_LEFT, attribute_get_value (attr, j));
457           r++;
458         }
459     }
460   free (attrs);
461 }
462
463 static void
464 display_data_file_attributes (struct attrset *set, int flags) 
465 {
466   struct tab_table *t;
467   size_t n_attrs;
468
469   n_attrs = count_attributes (set, flags);
470   if (!n_attrs)
471     return;
472
473   t = tab_create (2, n_attrs + 1);
474   tab_headers (t, 0, 0, 1, 0);
475   tab_box (t, TAL_1, TAL_1, -1, TAL_1, 0, 0, tab_nc (t) - 1, tab_nr (t) - 1);
476   tab_hline (t, TAL_2, 0, 1, 1); 
477   tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Attribute"));
478   tab_text (t, 1, 0, TAB_LEFT | TAT_TITLE, _("Value"));
479   display_attributes (t, set, flags, 0, 1);
480   tab_title (t, "Custom data file attributes.");
481   tab_submit (t);
482 }
483
484 /* Puts a description of variable V into table T starting at row
485    R.  The variable will be described in the format given by
486    FLAGS.  Returns the next row available for use in the
487    table. */
488 static int
489 describe_variable (const struct variable *v, struct tab_table *t, int r,
490                    int pc, int flags)
491 {
492   size_t n_attrs = 0;
493   int need_rows;
494
495   /* Make sure that enough rows are allocated. */
496   need_rows = 1;
497   if (flags & ~(DF_DICT_INDEX | DF_VARIABLE_LABELS))
498     need_rows += 16;
499   if (flags & DF_VALUE_LABELS)
500     need_rows += val_labs_count (var_get_value_labels (v));
501   if (flags & (DF_ATTRIBUTES | DF_AT_ATTRIBUTES))
502     {
503       n_attrs = count_attributes (var_get_attributes (v), flags);
504       need_rows += n_attrs; 
505     }
506   if (r + need_rows > tab_nr (t))
507     {
508       int nr = MAX (r + need_rows, tab_nr (t) * 2);
509       tab_realloc (t, -1, nr);
510     }
511
512   /* Put the name, var label, and position into the first row. */
513   tab_text (t, 0, r, TAB_LEFT, var_get_name (v));
514   if (flags & DF_DICT_INDEX)
515     tab_text_format (t, pc, r, 0, "%zu", var_get_dict_index (v) + 1);
516
517   if (flags & DF_VARIABLE_LABELS && var_has_label (v))
518     {
519       if (flags & ~(DF_DICT_INDEX | DF_VARIABLE_LABELS))
520         tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
521                                _("Label: %s"), var_get_label (v));
522       else
523         tab_joint_text (t, 1, r, 2, r, TAB_LEFT, var_get_label (v));
524       r++;
525     }
526
527   /* Print/write format, or print and write formats. */
528   if (flags & DF_FORMATS) 
529     {
530       const struct fmt_spec *print = var_get_print_format (v);
531       const struct fmt_spec *write = var_get_write_format (v);
532
533       if (fmt_equal (print, write))
534         {
535           char str[FMT_STRING_LEN_MAX + 1];
536           tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
537                                  _("Format: %s"), fmt_to_string (print, str));
538           r++;
539         }
540       else
541         {
542           char str[FMT_STRING_LEN_MAX + 1];
543           tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
544                                  _("Print Format: %s"),
545                                  fmt_to_string (print, str));
546           r++;
547           tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
548                                  _("Write Format: %s"),
549                                  fmt_to_string (write, str));
550           r++;
551         }
552     }
553   
554   /* Measurement level, role, display width, alignment. */
555   if (flags & DF_MISC) 
556     {
557       enum var_role role = var_get_role (v);
558
559       tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
560                              _("Measure: %s"),
561                              measure_to_string (var_get_measure (v)));
562       r++;
563
564       if (role != ROLE_INPUT)
565         {
566           tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
567                                  _("Role: %s"), var_role_to_string (role));
568           r++;
569         }
570
571       tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
572                              _("Display Alignment: %s"),
573                              alignment_to_string (var_get_alignment (v)));
574       r++;
575
576       tab_joint_text_format (t, 1, r, 2, r, TAB_LEFT,
577                              _("Display Width: %d"),
578                              var_get_display_width (v));
579       r++;
580     }
581   
582   /* Missing values if any. */
583   if (flags & DF_MISSING_VALUES && var_has_missing_values (v))
584     {
585       const struct missing_values *mv = var_get_missing_values (v);
586       char buf[128];
587       char *cp;
588       int cnt = 0;
589       int i;
590
591       cp = stpcpy (buf, _("Missing Values: "));
592
593       if (mv_has_range (mv))
594         {
595           double x, y;
596           mv_get_range (mv, &x, &y);
597           if (x == LOWEST)
598             cp += sprintf (cp, "LOWEST THRU %.*g", DBL_DIG + 1, y);
599           else if (y == HIGHEST)
600             cp += sprintf (cp, "%.*g THRU HIGHEST", DBL_DIG + 1, x);
601           else
602             cp += sprintf (cp, "%.*g THRU %.*g",
603                            DBL_DIG + 1, x,
604                            DBL_DIG + 1, y);
605           cnt++;
606         }
607       for (i = 0; i < mv_n_values (mv); i++)
608         {
609           const union value *value = mv_get_value (mv, i);
610           if (cnt++ > 0)
611             cp += sprintf (cp, "; ");
612           if (var_is_numeric (v))
613             cp += sprintf (cp, "%.*g", DBL_DIG + 1, value->f);
614           else
615             {
616               int width = var_get_width (v);
617               int mv_width = MIN (width, MV_MAX_STRING);
618
619               *cp++ = '"';
620               memcpy (cp, value_str (value, width), mv_width);
621               cp += mv_width;
622               *cp++ = '"';
623               *cp = '\0';
624             }
625         }
626
627       tab_joint_text (t, 1, r, 2, r, TAB_LEFT, buf);
628       r++;
629     }
630
631   /* Value labels. */
632   if (flags & DF_VALUE_LABELS && var_has_value_labels (v))
633     {
634       const struct val_labs *val_labs = var_get_value_labels (v);
635       size_t n_labels = val_labs_count (val_labs);
636       const struct val_lab **labels;
637       int orig_r = r;
638       size_t i;
639
640 #if 0
641       tab_text (t, 1, r, TAB_LEFT, _("Value"));
642       tab_text (t, 2, r, TAB_LEFT, _("Label"));
643       r++;
644 #endif
645
646       tab_hline (t, TAL_1, 1, 2, r);
647
648       labels = val_labs_sorted (val_labs);
649       for (i = 0; i < n_labels; i++)
650         {
651           const struct val_lab *vl = labels[i];
652
653           tab_value (t, 1, r, TAB_NONE, &vl->value, v, NULL);
654           tab_text (t, 2, r, TAB_LEFT, val_lab_get_escaped_label (vl));
655           r++;
656         }
657       free (labels);
658
659       tab_vline (t, TAL_1, 2, orig_r, r - 1);
660     }
661
662   if (flags & (DF_ATTRIBUTES | DF_AT_ATTRIBUTES) && n_attrs)
663     {
664       tab_joint_text (t, 1, r, 2, r, TAB_LEFT, "Custom attributes:");
665       r++;
666
667       display_attributes (t, var_get_attributes (v), flags, 1, r);
668       r += n_attrs;
669     }
670
671   /* Draw a line below the last row of information on this variable. */
672   tab_hline (t, TAL_1, 0, tab_nc (t) - 1, r);
673
674   return r;
675 }
676
677 /* Display a list of vectors.  If SORTED is nonzero then they are
678    sorted alphabetically. */
679 static void
680 display_vectors (const struct dictionary *dict, int sorted)
681 {
682   const struct vector **vl;
683   int i;
684   struct tab_table *t;
685   size_t nvec;
686   size_t nrow;
687   size_t row;
688
689   nvec = dict_get_vector_cnt (dict);
690   if (nvec == 0)
691     {
692       msg (SW, _("No vectors defined."));
693       return;
694     }
695
696   vl = xnmalloc (nvec, sizeof *vl);
697   nrow = 0;
698   for (i = 0; i < nvec; i++)
699     {
700       vl[i] = dict_get_vector (dict, i);
701       nrow += vector_get_var_cnt (vl[i]);
702     }
703   if (sorted)
704     qsort (vl, nvec, sizeof *vl, compare_vector_ptrs_by_name);
705
706   t = tab_create (4, nrow + 1);
707   tab_headers (t, 0, 0, 1, 0);
708   tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, nrow);
709   tab_box (t, -1, -1, -1, TAL_1, 0, 0, 3, nrow);
710   tab_hline (t, TAL_2, 0, 3, 1);
711   tab_text (t, 0, 0, TAT_TITLE | TAB_LEFT, _("Vector"));
712   tab_text (t, 1, 0, TAT_TITLE | TAB_LEFT, _("Position"));
713   tab_text (t, 2, 0, TAT_TITLE | TAB_LEFT, _("Variable"));
714   tab_text (t, 3, 0, TAT_TITLE | TAB_LEFT, _("Print Format"));
715
716   row = 1;
717   for (i = 0; i < nvec; i++)
718     {
719       const struct vector *vec = vl[i];
720       size_t j;
721
722       tab_joint_text (t, 0, row, 0, row + vector_get_var_cnt (vec) - 1,
723                       TAB_LEFT, vector_get_name (vl[i]));
724
725       for (j = 0; j < vector_get_var_cnt (vec); j++)
726         {
727           struct variable *var = vector_get_var (vec, j);
728           char fmt_string[FMT_STRING_LEN_MAX + 1];
729           fmt_to_string (var_get_print_format (var), fmt_string);
730
731           tab_text_format (t, 1, row, TAB_RIGHT, "%zu", j + 1);
732           tab_text (t, 2, row, TAB_LEFT, var_get_name (var));
733           tab_text (t, 3, row, TAB_LEFT, fmt_string);
734           row++;
735         }
736       tab_hline (t, TAL_1, 0, 3, row);
737     }
738
739   tab_submit (t);
740
741   free (vl);
742 }