Get rid of src/libpspp/debug-print.h and all its users. (There were
[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 /* (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.font = OUTP_FIXED;
117   text.justification = OUTP_LEFT;
118   ls_init (&text.string, s, strlen (s));
119   text.x = d->cp_x;
120   text.y = d->cp_y;
121   text.h = text.v = INT_MAX;
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           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file);
256           
257           {
258             size_t i;
259
260             for (i = 0; i < cmd.n_variables; i++)
261               fprintf (x->file, "    <TH><EM>%s</EM></TH>\n",
262                        cmd.v_variables[i]->name);
263           }
264
265           fputs ("  </TR>\n", x->file);
266         }
267       else
268         assert (0);
269     }
270 }
271
272 /* Writes the headers.  Some of them might be vertical; most are
273    probably horizontal. */
274 static void
275 write_header (struct outp_driver *d)
276 {
277   struct list_ext *prc = d->prc;
278
279   if (!prc->header_rows)
280     return;
281   
282   if (n_lines_remaining (d) < prc->header_rows + 1)
283     {
284       outp_eject_page (d);
285       assert (n_lines_remaining (d) >= prc->header_rows + 1);
286     }
287
288   /* Design the header. */
289   if (!prc->header)
290     {
291       size_t i;
292       size_t x;
293       
294       /* Allocate, initialize header. */
295       prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
296       {
297         int w = n_chars_width (d);
298         for (i = 0; i < prc->header_rows; i++)
299           {
300             prc->header[i] = xmalloc (w + 1);
301             memset (prc->header[i], ' ', w);
302           }
303       }
304
305       /* Put in vertical names. */
306       for (i = x = 0; i < prc->n_vertical; i++)
307         {
308           struct variable *v = cmd.v_variables[i];
309           size_t j;
310
311           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
312           x += v->print.w - 1;
313           for (j = 0; j < strlen (v->name); j++)
314             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
315           x += 2;
316         }
317
318       /* Put in horizontal names. */
319       for (; i < cmd.n_variables; i++)
320         {
321           struct variable *v = cmd.v_variables[i];
322           
323           memset (&prc->header[prc->header_rows - 1][x], '-',
324                   max (v->print.w, (int) strlen (v->name)));
325           if ((int) strlen (v->name) < v->print.w)
326             x += v->print.w - strlen (v->name);
327           memcpy (&prc->header[0][x], v->name, strlen (v->name));
328           x += strlen (v->name) + 1;
329         }
330
331       /* Add null bytes. */
332       for (i = 0; i < prc->header_rows; i++)
333         {
334           for (x = n_chars_width (d); x >= 1; x--)
335             if (prc->header[i][x - 1] != ' ')
336               {
337                 prc->header[i][x] = 0;
338                 break;
339               }
340           assert (x);
341         }
342     }
343
344   /* Write out the header, in back-to-front order except for the last line. */
345   if (prc->header_rows >= 2) 
346     {
347       size_t i;
348         
349       for (i = prc->header_rows - 1; i-- != 0; )
350         write_line (d, prc->header[i]); 
351     }
352   write_line (d, prc->header[prc->header_rows - 1]);
353 }
354       
355   
356 /* Frees up all the memory we've allocated. */
357 static void
358 clean_up (void)
359 {
360   struct outp_driver *d;
361   
362   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
363     if (d->class->special == 0)
364       {
365         struct list_ext *prc = d->prc;
366         size_t i;
367
368         if (prc->header)
369           {
370             for (i = 0; i < prc->header_rows; i++)
371               free (prc->header[i]);
372             free (prc->header);
373           }
374         free (prc);
375       }
376     else if (d->class == &html_class)
377       {
378         if (d->page_open)
379           {
380             struct html_driver_ext *x = d->ext;
381
382             fputs ("</TABLE>\n", x->file);
383           }
384       }
385     else
386       assert (0);
387   
388   free (cmd.v_variables);
389 }
390
391 /* Writes string STRING at the current position.  If the text would
392    fall off the side of the page, then advance to the next line,
393    indenting by amount INDENT. */
394 static void
395 write_varname (struct outp_driver *d, char *string, int indent)
396 {
397   struct outp_text text;
398   int width;
399   
400   if (d->cp_x + outp_string_width (d, string, OUTP_FIXED) > d->width)
401     {
402       d->cp_y += d->font_height;
403       if (d->cp_y + d->font_height > d->length)
404         outp_eject_page (d);
405       d->cp_x = indent;
406     }
407
408   text.font = OUTP_FIXED;
409   text.justification = OUTP_LEFT;
410   ls_init (&text.string, string, strlen (string));
411   text.x = d->cp_x;
412   text.y = d->cp_y;
413   text.h = text.v = INT_MAX;
414   d->class->text_draw (d, &text);
415   d->class->text_metrics (d, &text, &width, NULL);
416   d->cp_x += width;
417 }
418
419 /* When we can't fit all the values across the page, we write out all
420    the variable names just once.  This is where we do it. */
421 static void
422 write_fallback_headers (struct outp_driver *d)
423 {
424   const int max_width = n_chars_width(d) - 10;
425   
426   int index = 0;
427   int width = 0;
428   int line_number = 0;
429
430   const char *Line = _("Line");
431   char *leader = local_alloc (strlen (Line)
432                               + INT_STRLEN_BOUND (line_number) + 1 + 1);
433       
434   while (index < cmd.n_variables)
435     {
436       struct outp_text text;
437
438       /* Ensure that there is enough room for a line of text. */
439       if (d->cp_y + d->font_height > d->length)
440         outp_eject_page (d);
441       
442       /* The leader is a string like `Line 1: '.  Write the leader. */
443       sprintf (leader, "%s %d:", Line, ++line_number);
444       text.font = OUTP_FIXED;
445       text.justification = OUTP_LEFT;
446       ls_init (&text.string, leader, strlen (leader));
447       text.x = 0;
448       text.y = d->cp_y;
449       text.h = text.v = INT_MAX;
450       d->class->text_draw (d, &text);
451       d->class->text_metrics (d, &text, &d->cp_x, NULL);
452
453       goto entry;
454       do
455         {
456           width++;
457
458         entry:
459           {
460             int var_width = cmd.v_variables[index]->print.w;
461             if (width + var_width > max_width && width != 0)
462               {
463                 width = 0;
464                 d->cp_x = 0;
465                 d->cp_y += d->font_height;
466                 break;
467               }
468             width += var_width;
469           }
470           
471           {
472             char varname[10];
473             sprintf (varname, " %s", cmd.v_variables[index]->name);
474             write_varname (d, varname, text.h);
475           }
476         }
477       while (++index < cmd.n_variables);
478
479     }
480   d->cp_x = 0;
481   d->cp_y += d->font_height;
482   
483   local_free (leader);
484 }
485
486 /* There are three possible layouts for the LIST procedure:
487
488    1. If the values and their variables' name fit across the page,
489    then they are listed across the page in that way.
490
491    2. If the values can fit across the page, but not the variable
492    names, then as many variable names as necessary are printed
493    vertically to compensate.
494
495    3. If not even the values can fit across the page, the variable
496    names are listed just once, at the beginning, in a compact format,
497    and the values are listed with a variable name label at the
498    beginning of each line for easier reference.
499
500    This is complicated by the fact that we have to do all this for
501    every output driver, not just once.  */
502 static void
503 determine_layout (void)
504 {
505   struct outp_driver *d;
506   
507   /* This is the largest page width of any driver, so we can tell what
508      size buffer to allocate. */
509   int largest_page_width = 0;
510   
511   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
512     {
513       size_t column;    /* Current column. */
514       int width;        /* Accumulated width. */
515       int height;       /* Height of vertical names. */
516       int max_width;    /* Page width. */
517
518       struct list_ext *prc;
519
520       if (d->class == &html_class)
521         continue;
522       
523       assert (d->class->special == 0);
524
525       outp_open_page (d);
526       
527       max_width = n_chars_width (d);
528       largest_page_width = max (largest_page_width, max_width);
529
530       prc = d->prc = xmalloc (sizeof *prc);
531       prc->type = 0;
532       prc->n_vertical = 0;
533       prc->header = NULL;
534
535       /* Try layout #1. */
536       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
537         {
538           struct variable *v = cmd.v_variables[column];
539           width += max (v->print.w, (int) strlen (v->name));
540         }
541       if (width <= max_width)
542         {
543           prc->header_rows = 2;
544           continue;
545         }
546
547       /* Try layout #2. */
548       for (width = cmd.n_variables - 1, height = 0, column = 0;
549            column < cmd.n_variables && width <= max_width;
550            column++) 
551         {
552           struct variable *v = cmd.v_variables[column];
553           width += v->print.w;
554           if (strlen (v->name) > height)
555             height = strlen (v->name);
556         }
557       
558       /* If it fit then we need to determine how many labels can be
559          written horizontally. */
560       if (width <= max_width && height <= SHORT_NAME_LEN)
561         {
562 #ifndef NDEBUG
563           prc->n_vertical = SIZE_MAX;
564 #endif
565           for (column = cmd.n_variables; column-- != 0; )
566             {
567               struct variable *v = cmd.v_variables[column];
568               int trial_width = (width - v->print.w
569                                  + max (v->print.w, (int) strlen (v->name)));
570               
571               if (trial_width > max_width)
572                 {
573                   prc->n_vertical = column + 1;
574                   break;
575                 }
576               width = trial_width;
577             }
578           assert (prc->n_vertical != SIZE_MAX);
579
580           prc->n_vertical = cmd.n_variables;
581           /* Finally determine the length of the headers. */
582           for (prc->header_rows = 0, column = 0;
583                column < prc->n_vertical;
584                column++)
585             prc->header_rows = max (prc->header_rows,
586                                     strlen (cmd.v_variables[column]->name));
587           prc->header_rows++;
588           continue;
589         }
590
591       /* Otherwise use the ugly fallback listing format. */
592       prc->type = 1;
593       prc->header_rows = 0;
594
595       d->cp_y += d->font_height;
596       write_fallback_headers (d);
597       d->cp_y += d->font_height;
598     }
599
600   line_buf = xmalloc (max (1022, largest_page_width) + 2);
601 }
602
603 /* Writes case C to output. */
604 static bool
605 list_cases (struct ccase *c, void *aux UNUSED)
606 {
607   struct outp_driver *d;
608   
609   case_idx++;
610   if (case_idx < cmd.first || case_idx > cmd.last
611       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
612     return true;
613
614   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
615     if (d->class->special == 0)
616       {
617         const struct list_ext *prc = d->prc;
618         const int max_width = n_chars_width (d);
619         int column;
620         int x = 0;
621
622         if (!prc->header_rows)
623           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
624       
625         for (column = 0; column < cmd.n_variables; column++)
626           {
627             struct variable *v = cmd.v_variables[column];
628             int width;
629
630             if (prc->type == 0 && column >= prc->n_vertical)
631               width = max ((int) strlen (v->name), v->print.w);
632             else
633               width = v->print.w;
634
635             if (width + x > max_width && x != 0)
636               {
637                 if (!n_lines_remaining (d))
638                   {
639                     outp_eject_page (d);
640                     write_header (d);
641                   }
642               
643                 line_buf[x] = 0;
644                 write_line (d, line_buf);
645
646                 x = 0;
647                 if (!prc->header_rows)
648                   x = nsprintf (line_buf, "%8s: ", v->name);
649               }
650
651             if (width > v->print.w)
652               {
653                 memset(&line_buf[x], ' ', width - v->print.w);
654                 x += width - v->print.w;
655               }
656
657             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
658               data_out (&line_buf[x], &v->print, case_data (c, v->fv));
659             else 
660               {
661                 union value case_idx_value;
662                 case_idx_value.f = case_idx;
663                 data_out (&line_buf[x], &v->print, &case_idx_value); 
664               }
665             x += v->print.w;
666           
667             line_buf[x++] = ' ';
668           }
669       
670         if (!n_lines_remaining (d))
671           {
672             outp_eject_page (d);
673             write_header (d);
674           }
675               
676         line_buf[x] = 0;
677         write_line (d, line_buf);
678       }
679     else if (d->class == &html_class)
680       {
681         struct html_driver_ext *x = d->ext;
682         int column;
683
684         fputs ("  <TR>\n", x->file);
685         
686         for (column = 0; column < cmd.n_variables; column++)
687           {
688             struct variable *v = cmd.v_variables[column];
689             char buf[256];
690             struct fixed_string s;
691             
692             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
693               data_out (buf, &v->print, case_data (c, v->fv));
694             else 
695               {
696                 union value case_idx_value;
697                 case_idx_value.f = case_idx;
698                 data_out (buf, &v->print, &case_idx_value); 
699               }
700
701             ls_init (&s, buf, v->print.w);
702             fputs ("    <TD>", x->file);
703             html_put_cell_contents (d, TAB_FIX, &s);
704             fputs ("</TD>\n", x->file);
705           }
706           
707         fputs ("  </TR>\n", x->file);
708       }
709     else
710       assert (0);
711
712   return true;
713 }
714
715 /* 
716    Local Variables:
717    mode: c
718    End:
719 */