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