174c7ead573ad9f30900bb4b9d4851b565ec8aea
[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     fill_all_vars (&cmd.v_variables, &cmd.n_variables,
156                    FV_NO_SYSTEM | FV_NO_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       update_weighting (&default_dict);
196       if (default_dict.weight_index != -1)
197         {
198           int i;
199
200           for (i = 0; i < cmd.n_variables; i++)
201             if (cmd.v_variables[i]->index == default_dict.weight_index)
202               break;
203           if (i >= cmd.n_variables)
204             {
205               /* Add the weight variable to the end of the variable list. */
206               cmd.n_variables++;
207               cmd.v_variables = xrealloc (cmd.v_variables,
208                                           (cmd.n_variables
209                                            * sizeof *cmd.v_variables));
210               cmd.v_variables[cmd.n_variables - 1]
211                 = default_dict.var[default_dict.weight_index];
212             }
213         }
214       else
215         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
216     }
217
218   /* Case number. */
219   if (cmd.numbering == LST_NUMBERED)
220     {
221       /* Initialize the case-number variable. */
222       strcpy (casenum_var.name, "Case#");
223       casenum_var.type = NUMERIC;
224       casenum_var.fv = -1;
225       casenum_var.print.type = FMT_F;
226       casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
227       casenum_var.print.d = 0;
228
229       /* Add the weight variable at the beginning of the variable list. */
230       cmd.n_variables++;
231       cmd.v_variables = xrealloc (cmd.v_variables,
232                                   cmd.n_variables * sizeof *cmd.v_variables);
233       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
234                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
235       cmd.v_variables[0] = &casenum_var;
236     }
237
238 #if DEBUGGING
239   /* Print out command. */
240   debug_print ();
241 #endif
242
243   determine_layout ();
244
245   case_num = 0;
246   procedure (write_all_headers, list_cases, NULL);
247   free (line_buf);
248
249   clean_up ();
250
251   return CMD_SUCCESS;
252 }
253
254 /* Writes headers to all devices.  This is done at the beginning of
255    each SPLIT FILE group. */
256 static void
257 write_all_headers (void)
258 {
259   struct outp_driver *d;
260
261   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
262     {
263       if (!d->class->special)
264         {
265           d->cp_y += d->font_height;            /* Blank line. */
266           write_header (d);
267         }
268       else if (d->class == &html_class)
269         {
270           struct html_driver_ext *x = d->ext;
271   
272           assert (d->driver_open && d->page_open);
273           if (x->sequence_no == 0 && !d->class->open_page (d))
274             {
275               msg (ME, _("Cannot open first page on HTML device %s."),
276                    d->name);
277               return;
278             }
279
280           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
281           
282           {
283             int i;
284
285             for (i = 0; i < cmd.n_variables; i++)
286               fprintf (x->file.file, "    <TH><I><B>%s</B></I></TH>\n",
287                        cmd.v_variables[i]->name);
288           }
289
290           fputs ("  <TR>\n", x->file.file);
291         }
292       else
293         assert (0);
294     }
295 }
296
297 /* Writes the headers.  Some of them might be vertical; most are
298    probably horizontal. */
299 static void
300 write_header (struct outp_driver *d)
301 {
302   struct list_ext *prc = d->prc;
303
304   if (!prc->header_rows)
305     return;
306   
307   if (n_lines_remaining (d) < prc->header_rows + 1)
308     {
309       outp_eject_page (d);
310       assert (n_lines_remaining (d) >= prc->header_rows + 1);
311     }
312
313   /* Design the header. */
314   if (!prc->header)
315     {
316       int i, x;
317
318       /* Allocate, initialize header. */
319       prc->header = xmalloc (sizeof (char *) * prc->header_rows);
320       {
321         int w = n_chars_width (d);
322         for (i = 0; i < prc->header_rows; i++)
323           {
324             prc->header[i] = xmalloc (w + 1);
325             memset (prc->header[i], ' ', w);
326           }
327       }
328
329       /* Put in vertical names. */
330       for (i = x = 0; i < prc->n_vertical; i++)
331         {
332           struct variable *v = cmd.v_variables[i];
333           int j;
334
335           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
336           x += v->print.w - 1;
337           for (j = 0; j < (int) strlen (v->name); j++)
338             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
339           x += 2;
340         }
341
342       /* Put in horizontal names. */
343       for (; i < cmd.n_variables; i++)
344         {
345           struct variable *v = cmd.v_variables[i];
346           
347           memset (&prc->header[prc->header_rows - 1][x], '-',
348                   max (v->print.w, (int) strlen (v->name)));
349           if ((int) strlen (v->name) < v->print.w)
350             x += v->print.w - strlen (v->name);
351           memcpy (&prc->header[0][x], v->name, strlen (v->name));
352           x += strlen (v->name) + 1;
353         }
354
355       /* Add null bytes. */
356       for (i = 0; i < prc->header_rows; i++)
357         {
358           for (x = n_chars_width (d); x >= 1; x--)
359             if (prc->header[i][x - 1] != ' ')
360               {
361                 prc->header[i][x] = 0;
362                 break;
363               }
364           assert (x);
365         }
366     }
367
368   /* Write out the header, in back-to-front order except for the last line. */
369   {
370     int i;
371     
372     for (i = prc->header_rows - 2; i >= 0; i--)
373       write_line (d, prc->header[i]);
374     write_line (d, prc->header[prc->header_rows - 1]);
375   }
376 }
377       
378   
379 /* Frees up all the memory we've allocated. */
380 static void
381 clean_up (void)
382 {
383   struct outp_driver *d;
384   
385   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
386     if (d->class->special == 0)
387       {
388         struct list_ext *prc = d->prc;
389         int i;
390
391         if (prc->header)
392           {
393             for (i = 0; i < prc->header_rows; i++)
394               free (prc->header[i]);
395             free (prc->header);
396           }
397         free (prc);
398       
399         d->class->text_set_font_by_name (d, "PROP");
400       }
401     else if (d->class == &html_class)
402       {
403         if (d->driver_open && d->page_open)
404           {
405             struct html_driver_ext *x = d->ext;
406
407             fputs ("</TABLE>\n", x->file.file);
408           }
409       }
410     else
411       assert (0);
412   
413   free (cmd.v_variables);
414 }
415
416 /* Writes string STRING at the current position.  If the text would
417    fall off the side of the page, then advance to the next line,
418    indenting by amount INDENT. */
419 static void
420 write_varname (struct outp_driver *d, char *string, int indent)
421 {
422   struct outp_text text;
423
424   text.options = OUTP_T_JUST_LEFT;
425   ls_init (&text.s, string, strlen (string));
426   d->class->text_metrics (d, &text);
427   
428   if (d->cp_x + text.h > d->width)
429     {
430       d->cp_y += d->font_height;
431       if (d->cp_y + d->font_height > d->length)
432         outp_eject_page (d);
433       d->cp_x = indent;
434     }
435
436   text.x = d->cp_x;
437   text.y = d->cp_y;
438   d->class->text_draw (d, &text);
439   d->cp_x += text.h;
440 }
441
442 /* When we can't fit all the values across the page, we write out all
443    the variable names just once.  This is where we do it. */
444 static void
445 write_fallback_headers (struct outp_driver *d)
446 {
447   const int max_width = n_chars_width(d) - 10;
448   
449   int index = 0;
450   int width = 0;
451   int line_number = 0;
452
453   const char *Line = _("Line");
454   char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
455       
456   while (index < cmd.n_variables)
457     {
458       struct outp_text text;
459
460       /* Ensure that there is enough room for a line of text. */
461       if (d->cp_y + d->font_height > d->length)
462         outp_eject_page (d);
463       
464       /* The leader is a string like `Line 1: '.  Write the leader. */
465       sprintf(leader, "%s %d:", Line, ++line_number);
466       text.options = OUTP_T_JUST_LEFT;
467       ls_init (&text.s, leader, strlen (leader));
468       text.x = 0;
469       text.y = d->cp_y;
470       d->class->text_draw (d, &text);
471       d->cp_x = text.h;
472
473       goto entry;
474       do
475         {
476           width++;
477
478         entry:
479           {
480             int var_width = cmd.v_variables[index]->print.w;
481             if (width + var_width > max_width && width != 0)
482               {
483                 width = 0;
484                 d->cp_x = 0;
485                 d->cp_y += d->font_height;
486                 break;
487               }
488             width += var_width;
489           }
490           
491           {
492             char varname[10];
493             sprintf (varname, " %s", cmd.v_variables[index]->name);
494             write_varname (d, varname, text.h);
495           }
496         }
497       while (++index < cmd.n_variables);
498
499     }
500   d->cp_x = 0;
501   d->cp_y += d->font_height;
502   
503   local_free (leader);
504 }
505
506 /* There are three possible layouts for the LIST procedure:
507
508    1. If the values and their variables' name fit across the page,
509    then they are listed across the page in that way.
510
511    2. If the values can fit across the page, but not the variable
512    names, then as many variable names as necessary are printed
513    vertically to compensate.
514
515    3. If not even the values can fit across the page, the variable
516    names are listed just once, at the beginning, in a compact format,
517    and the values are listed with a variable name label at the
518    beginning of each line for easier reference.
519
520    This is complicated by the fact that we have to do all this for
521    every output driver, not just once.  */
522 static void
523 determine_layout (void)
524 {
525   struct outp_driver *d;
526   
527   /* This is the largest page width of any driver, so we can tell what
528      size buffer to allocate. */
529   int largest_page_width = 0;
530   
531   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
532     {
533       int column;       /* Current column. */
534       int width;        /* Accumulated width. */
535       int max_width;    /* Page width. */
536       
537       struct list_ext *prc;
538
539       if (d->class == &html_class)
540         continue;
541       
542       assert (d->class->special == 0);
543
544       if (!d->page_open)
545         d->class->open_page (d);
546       
547       max_width = n_chars_width (d);
548       largest_page_width = max (largest_page_width, max_width);
549
550       prc = d->prc = xmalloc (sizeof *prc);
551       prc->type = 0;
552       prc->n_vertical = 0;
553       prc->header = NULL;
554
555       /* Try layout #1. */
556       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
557         {
558           struct variable *v = cmd.v_variables[column];
559           width += max (v->print.w, (int) strlen (v->name));
560         }
561       if (width <= max_width)
562         {
563           prc->header_rows = 2;
564           d->class->text_set_font_by_name (d, "FIXED");
565           continue;
566         }
567
568       /* Try layout #2. */
569       for (width = cmd.n_variables - 1, column = 0;
570            column < cmd.n_variables && width <= max_width;
571            column++)
572           width += cmd.v_variables[column]->print.w;
573       
574       /* If it fit then we need to determine how many labels can be
575          written horizontally. */
576       if (width <= max_width)
577         {
578 #ifndef NDEBUG
579           prc->n_vertical = -1;
580 #endif
581           for (column = cmd.n_variables - 1; column >= 0; column--)
582             {
583               struct variable *v = cmd.v_variables[column];
584               int trial_width = (width - v->print.w
585                                  + max (v->print.w, (int) strlen (v->name)));
586               
587               if (trial_width > max_width)
588                 {
589                   prc->n_vertical = column + 1;
590                   break;
591                 }
592               width = trial_width;
593             }
594           assert(prc->n_vertical != -1);
595
596           prc->n_vertical = cmd.n_variables;
597           /* Finally determine the length of the headers. */
598           for (prc->header_rows = 0, column = 0;
599                column < prc->n_vertical;
600                column++)
601             prc->header_rows = max (prc->header_rows,
602                                     (int) strlen (cmd.v_variables[column]->name));
603           prc->header_rows++;
604
605           d->class->text_set_font_by_name (d, "FIXED");
606           continue;
607         }
608
609       /* Otherwise use the ugly fallback listing format. */
610       prc->type = 1;
611       prc->header_rows = 0;
612
613       d->cp_y += d->font_height;
614       write_fallback_headers (d);
615       d->cp_y += d->font_height;
616       d->class->text_set_font_by_name (d, "FIXED");
617     }
618
619   line_buf = xmalloc (max (1022, largest_page_width) + 2);
620 }
621
622 static int
623 list_cases (struct ccase *c)
624 {
625   struct outp_driver *d;
626   
627   case_num++;
628   if (case_num < cmd.first || case_num > cmd.last
629       || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
630     return 1;
631
632   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
633     if (d->class->special == 0)
634       {
635         const struct list_ext *prc = d->prc;
636         const int max_width = n_chars_width (d);
637         int column;
638         int x = 0;
639
640         if (!prc->header_rows)
641           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
642       
643         for (column = 0; column < cmd.n_variables; column++)
644           {
645             struct variable *v = cmd.v_variables[column];
646             int width;
647
648             if (prc->type == 0 && column >= prc->n_vertical)
649               width = max ((int) strlen (v->name), v->print.w);
650             else
651               width = v->print.w;
652
653             if (width + x > max_width && x != 0)
654               {
655                 if (!n_lines_remaining (d))
656                   {
657                     outp_eject_page (d);
658                     write_header (d);
659                   }
660               
661                 line_buf[x] = 0;
662                 write_line (d, line_buf);
663
664                 x = 0;
665                 if (!prc->header_rows)
666                   x = nsprintf (line_buf, "%8s: ", v->name);
667               }
668
669             if (width > v->print.w)
670               {
671                 memset(&line_buf[x], ' ', width - v->print.w);
672                 x += width - v->print.w;
673               }
674           
675             {
676               union value value;
677             
678               if (formats[v->print.type].cat & FCAT_STRING)
679                 value.c = c->data[v->fv].s;
680               else if (v->fv == -1)
681                 value.f = case_num;
682               else
683                 value.f = c->data[v->fv].f;
684                 
685               data_out (&line_buf[x], &v->print, &value);
686             }
687             x += v->print.w;
688           
689             line_buf[x++] = ' ';
690           }
691       
692         if (!n_lines_remaining (d))
693           {
694             outp_eject_page (d);
695             write_header (d);
696           }
697               
698         line_buf[x] = 0;
699         write_line (d, line_buf);
700       }
701     else if (d->class == &html_class)
702       {
703         struct html_driver_ext *x = d->ext;
704         int column;
705
706         fputs ("  <TR>\n", x->file.file);
707         
708         for (column = 0; column < cmd.n_variables; column++)
709           {
710             struct variable *v = cmd.v_variables[column];
711             union value value;
712             char buf[41];
713             
714             if (formats[v->print.type].cat & FCAT_STRING)
715               value.c = c->data[v->fv].s;
716             else if (v->fv == -1)
717               value.f = case_num;
718             else
719               value.f = c->data[v->fv].f;
720                 
721             data_out (buf, &v->print, &value);
722             buf[v->print.w] = 0;
723
724             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
725                      &buf[strspn (buf, " ")]);
726           }
727           
728         fputs ("  </TR>\n", x->file.file);
729       }
730     else
731       assert (0);
732
733   return 1;
734 }
735 \f
736 /* Debugging output. */
737
738 #if DEBUGGING
739 /* Prints out the command as parsed by cmd_list(). */
740 static void
741 debug_print (void)
742 {
743   int i;
744
745   puts ("LIST");
746   printf ("  VARIABLES=");
747   for (i = 0; i < cmd.n_variables; i++)
748     {
749       if (i)
750         putc (' ', stdout);
751       fputs (cmd.v_variables[i]->name, stdout);
752     }
753
754   printf ("\n  /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
755
756   fputs ("  /FORMAT=", stdout);
757   if (cmd.numbering == LST_NUMBERED)
758     fputs ("NUMBERED", stdout);
759   else
760     fputs ("UNNUMBERED", stdout);
761   putc (' ', stdout);
762   if (cmd.wrap == LST_WRAP)
763     fputs ("WRAP", stdout);
764   else
765     fputs ("SINGLE", stdout);
766   putc (' ', stdout);
767   if (cmd.weight == LST_WEIGHT)
768     fputs ("WEIGHT", stdout);
769   else
770     fputs ("NOWEIGHT", stdout);
771   puts (".");
772 }
773 #endif /* DEBUGGING */
774
775 /* 
776    Local Variables:
777    mode: c
778    End:
779 */