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