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