Categorical value cache added
[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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, 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 "dictionary.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 "size_max.h"
35 #include "som.h"
36 #include "tab.h"
37 #include "var.h"
38 #include "vfm.h"
39 #include "format.h"
40
41 #include "gettext.h"
42 #define _(msgid) gettext (msgid)
43
44 /* (headers) */
45
46 #include "debug-print.h"
47
48 /* (specification)
49    list (lst_):
50      *variables=varlist("PV_NO_SCRATCH");
51      cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
52      format=numbering:numbered/!unnumbered,
53             wrap:!wrap/single,
54             weight:weight/!noweight.
55 */
56 /* (declarations) */
57 /* (functions) */
58
59 /* Layout for one output driver. */
60 struct list_ext
61   {
62     int type;           /* 0=Values and labels fit across the page. */
63     size_t n_vertical;  /* Number of labels to list vertically. */
64     size_t header_rows; /* Number of header rows. */
65     char **header;      /* The header itself. */
66   };
67
68 /* Parsed command. */
69 static struct cmd_list cmd;
70
71 /* Current case number. */
72 static int case_idx;
73
74 /* Line buffer. */
75 static char *line_buf;
76
77 /* TTY-style output functions. */
78 static unsigned n_lines_remaining (struct outp_driver *d);
79 static unsigned n_chars_width (struct outp_driver *d);
80 static void write_line (struct outp_driver *d, char *s);
81
82 /* Other functions. */
83 static int list_cases (struct ccase *, void *);
84 static void determine_layout (void);
85 static void clean_up (void);
86 static void write_header (struct outp_driver *);
87 static void write_all_headers (void *);
88
89 /* Returns the number of text lines that can fit on the remainder of
90    the page. */
91 static inline unsigned
92 n_lines_remaining (struct outp_driver *d)
93 {
94   int diff;
95
96   diff = d->length - d->cp_y;
97   return (diff > 0) ? (diff / d->font_height) : 0;
98 }
99
100 /* Returns the number of fixed-width character that can fit across the
101    page. */
102 static inline unsigned
103 n_chars_width (struct outp_driver *d)
104 {
105   return d->width / d->fixed_width;
106 }
107
108 /* Writes the line S at the current position and advances to the next
109    line.  */
110 static void
111 write_line (struct outp_driver *d, char *s)
112 {
113   struct outp_text text;
114   
115   assert (d->cp_y + d->font_height <= d->length);
116   text.options = OUTP_T_JUST_LEFT;
117   ls_init (&text.s, s, strlen (s));
118   text.x = d->cp_x;
119   text.y = d->cp_y;
120   d->class->text_draw (d, &text);
121   d->cp_x = 0;
122   d->cp_y += d->font_height;
123 }
124     
125 /* Parses and executes the LIST procedure. */
126 int
127 cmd_list (void)
128 {
129   struct variable casenum_var;
130
131   if (!parse_list (&cmd))
132     return CMD_FAILURE;
133   
134   /* Fill in defaults. */
135   if (cmd.step == NOT_LONG)
136     cmd.step = 1;
137   if (cmd.first == NOT_LONG)
138     cmd.first = 1;
139   if (cmd.last == NOT_LONG)
140     cmd.last = LONG_MAX;
141   if (!cmd.sbc_variables)
142     dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
143                    (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
144   if (cmd.n_variables == 0)
145     {
146       msg (SE, _("No variables specified."));
147       return CMD_FAILURE;
148     }
149
150   /* Verify arguments. */
151   if (cmd.first > cmd.last)
152     {
153       int t;
154       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
155            "specified.  The values will be swapped."), cmd.first, cmd.last);
156       t = cmd.first;
157       cmd.first = cmd.last;
158       cmd.last = t;
159     }
160   if (cmd.first < 1)
161     {
162       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
163            "being reset to 1."), cmd.first);
164       cmd.first = 1;
165     }
166   if (cmd.last < 1)
167     {
168       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
169            "being reset to 1."), cmd.last);
170       cmd.last = 1;
171     }
172   if (cmd.step < 1)
173     {
174       msg (SW, _("The step value %ld is less than 1.  The value is being "
175            "reset to 1."), cmd.step);
176       cmd.step = 1;
177     }
178
179   /* Weighting variable. */
180   if (cmd.weight == LST_WEIGHT)
181     {
182       if (dict_get_weight (default_dict) != NULL)
183         {
184           size_t i;
185
186           for (i = 0; i < cmd.n_variables; i++)
187             if (cmd.v_variables[i] == dict_get_weight (default_dict))
188               break;
189           if (i >= cmd.n_variables)
190             {
191               /* Add the weight variable to the end of the variable list. */
192               cmd.n_variables++;
193               cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
194                                            sizeof *cmd.v_variables);
195               cmd.v_variables[cmd.n_variables - 1]
196                 = dict_get_weight (default_dict);
197             }
198         }
199       else
200         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
201     }
202
203   /* Case number. */
204   if (cmd.numbering == LST_NUMBERED)
205     {
206       /* Initialize the case-number variable. */
207       strcpy (casenum_var.name, "Case#");
208       casenum_var.type = NUMERIC;
209       casenum_var.fv = -1;
210       casenum_var.print = make_output_format (FMT_F,
211                                               (cmd.last == LONG_MAX
212                                                ? 5 : intlog10 (cmd.last)), 0);
213
214       /* Add the weight variable at the beginning of the variable list. */
215       cmd.n_variables++;
216       cmd.v_variables = xnrealloc (cmd.v_variables,
217                                    cmd.n_variables, sizeof *cmd.v_variables);
218       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
219                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
220       cmd.v_variables[0] = &casenum_var;
221     }
222
223   determine_layout ();
224
225   case_idx = 0;
226   procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
227   free (line_buf);
228
229   clean_up ();
230
231   return CMD_SUCCESS;
232 }
233
234 /* Writes headers to all devices.  This is done at the beginning of
235    each SPLIT FILE group. */
236 static void
237 write_all_headers (void *aux UNUSED)
238 {
239   struct outp_driver *d;
240
241   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
242     {
243       if (!d->class->special)
244         {
245           d->cp_y += d->font_height;            /* Blank line. */
246           write_header (d);
247         }
248       else if (d->class == &html_class)
249         {
250           struct html_driver_ext *x = d->ext;
251   
252           assert (d->driver_open);
253           if (x->sequence_no == 0 && !d->class->open_page (d))
254             {
255               msg (ME, _("Cannot open first page on HTML device %s."),
256                    d->name);
257               return;
258             }
259
260           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
261           
262           {
263             size_t i;
264
265             for (i = 0; i < cmd.n_variables; i++)
266               fprintf (x->file.file, "    <TH><I><B>%s</B></I></TH>\n",
267                        cmd.v_variables[i]->name);
268           }
269
270           fputs ("  <TR>\n", x->file.file);
271         }
272       else
273         assert (0);
274     }
275 }
276
277 /* Writes the headers.  Some of them might be vertical; most are
278    probably horizontal. */
279 static void
280 write_header (struct outp_driver *d)
281 {
282   struct list_ext *prc = d->prc;
283
284   if (!prc->header_rows)
285     return;
286   
287   if (n_lines_remaining (d) < prc->header_rows + 1)
288     {
289       outp_eject_page (d);
290       assert (n_lines_remaining (d) >= prc->header_rows + 1);
291     }
292
293   /* Design the header. */
294   if (!prc->header)
295     {
296       size_t i;
297       size_t x;
298       
299       /* Allocate, initialize header. */
300       prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
301       {
302         int w = n_chars_width (d);
303         for (i = 0; i < prc->header_rows; i++)
304           {
305             prc->header[i] = xmalloc (w + 1);
306             memset (prc->header[i], ' ', w);
307           }
308       }
309
310       /* Put in vertical names. */
311       for (i = x = 0; i < prc->n_vertical; i++)
312         {
313           struct variable *v = cmd.v_variables[i];
314           size_t j;
315
316           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
317           x += v->print.w - 1;
318           for (j = 0; j < strlen (v->name); j++)
319             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
320           x += 2;
321         }
322
323       /* Put in horizontal names. */
324       for (; i < cmd.n_variables; i++)
325         {
326           struct variable *v = cmd.v_variables[i];
327           
328           memset (&prc->header[prc->header_rows - 1][x], '-',
329                   max (v->print.w, (int) strlen (v->name)));
330           if ((int) strlen (v->name) < v->print.w)
331             x += v->print.w - strlen (v->name);
332           memcpy (&prc->header[0][x], v->name, strlen (v->name));
333           x += strlen (v->name) + 1;
334         }
335
336       /* Add null bytes. */
337       for (i = 0; i < prc->header_rows; i++)
338         {
339           for (x = n_chars_width (d); x >= 1; x--)
340             if (prc->header[i][x - 1] != ' ')
341               {
342                 prc->header[i][x] = 0;
343                 break;
344               }
345           assert (x);
346         }
347     }
348
349   /* Write out the header, in back-to-front order except for the last line. */
350   if (prc->header_rows >= 2) 
351     {
352       size_t i;
353         
354       for (i = prc->header_rows - 1; i-- != 0; )
355         write_line (d, prc->header[i]); 
356     }
357   write_line (d, prc->header[prc->header_rows - 1]);
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         size_t 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       size_t column;    /* Current column. */
516       int width;        /* Accumulated width. */
517       int height;       /* Height of vertical names. */
518       int max_width;    /* Page width. */
519
520       struct list_ext *prc;
521
522       if (d->class == &html_class)
523         continue;
524       
525       assert (d->class->special == 0);
526
527       if (!d->page_open)
528         d->class->open_page (d);
529       
530       max_width = n_chars_width (d);
531       largest_page_width = max (largest_page_width, max_width);
532
533       prc = d->prc = xmalloc (sizeof *prc);
534       prc->type = 0;
535       prc->n_vertical = 0;
536       prc->header = NULL;
537
538       /* Try layout #1. */
539       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
540         {
541           struct variable *v = cmd.v_variables[column];
542           width += max (v->print.w, (int) strlen (v->name));
543         }
544       if (width <= max_width)
545         {
546           prc->header_rows = 2;
547           d->class->text_set_font_by_name (d, "FIXED");
548           continue;
549         }
550
551       /* Try layout #2. */
552       for (width = cmd.n_variables - 1, height = 0, column = 0;
553            column < cmd.n_variables && width <= max_width;
554            column++) 
555         {
556           struct variable *v = cmd.v_variables[column];
557           width += v->print.w;
558           if (strlen (v->name) > height)
559             height = strlen (v->name);
560         }
561       
562       /* If it fit then we need to determine how many labels can be
563          written horizontally. */
564       if (width <= max_width && height <= SHORT_NAME_LEN)
565         {
566 #ifndef NDEBUG
567           prc->n_vertical = SIZE_MAX;
568 #endif
569           for (column = cmd.n_variables; column-- != 0; )
570             {
571               struct variable *v = cmd.v_variables[column];
572               int trial_width = (width - v->print.w
573                                  + max (v->print.w, (int) strlen (v->name)));
574               
575               if (trial_width > max_width)
576                 {
577                   prc->n_vertical = column + 1;
578                   break;
579                 }
580               width = trial_width;
581             }
582           assert (prc->n_vertical != SIZE_MAX);
583
584           prc->n_vertical = cmd.n_variables;
585           /* Finally determine the length of the headers. */
586           for (prc->header_rows = 0, column = 0;
587                column < prc->n_vertical;
588                column++)
589             prc->header_rows = max (prc->header_rows,
590                                     strlen (cmd.v_variables[column]->name));
591           prc->header_rows++;
592
593           d->class->text_set_font_by_name (d, "FIXED");
594           continue;
595         }
596
597       /* Otherwise use the ugly fallback listing format. */
598       prc->type = 1;
599       prc->header_rows = 0;
600
601       d->cp_y += d->font_height;
602       write_fallback_headers (d);
603       d->cp_y += d->font_height;
604       d->class->text_set_font_by_name (d, "FIXED");
605     }
606
607   line_buf = xmalloc (max (1022, largest_page_width) + 2);
608 }
609
610 static int
611 list_cases (struct ccase *c, void *aux UNUSED)
612 {
613   struct outp_driver *d;
614   
615   case_idx++;
616   if (case_idx < cmd.first || case_idx > cmd.last
617       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
618     return 1;
619
620   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
621     if (d->class->special == 0)
622       {
623         const struct list_ext *prc = d->prc;
624         const int max_width = n_chars_width (d);
625         int column;
626         int x = 0;
627
628         if (!prc->header_rows)
629           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
630       
631         for (column = 0; column < cmd.n_variables; column++)
632           {
633             struct variable *v = cmd.v_variables[column];
634             int width;
635
636             if (prc->type == 0 && column >= prc->n_vertical)
637               width = max ((int) strlen (v->name), v->print.w);
638             else
639               width = v->print.w;
640
641             if (width + x > max_width && x != 0)
642               {
643                 if (!n_lines_remaining (d))
644                   {
645                     outp_eject_page (d);
646                     write_header (d);
647                   }
648               
649                 line_buf[x] = 0;
650                 write_line (d, line_buf);
651
652                 x = 0;
653                 if (!prc->header_rows)
654                   x = nsprintf (line_buf, "%8s: ", v->name);
655               }
656
657             if (width > v->print.w)
658               {
659                 memset(&line_buf[x], ' ', width - v->print.w);
660                 x += width - v->print.w;
661               }
662
663             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
664               data_out (&line_buf[x], &v->print, case_data (c, v->fv));
665             else 
666               {
667                 union value case_idx_value;
668                 case_idx_value.f = case_idx;
669                 data_out (&line_buf[x], &v->print, &case_idx_value); 
670               }
671             x += v->print.w;
672           
673             line_buf[x++] = ' ';
674           }
675       
676         if (!n_lines_remaining (d))
677           {
678             outp_eject_page (d);
679             write_header (d);
680           }
681               
682         line_buf[x] = 0;
683         write_line (d, line_buf);
684       }
685     else if (d->class == &html_class)
686       {
687         struct html_driver_ext *x = d->ext;
688         int column;
689
690         fputs ("  <TR>\n", x->file.file);
691         
692         for (column = 0; column < cmd.n_variables; column++)
693           {
694             struct variable *v = cmd.v_variables[column];
695             char buf[41];
696             
697             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
698               data_out (buf, &v->print, case_data (c, v->fv));
699             else 
700               {
701                 union value case_idx_value;
702                 case_idx_value.f = case_idx;
703                 data_out (buf, &v->print, &case_idx_value); 
704               }
705             buf[v->print.w] = 0;
706
707             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
708                      &buf[strspn (buf, " ")]);
709           }
710           
711         fputs ("  </TR>\n", x->file.file);
712       }
713     else
714       assert (0);
715
716   return 1;
717 }
718
719 /* 
720    Local Variables:
721    mode: c
722    End:
723 */