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