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