LIST: Remove WEIGHT subcommand.
[pspp-builds.git] / src / language / data-io / list.q
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2009 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 <stdint.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22
23 #include "intprops.h"
24 #include "xmalloca.h"
25
26 #include <data/casegrouper.h>
27 #include <data/casereader.h>
28 #include <data/dictionary.h>
29 #include <data/data-out.h>
30 #include <data/format.h>
31 #include <data/procedure.h>
32 #include <data/short-names.h>
33 #include <data/variable.h>
34 #include <language/command.h>
35 #include <language/dictionary/split-file.h>
36 #include <language/lexer/lexer.h>
37 #include <libpspp/compiler.h>
38 #include <libpspp/ll.h>
39 #include <libpspp/message.h>
40 #include <libpspp/misc.h>
41 #include <output/htmlP.h>
42 #include <output/manager.h>
43 #include <output/output.h>
44 #include <output/table.h>
45
46 #include "minmax.h"
47 #include "xalloc.h"
48
49 #include "gettext.h"
50 #define _(msgid) gettext (msgid)
51
52 /* (headers) */
53
54 /* (specification)
55    list (lst_):
56      *variables=varlist("PV_NO_SCRATCH");
57      cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
58      +format=numbering:numbered/!unnumbered,
59              wrap:!wrap/single.
60 */
61 /* (declarations) */
62 /* (functions) */
63
64 /* Layout for one output driver. */
65 struct list_target
66   {
67     struct ll ll;
68     struct outp_driver *driver;
69     int type;           /* 0=Values and labels fit across the page. */
70     size_t n_vertical;  /* Number of labels to list vertically. */
71     size_t header_rows; /* Number of header rows. */
72     char **header;      /* The header itself. */
73   };
74
75 /* Parsed command. */
76 static struct cmd_list cmd;
77
78 /* Line buffer. */
79 static struct string line_buffer;
80
81 /* TTY-style output functions. */
82 static unsigned n_lines_remaining (struct outp_driver *d);
83 static unsigned n_chars_width (struct outp_driver *d);
84 static void write_line (struct outp_driver *d, const char *s);
85
86 /* Other functions. */
87 static void list_case (const struct ccase *, casenumber case_idx,
88                        const struct dataset *, struct ll_list *targets);
89 static void determine_layout (struct ll_list *targets);
90 static void clean_up (struct ll_list *targets);
91 static void write_header (struct list_target *);
92 static void write_all_headers (struct casereader *, const struct dataset *,
93                                struct ll_list *targets);
94
95 /* Returns the number of text lines that can fit on the remainder of
96    the page. */
97 static inline unsigned
98 n_lines_remaining (struct outp_driver *d)
99 {
100   int diff;
101
102   diff = d->length - d->cp_y;
103   return (diff > 0) ? (diff / d->font_height) : 0;
104 }
105
106 /* Returns the number of fixed-width character that can fit across the
107    page. */
108 static inline unsigned
109 n_chars_width (struct outp_driver *d)
110 {
111   return d->width / d->fixed_width;
112 }
113
114 /* Writes the line S at the current position and advances to the next
115    line.  */
116 static void
117 write_line (struct outp_driver *d, const char *s)
118 {
119   struct outp_text text;
120
121   assert (d->cp_y + d->font_height <= d->length);
122   text.font = OUTP_FIXED;
123   text.justification = OUTP_LEFT;
124   text.string = ss_cstr (s);
125   text.x = d->cp_x;
126   text.y = d->cp_y;
127   text.h = text.v = INT_MAX;
128   d->class->text_draw (d, &text);
129   d->cp_x = 0;
130   d->cp_y += d->font_height;
131 }
132
133 /* Parses and executes the LIST procedure. */
134 int
135 cmd_list (struct lexer *lexer, struct dataset *ds)
136 {
137   struct dictionary *dict = dataset_dict (ds);
138   struct variable *casenum_var = NULL;
139   struct casegrouper *grouper;
140   struct casereader *group;
141   struct ll_list targets;
142   casenumber case_idx;
143   bool ok;
144
145   if (!parse_list (lexer, ds, &cmd, NULL))
146     return CMD_FAILURE;
147
148   /* Fill in defaults. */
149   if (cmd.step == LONG_MIN)
150     cmd.step = 1;
151   if (cmd.first == LONG_MIN)
152     cmd.first = 1;
153   if (cmd.last == LONG_MIN)
154     cmd.last = LONG_MAX;
155   if (!cmd.sbc_variables)
156     dict_get_vars (dict, &cmd.v_variables, &cmd.n_variables,
157                    DC_SYSTEM | DC_SCRATCH);
158   if (cmd.n_variables == 0)
159     {
160       msg (SE, _("No variables specified."));
161       return CMD_FAILURE;
162     }
163
164   /* Verify arguments. */
165   if (cmd.first > cmd.last)
166     {
167       int t;
168       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
169            "specified.  The values will be swapped."), cmd.first, cmd.last);
170       t = cmd.first;
171       cmd.first = cmd.last;
172       cmd.last = t;
173     }
174   if (cmd.first < 1)
175     {
176       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
177            "being reset to 1."), cmd.first);
178       cmd.first = 1;
179     }
180   if (cmd.last < 1)
181     {
182       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
183            "being reset to 1."), cmd.last);
184       cmd.last = 1;
185     }
186   if (cmd.step < 1)
187     {
188       msg (SW, _("The step value %ld is less than 1.  The value is being "
189            "reset to 1."), cmd.step);
190       cmd.step = 1;
191     }
192
193   /* Case number. */
194   if (cmd.numbering == LST_NUMBERED)
195     {
196       /* Initialize the case-number variable. */
197       int width = cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last);
198       struct fmt_spec format = fmt_for_output (FMT_F, width, 0);
199       casenum_var = var_create ("Case#", 0);
200       var_set_both_formats (casenum_var, &format);
201
202       /* Add the case-number variable at the beginning of the variable list. */
203       cmd.n_variables++;
204       cmd.v_variables = xnrealloc (cmd.v_variables,
205                                    cmd.n_variables, sizeof *cmd.v_variables);
206       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
207                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
208       cmd.v_variables[0] = casenum_var;
209     }
210
211   determine_layout (&targets);
212
213   case_idx = 0;
214   for (grouper = casegrouper_create_splits (proc_open (ds), dict);
215        casegrouper_get_next_group (grouper, &group);
216        casereader_destroy (group))
217     {
218       struct ccase *c;
219
220       write_all_headers (group, ds, &targets);
221       for (; (c = casereader_read (group)) != NULL; case_unref (c))
222         {
223           case_idx++;
224           if (case_idx >= cmd.first && case_idx <= cmd.last
225               && (case_idx - cmd.first) % cmd.step == 0)
226             list_case (c, case_idx, ds, &targets);
227         }
228     }
229   ok = casegrouper_destroy (grouper);
230   ok = proc_commit (ds) && ok;
231
232   ds_destroy(&line_buffer);
233
234   clean_up (&targets);
235
236   var_destroy (casenum_var);
237
238   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
239 }
240
241 /* Writes headers to all devices.  This is done at the beginning of
242    each SPLIT FILE group. */
243 static void
244 write_all_headers (struct casereader *input, const struct dataset *ds,
245                    struct ll_list *targets)
246 {
247   struct list_target *target;
248   struct ccase *c;
249
250   c = casereader_peek (input, 0);
251   if (c == NULL)
252     return;
253   output_split_file_values (ds, c);
254   case_unref (c);
255
256   ll_for_each (target, struct list_target, ll, targets)
257     {
258       struct outp_driver *d = target->driver;
259       if (!d->class->special)
260         {
261           d->cp_y += d->font_height;            /* Blank line. */
262           write_header (target);
263         }
264       else if (d->class == &html_class)
265         {
266           struct html_driver_ext *x = d->ext;
267
268           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file);
269
270           {
271             size_t i;
272
273             for (i = 0; i < cmd.n_variables; i++)
274               fprintf (x->file, "    <TH><EM>%s</EM></TH>\n",
275                        var_get_name (cmd.v_variables[i]));
276           }
277
278           fputs ("  </TR>\n", x->file);
279         }
280       else
281         NOT_REACHED ();
282     }
283 }
284
285 /* Writes the headers.  Some of them might be vertical; most are
286    probably horizontal. */
287 static void
288 write_header (struct list_target *target)
289 {
290   struct outp_driver *d = target->driver;
291
292   if (d->class->special || !target->header_rows)
293     return;
294
295   if (n_lines_remaining (d) < target->header_rows + 1)
296     {
297       outp_eject_page (d);
298       assert (n_lines_remaining (d) >= target->header_rows + 1);
299     }
300
301   /* Design the header. */
302   if (!target->header)
303     {
304       size_t i;
305       size_t x;
306
307       /* Allocate, initialize header. */
308       target->header = xnmalloc (target->header_rows, sizeof *target->header);
309       {
310         int w = n_chars_width (d);
311         for (i = 0; i < target->header_rows; i++)
312           {
313             target->header[i] = xmalloc (w + 1);
314             memset (target->header[i], ' ', w);
315           }
316       }
317
318       /* Put in vertical names. */
319       for (i = x = 0; i < target->n_vertical; i++)
320         {
321           const struct variable *v = cmd.v_variables[i];
322           const char *name = var_get_name (v);
323           size_t name_len = strlen (name);
324           const struct fmt_spec *print = var_get_print_format (v);
325           size_t j;
326
327           memset (&target->header[target->header_rows - 1][x], '-', print->w);
328           x += print->w - 1;
329           for (j = 0; j < name_len; j++)
330             target->header[name_len - j - 1][x] = name[j];
331           x += 2;
332         }
333
334       /* Put in horizontal names. */
335       for (; i < cmd.n_variables; i++)
336         {
337           const struct variable *v = cmd.v_variables[i];
338           const char *name = var_get_name (v);
339           size_t name_len = strlen (name);
340           const struct fmt_spec *print = var_get_print_format (v);
341
342           memset (&target->header[target->header_rows - 1][x], '-',
343                   MAX (print->w, (int) name_len));
344           if ((int) name_len < print->w)
345             x += print->w - name_len;
346           memcpy (&target->header[0][x], name, name_len);
347           x += name_len + 1;
348         }
349
350       /* Add null bytes. */
351       for (i = 0; i < target->header_rows; i++)
352         {
353           for (x = n_chars_width (d); x >= 1; x--)
354             if (target->header[i][x - 1] != ' ')
355               {
356                 target->header[i][x] = 0;
357                 break;
358               }
359           assert (x);
360         }
361     }
362
363   /* Write out the header, in back-to-front order except for the last line. */
364   if (target->header_rows >= 2)
365     {
366       size_t i;
367
368       for (i = target->header_rows - 1; i-- != 0; )
369         write_line (d, target->header[i]);
370     }
371   write_line (d, target->header[target->header_rows - 1]);
372 }
373
374
375 /* Frees up all the memory we've allocated. */
376 static void
377 clean_up (struct ll_list *targets)
378 {
379   struct list_target *target, *next;
380
381   ll_for_each_safe (target, next, struct list_target, ll, targets)
382     {
383       struct outp_driver *d = target->driver;
384       if (d->class->special == 0)
385         {
386           if (target->header)
387             {
388               size_t i;
389               for (i = 0; i < target->header_rows; i++)
390                 free (target->header[i]);
391               free (target->header);
392             }
393         }
394       else if (d->class == &html_class)
395         {
396           if (d->page_open)
397             {
398               struct html_driver_ext *x = d->ext;
399
400               fputs ("</TABLE>\n", x->file);
401             }
402         }
403       else
404         NOT_REACHED ();
405
406       ll_remove (&target->ll);
407       free (target);
408     }
409   
410   free (cmd.v_variables);
411 }
412
413 /* Writes string STRING at the current position.  If the text would
414    fall off the side of the page, then advance to the next line,
415    indenting by amount INDENT. */
416 static void
417 write_varname (struct outp_driver *d, char *string, int indent)
418 {
419   struct outp_text text;
420   int width;
421
422   if (d->cp_x + outp_string_width (d, string, OUTP_FIXED) > d->width)
423     {
424       d->cp_y += d->font_height;
425       if (d->cp_y + d->font_height > d->length)
426         outp_eject_page (d);
427       d->cp_x = indent;
428     }
429
430   text.font = OUTP_FIXED;
431   text.justification = OUTP_LEFT;
432   text.string = ss_cstr (string);
433   text.x = d->cp_x;
434   text.y = d->cp_y;
435   text.h = text.v = INT_MAX;
436   d->class->text_draw (d, &text);
437   d->class->text_metrics (d, &text, &width, NULL);
438   d->cp_x += width;
439 }
440
441 /* When we can't fit all the values across the page, we write out all
442    the variable names just once.  This is where we do it. */
443 static void
444 write_fallback_headers (struct outp_driver *d)
445 {
446   const int max_width = n_chars_width(d) - 10;
447
448   int index = 0;
449   int width = 0;
450   int line_number = 0;
451
452   const char *Line = _("Line");
453   char *leader = xmalloca (strlen (Line)
454                            + INT_STRLEN_BOUND (line_number) + 1 + 1);
455
456   while (index < cmd.n_variables)
457     {
458       struct outp_text text;
459       int leader_width;
460
461       /* Ensure that there is enough room for a line of text. */
462       if (d->cp_y + d->font_height > d->length)
463         outp_eject_page (d);
464
465       /* The leader is a string like `Line 1: '.  Write the leader. */
466       sprintf (leader, "%s %d:", Line, ++line_number);
467       text.font = OUTP_FIXED;
468       text.justification = OUTP_LEFT;
469       text.string = ss_cstr (leader);
470       text.x = 0;
471       text.y = d->cp_y;
472       text.h = text.v = INT_MAX;
473       d->class->text_draw (d, &text);
474       d->class->text_metrics (d, &text, &leader_width, NULL);
475       d->cp_x = leader_width;
476
477       goto entry;
478       do
479         {
480           width++;
481
482         entry:
483           {
484             int var_width = var_get_print_format (cmd.v_variables[index])->w;
485             if (width + var_width > max_width && width != 0)
486               {
487                 width = 0;
488                 d->cp_x = 0;
489                 d->cp_y += d->font_height;
490                 break;
491               }
492             width += var_width;
493           }
494
495           {
496             char varname[VAR_NAME_LEN + 2];
497             snprintf (varname, sizeof varname,
498                       " %s", var_get_name (cmd.v_variables[index]));
499             write_varname (d, varname, leader_width);
500           }
501         }
502       while (++index < cmd.n_variables);
503
504     }
505   d->cp_x = 0;
506   d->cp_y += d->font_height;
507
508   freea (leader);
509 }
510
511 /* There are three possible layouts for the LIST procedure:
512
513    1. If the values and their variables' name fit across the page,
514    then they are listed across the page in that way.
515
516    2. If the values can fit across the page, but not the variable
517    names, then as many variable names as necessary are printed
518    vertically to compensate.
519
520    3. If not even the values can fit across the page, the variable
521    names are listed just once, at the beginning, in a compact format,
522    and the values are listed with a variable name label at the
523    beginning of each line for easier reference.
524
525    This is complicated by the fact that we have to do all this for
526    every output driver, not just once.  */
527 static void
528 determine_layout (struct ll_list *targets)
529 {
530   struct outp_driver *d;
531
532   /* This is the largest page width of any driver, so we can tell what
533      size buffer to allocate. */
534   int largest_page_width = 0;
535
536   ll_init (targets);
537   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
538     {
539       size_t column;    /* Current column. */
540       int width;        /* Accumulated width. */
541       int height;       /* Height of vertical names. */
542       int max_width;    /* Page width. */
543
544       struct list_target *target;
545
546       target = xmalloc (sizeof *target);
547       ll_push_tail (targets, &target->ll);
548       target->driver = d;
549       target->type = 0;
550       target->n_vertical = 0;
551       target->header = NULL;
552
553       if (d->class == &html_class)
554         continue;
555       assert (d->class->special == 0);
556
557       outp_open_page (d);
558
559       max_width = n_chars_width (d);
560       largest_page_width = MAX (largest_page_width, max_width);
561
562       /* Try layout #1. */
563       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
564         {
565           const struct variable *v = cmd.v_variables[column];
566           int fmt_width = var_get_print_format (v)->w;
567           int name_len = strlen (var_get_name (v));
568           width += MAX (fmt_width, name_len);
569         }
570       if (width <= max_width)
571         {
572           target->header_rows = 2;
573           continue;
574         }
575
576       /* Try layout #2. */
577       for (width = cmd.n_variables - 1, height = 0, column = 0;
578            column < cmd.n_variables && width <= max_width;
579            column++)
580         {
581           const struct variable *v = cmd.v_variables[column];
582           int fmt_width = var_get_print_format (v)->w;
583           size_t name_len = strlen (var_get_name (v));
584           width += fmt_width;
585           if (name_len > height)
586             height = name_len;
587         }
588
589       /* If it fit then we need to determine how many labels can be
590          written horizontally. */
591       if (width <= max_width && height <= SHORT_NAME_LEN)
592         {
593 #ifndef NDEBUG
594           target->n_vertical = SIZE_MAX;
595 #endif
596           for (column = cmd.n_variables; column-- != 0; )
597             {
598               const struct variable *v = cmd.v_variables[column];
599               int name_len = strlen (var_get_name (v));
600               int fmt_width = var_get_print_format (v)->w;
601               int trial_width = width - fmt_width + MAX (fmt_width, name_len);
602               if (trial_width > max_width)
603                 {
604                   target->n_vertical = column + 1;
605                   break;
606                 }
607               width = trial_width;
608             }
609           assert (target->n_vertical != SIZE_MAX);
610
611           target->n_vertical = cmd.n_variables;
612           /* Finally determine the length of the headers. */
613           for (target->header_rows = 0, column = 0;
614                column < target->n_vertical;
615                column++)
616             {
617               const struct variable *var = cmd.v_variables[column];
618               size_t name_len = strlen (var_get_name (var));
619               target->header_rows = MAX (target->header_rows, name_len);
620             }
621           target->header_rows++;
622           continue;
623         }
624
625       /* Otherwise use the ugly fallback listing format. */
626       target->type = 1;
627       target->header_rows = 0;
628
629       d->cp_y += d->font_height;
630       write_fallback_headers (d);
631       d->cp_y += d->font_height;
632     }
633
634   ds_init_empty (&line_buffer);
635 }
636
637 /* Writes case C to output. */
638 static void
639 list_case (const struct ccase *c, casenumber case_idx,
640            const struct dataset *ds, struct ll_list *targets)
641 {
642   struct dictionary *dict = dataset_dict (ds);
643   const char *encoding = dict_get_encoding (dict);
644   struct list_target *target;
645
646   ll_for_each (target, struct list_target, ll, targets)
647     {
648       struct outp_driver *d = target->driver;
649
650       if (d->class->special == 0)
651         {
652           const int max_width = n_chars_width (d);
653           int column;
654
655           if (!target->header_rows)
656             {
657               ds_put_format(&line_buffer, "%8s: ",
658                             var_get_name (cmd.v_variables[0]));
659             }
660
661
662           for (column = 0; column < cmd.n_variables; column++)
663             {
664               const struct variable *v = cmd.v_variables[column];
665               const struct fmt_spec *print = var_get_print_format (v);
666               int width;
667               char *s;
668
669               if (target->type == 0 && column >= target->n_vertical)
670                 {
671                   int name_len = strlen (var_get_name (v));
672                   width = MAX (name_len, print->w);
673                 }
674               else
675                 width = print->w;
676
677               if (width + ds_length(&line_buffer) > max_width &&
678                   ds_length(&line_buffer) != 0)
679                 {
680                   if (!n_lines_remaining (d))
681                     {
682                       outp_eject_page (d);
683                       write_header (target);
684                     }
685
686                   write_line (d, ds_cstr (&line_buffer));
687                   ds_clear(&line_buffer);
688
689                   if (!target->header_rows)
690                     ds_put_format (&line_buffer, "%8s: ", var_get_name (v));
691                 }
692
693               if (width > print->w)
694                 ds_put_char_multiple(&line_buffer, ' ', width - print->w);
695
696               if (fmt_is_string (print->type) || dict_contains_var (dict, v))
697                 s = data_out (case_data (c, v), encoding, print);
698               else
699                 {
700                   union value case_idx_value;
701                   case_idx_value.f = case_idx;
702                   s = data_out (&case_idx_value, encoding, print);
703                 }
704
705               ds_put_cstr (&line_buffer, s);
706               free (s);
707               ds_put_char(&line_buffer, ' ');
708             }
709
710           if (!n_lines_remaining (d))
711             {
712               outp_eject_page (d);
713               write_header (target);
714             }
715
716           write_line (d, ds_cstr (&line_buffer));
717           ds_clear(&line_buffer);
718         }
719       else if (d->class == &html_class)
720         {
721           struct html_driver_ext *x = d->ext;
722           int column;
723
724           fputs ("  <TR>\n", x->file);
725
726           for (column = 0; column < cmd.n_variables; column++)
727             {
728               const struct variable *v = cmd.v_variables[column];
729               const struct fmt_spec *print = var_get_print_format (v);
730               char *s;
731
732               if (fmt_is_string (print->type)
733                   || dict_contains_var (dict, v))
734                 s = data_out (case_data (c, v), encoding, print);
735               else
736                 {
737                   union value case_idx_value;
738                   case_idx_value.f = case_idx;
739                   s = data_out (&case_idx_value, encoding, print);
740                 }
741
742               fputs ("    <TD>", x->file);
743               html_put_cell_contents (d, TAB_FIX, ss_cstr (s));
744               fputs ("</TD>\n", x->file);
745
746               free (s);
747             }
748
749           fputs ("  </TR>\n", x->file);
750         }
751       else
752         NOT_REACHED ();
753     }
754 }
755
756
757 /*
758    Local Variables:
759    mode: c
760    End:
761 */