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