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