Changed include paths to be explicitly specified in the #include directive.
[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.options = OUTP_T_JUST_LEFT;
119   ls_init (&text.s, s, strlen (s));
120   text.x = d->cp_x;
121   text.y = d->cp_y;
122   d->class->text_draw (d, &text);
123   d->cp_x = 0;
124   d->cp_y += d->font_height;
125 }
126     
127 /* Parses and executes the LIST procedure. */
128 int
129 cmd_list (void)
130 {
131   struct variable casenum_var;
132   bool ok;
133
134   if (!parse_list (&cmd))
135     return CMD_FAILURE;
136   
137   /* Fill in defaults. */
138   if (cmd.step == NOT_LONG)
139     cmd.step = 1;
140   if (cmd.first == NOT_LONG)
141     cmd.first = 1;
142   if (cmd.last == NOT_LONG)
143     cmd.last = LONG_MAX;
144   if (!cmd.sbc_variables)
145     dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
146                    (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
147   if (cmd.n_variables == 0)
148     {
149       msg (SE, _("No variables specified."));
150       return CMD_FAILURE;
151     }
152
153   /* Verify arguments. */
154   if (cmd.first > cmd.last)
155     {
156       int t;
157       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
158            "specified.  The values will be swapped."), cmd.first, cmd.last);
159       t = cmd.first;
160       cmd.first = cmd.last;
161       cmd.last = t;
162     }
163   if (cmd.first < 1)
164     {
165       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
166            "being reset to 1."), cmd.first);
167       cmd.first = 1;
168     }
169   if (cmd.last < 1)
170     {
171       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
172            "being reset to 1."), cmd.last);
173       cmd.last = 1;
174     }
175   if (cmd.step < 1)
176     {
177       msg (SW, _("The step value %ld is less than 1.  The value is being "
178            "reset to 1."), cmd.step);
179       cmd.step = 1;
180     }
181
182   /* Weighting variable. */
183   if (cmd.weight == LST_WEIGHT)
184     {
185       if (dict_get_weight (default_dict) != NULL)
186         {
187           size_t i;
188
189           for (i = 0; i < cmd.n_variables; i++)
190             if (cmd.v_variables[i] == dict_get_weight (default_dict))
191               break;
192           if (i >= cmd.n_variables)
193             {
194               /* Add the weight variable to the end of the variable list. */
195               cmd.n_variables++;
196               cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
197                                            sizeof *cmd.v_variables);
198               cmd.v_variables[cmd.n_variables - 1]
199                 = dict_get_weight (default_dict);
200             }
201         }
202       else
203         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
204     }
205
206   /* Case number. */
207   if (cmd.numbering == LST_NUMBERED)
208     {
209       /* Initialize the case-number variable. */
210       strcpy (casenum_var.name, "Case#");
211       casenum_var.type = NUMERIC;
212       casenum_var.fv = -1;
213       casenum_var.print = make_output_format (FMT_F,
214                                               (cmd.last == LONG_MAX
215                                                ? 5 : intlog10 (cmd.last)), 0);
216
217       /* Add the weight variable at the beginning of the variable list. */
218       cmd.n_variables++;
219       cmd.v_variables = xnrealloc (cmd.v_variables,
220                                    cmd.n_variables, sizeof *cmd.v_variables);
221       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
222                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
223       cmd.v_variables[0] = &casenum_var;
224     }
225
226   determine_layout ();
227
228   case_idx = 0;
229   ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
230   free (line_buf);
231
232   clean_up ();
233
234   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
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             size_t 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
276         assert (0);
277     }
278 }
279
280 /* Writes the headers.  Some of them might be vertical; most are
281    probably horizontal. */
282 static void
283 write_header (struct outp_driver *d)
284 {
285   struct list_ext *prc = d->prc;
286
287   if (!prc->header_rows)
288     return;
289   
290   if (n_lines_remaining (d) < prc->header_rows + 1)
291     {
292       outp_eject_page (d);
293       assert (n_lines_remaining (d) >= prc->header_rows + 1);
294     }
295
296   /* Design the header. */
297   if (!prc->header)
298     {
299       size_t i;
300       size_t x;
301       
302       /* Allocate, initialize header. */
303       prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
304       {
305         int w = n_chars_width (d);
306         for (i = 0; i < prc->header_rows; i++)
307           {
308             prc->header[i] = xmalloc (w + 1);
309             memset (prc->header[i], ' ', w);
310           }
311       }
312
313       /* Put in vertical names. */
314       for (i = x = 0; i < prc->n_vertical; i++)
315         {
316           struct variable *v = cmd.v_variables[i];
317           size_t j;
318
319           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
320           x += v->print.w - 1;
321           for (j = 0; j < strlen (v->name); j++)
322             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
323           x += 2;
324         }
325
326       /* Put in horizontal names. */
327       for (; i < cmd.n_variables; i++)
328         {
329           struct variable *v = cmd.v_variables[i];
330           
331           memset (&prc->header[prc->header_rows - 1][x], '-',
332                   max (v->print.w, (int) strlen (v->name)));
333           if ((int) strlen (v->name) < v->print.w)
334             x += v->print.w - strlen (v->name);
335           memcpy (&prc->header[0][x], v->name, strlen (v->name));
336           x += strlen (v->name) + 1;
337         }
338
339       /* Add null bytes. */
340       for (i = 0; i < prc->header_rows; i++)
341         {
342           for (x = n_chars_width (d); x >= 1; x--)
343             if (prc->header[i][x - 1] != ' ')
344               {
345                 prc->header[i][x] = 0;
346                 break;
347               }
348           assert (x);
349         }
350     }
351
352   /* Write out the header, in back-to-front order except for the last line. */
353   if (prc->header_rows >= 2) 
354     {
355       size_t i;
356         
357       for (i = prc->header_rows - 1; i-- != 0; )
358         write_line (d, prc->header[i]); 
359     }
360   write_line (d, prc->header[prc->header_rows - 1]);
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         size_t 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
396       assert (0);
397   
398   free (cmd.v_variables);
399 }
400
401 /* Writes string STRING at the current position.  If the text would
402    fall off the side of the page, then advance to the next line,
403    indenting by amount INDENT. */
404 static void
405 write_varname (struct outp_driver *d, char *string, int indent)
406 {
407   struct outp_text text;
408
409   text.options = OUTP_T_JUST_LEFT;
410   ls_init (&text.s, string, strlen (string));
411   d->class->text_metrics (d, &text);
412   
413   if (d->cp_x + text.h > d->width)
414     {
415       d->cp_y += d->font_height;
416       if (d->cp_y + d->font_height > d->length)
417         outp_eject_page (d);
418       d->cp_x = indent;
419     }
420
421   text.x = d->cp_x;
422   text.y = d->cp_y;
423   d->class->text_draw (d, &text);
424   d->cp_x += text.h;
425 }
426
427 /* When we can't fit all the values across the page, we write out all
428    the variable names just once.  This is where we do it. */
429 static void
430 write_fallback_headers (struct outp_driver *d)
431 {
432   const int max_width = n_chars_width(d) - 10;
433   
434   int index = 0;
435   int width = 0;
436   int line_number = 0;
437
438   const char *Line = _("Line");
439   char *leader = local_alloc (strlen (Line)
440                               + INT_STRLEN_BOUND (line_number) + 1 + 1);
441       
442   while (index < cmd.n_variables)
443     {
444       struct outp_text text;
445
446       /* Ensure that there is enough room for a line of text. */
447       if (d->cp_y + d->font_height > d->length)
448         outp_eject_page (d);
449       
450       /* The leader is a string like `Line 1: '.  Write the leader. */
451       sprintf(leader, "%s %d:", Line, ++line_number);
452       text.options = OUTP_T_JUST_LEFT;
453       ls_init (&text.s, leader, strlen (leader));
454       text.x = 0;
455       text.y = d->cp_y;
456       d->class->text_draw (d, &text);
457       d->cp_x = text.h;
458
459       goto entry;
460       do
461         {
462           width++;
463
464         entry:
465           {
466             int var_width = cmd.v_variables[index]->print.w;
467             if (width + var_width > max_width && width != 0)
468               {
469                 width = 0;
470                 d->cp_x = 0;
471                 d->cp_y += d->font_height;
472                 break;
473               }
474             width += var_width;
475           }
476           
477           {
478             char varname[10];
479             sprintf (varname, " %s", cmd.v_variables[index]->name);
480             write_varname (d, varname, text.h);
481           }
482         }
483       while (++index < cmd.n_variables);
484
485     }
486   d->cp_x = 0;
487   d->cp_y += d->font_height;
488   
489   local_free (leader);
490 }
491
492 /* There are three possible layouts for the LIST procedure:
493
494    1. If the values and their variables' name fit across the page,
495    then they are listed across the page in that way.
496
497    2. If the values can fit across the page, but not the variable
498    names, then as many variable names as necessary are printed
499    vertically to compensate.
500
501    3. If not even the values can fit across the page, the variable
502    names are listed just once, at the beginning, in a compact format,
503    and the values are listed with a variable name label at the
504    beginning of each line for easier reference.
505
506    This is complicated by the fact that we have to do all this for
507    every output driver, not just once.  */
508 static void
509 determine_layout (void)
510 {
511   struct outp_driver *d;
512   
513   /* This is the largest page width of any driver, so we can tell what
514      size buffer to allocate. */
515   int largest_page_width = 0;
516   
517   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
518     {
519       size_t column;    /* Current column. */
520       int width;        /* Accumulated width. */
521       int height;       /* Height of vertical names. */
522       int max_width;    /* Page width. */
523
524       struct list_ext *prc;
525
526       if (d->class == &html_class)
527         continue;
528       
529       assert (d->class->special == 0);
530
531       if (!d->page_open)
532         d->class->open_page (d);
533       
534       max_width = n_chars_width (d);
535       largest_page_width = max (largest_page_width, max_width);
536
537       prc = d->prc = xmalloc (sizeof *prc);
538       prc->type = 0;
539       prc->n_vertical = 0;
540       prc->header = NULL;
541
542       /* Try layout #1. */
543       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
544         {
545           struct variable *v = cmd.v_variables[column];
546           width += max (v->print.w, (int) strlen (v->name));
547         }
548       if (width <= max_width)
549         {
550           prc->header_rows = 2;
551           d->class->text_set_font_by_name (d, "FIXED");
552           continue;
553         }
554
555       /* Try layout #2. */
556       for (width = cmd.n_variables - 1, height = 0, column = 0;
557            column < cmd.n_variables && width <= max_width;
558            column++) 
559         {
560           struct variable *v = cmd.v_variables[column];
561           width += v->print.w;
562           if (strlen (v->name) > height)
563             height = strlen (v->name);
564         }
565       
566       /* If it fit then we need to determine how many labels can be
567          written horizontally. */
568       if (width <= max_width && height <= SHORT_NAME_LEN)
569         {
570 #ifndef NDEBUG
571           prc->n_vertical = SIZE_MAX;
572 #endif
573           for (column = cmd.n_variables; column-- != 0; )
574             {
575               struct variable *v = cmd.v_variables[column];
576               int trial_width = (width - v->print.w
577                                  + max (v->print.w, (int) strlen (v->name)));
578               
579               if (trial_width > max_width)
580                 {
581                   prc->n_vertical = column + 1;
582                   break;
583                 }
584               width = trial_width;
585             }
586           assert (prc->n_vertical != SIZE_MAX);
587
588           prc->n_vertical = cmd.n_variables;
589           /* Finally determine the length of the headers. */
590           for (prc->header_rows = 0, column = 0;
591                column < prc->n_vertical;
592                column++)
593             prc->header_rows = max (prc->header_rows,
594                                     strlen (cmd.v_variables[column]->name));
595           prc->header_rows++;
596
597           d->class->text_set_font_by_name (d, "FIXED");
598           continue;
599         }
600
601       /* Otherwise use the ugly fallback listing format. */
602       prc->type = 1;
603       prc->header_rows = 0;
604
605       d->cp_y += d->font_height;
606       write_fallback_headers (d);
607       d->cp_y += d->font_height;
608       d->class->text_set_font_by_name (d, "FIXED");
609     }
610
611   line_buf = xmalloc (max (1022, largest_page_width) + 2);
612 }
613
614 /* Writes case C to output. */
615 static bool
616 list_cases (struct ccase *c, void *aux UNUSED)
617 {
618   struct outp_driver *d;
619   
620   case_idx++;
621   if (case_idx < cmd.first || case_idx > cmd.last
622       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
623     return true;
624
625   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
626     if (d->class->special == 0)
627       {
628         const struct list_ext *prc = d->prc;
629         const int max_width = n_chars_width (d);
630         int column;
631         int x = 0;
632
633         if (!prc->header_rows)
634           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
635       
636         for (column = 0; column < cmd.n_variables; column++)
637           {
638             struct variable *v = cmd.v_variables[column];
639             int width;
640
641             if (prc->type == 0 && column >= prc->n_vertical)
642               width = max ((int) strlen (v->name), v->print.w);
643             else
644               width = v->print.w;
645
646             if (width + x > max_width && x != 0)
647               {
648                 if (!n_lines_remaining (d))
649                   {
650                     outp_eject_page (d);
651                     write_header (d);
652                   }
653               
654                 line_buf[x] = 0;
655                 write_line (d, line_buf);
656
657                 x = 0;
658                 if (!prc->header_rows)
659                   x = nsprintf (line_buf, "%8s: ", v->name);
660               }
661
662             if (width > v->print.w)
663               {
664                 memset(&line_buf[x], ' ', width - v->print.w);
665                 x += width - v->print.w;
666               }
667
668             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
669               data_out (&line_buf[x], &v->print, case_data (c, v->fv));
670             else 
671               {
672                 union value case_idx_value;
673                 case_idx_value.f = case_idx;
674                 data_out (&line_buf[x], &v->print, &case_idx_value); 
675               }
676             x += v->print.w;
677           
678             line_buf[x++] = ' ';
679           }
680       
681         if (!n_lines_remaining (d))
682           {
683             outp_eject_page (d);
684             write_header (d);
685           }
686               
687         line_buf[x] = 0;
688         write_line (d, line_buf);
689       }
690     else if (d->class == &html_class)
691       {
692         struct html_driver_ext *x = d->ext;
693         int column;
694
695         fputs ("  <TR>\n", x->file.file);
696         
697         for (column = 0; column < cmd.n_variables; column++)
698           {
699             struct variable *v = cmd.v_variables[column];
700             char buf[41];
701             
702             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
703               data_out (buf, &v->print, case_data (c, v->fv));
704             else 
705               {
706                 union value case_idx_value;
707                 case_idx_value.f = case_idx;
708                 data_out (buf, &v->print, &case_idx_value); 
709               }
710             buf[v->print.w] = 0;
711
712             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
713                      &buf[strspn (buf, " ")]);
714           }
715           
716         fputs ("  </TR>\n", x->file.file);
717       }
718     else
719       assert (0);
720
721   return true;
722 }
723
724 /* 
725    Local Variables:
726    mode: c
727    End:
728 */