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