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