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