We've had a mix of min, max from libpspp/misc.h and MIN, MAX from
[pspp-builds.git] / src / language / data-io / list.q
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000, 2006 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
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include "intprops.h"
26 #include "size_max.h"
27 #include <data/case.h>
28 #include <data/dictionary.h>
29 #include <data/data-out.h>
30 #include <data/format.h>
31 #include <data/procedure.h>
32 #include <data/variable.h>
33 #include <language/command.h>
34 #include <language/dictionary/split-file.h>
35 #include <language/lexer/lexer.h>
36 #include <libpspp/alloc.h>
37 #include <libpspp/compiler.h>
38 #include <libpspp/magic.h>
39 #include <libpspp/message.h>
40 #include <libpspp/message.h>
41 #include <libpspp/misc.h>
42 #include <output/htmlP.h>
43 #include <output/manager.h>
44 #include <output/output.h>
45 #include <output/table.h>
46
47 #include "minmax.h"
48
49 #include "gettext.h"
50 #define _(msgid) gettext (msgid)
51
52 /* (headers) */
53
54 /* (specification)
55    list (lst_):
56      *variables=varlist("PV_NO_SCRATCH");
57      cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
58      +format=numbering:numbered/!unnumbered,
59              wrap:!wrap/single,
60              weight:weight/!noweight.
61 */
62 /* (declarations) */
63 /* (functions) */
64
65 /* Layout for one output driver. */
66 struct list_ext
67   {
68     int type;           /* 0=Values and labels fit across the page. */
69     size_t n_vertical;  /* Number of labels to list vertically. */
70     size_t header_rows; /* Number of header rows. */
71     char **header;      /* The header itself. */
72   };
73
74 /* Parsed command. */
75 static struct cmd_list cmd;
76
77 /* Current case number. */
78 static int case_idx;
79
80 /* Line buffer. */
81 static struct string line_buffer;
82
83 /* TTY-style output functions. */
84 static unsigned n_lines_remaining (struct outp_driver *d);
85 static unsigned n_chars_width (struct outp_driver *d);
86 static void write_line (struct outp_driver *d, const char *s);
87
88 /* Other functions. */
89 static bool list_cases (const struct ccase *, void *, const struct dataset *);
90 static void determine_layout (void);
91 static void clean_up (void);
92 static void write_header (struct outp_driver *);
93 static void write_all_headers (const struct ccase *, void *, const struct dataset*);
94
95 /* Returns the number of text lines that can fit on the remainder of
96    the page. */
97 static inline unsigned
98 n_lines_remaining (struct outp_driver *d)
99 {
100   int diff;
101
102   diff = d->length - d->cp_y;
103   return (diff > 0) ? (diff / d->font_height) : 0;
104 }
105
106 /* Returns the number of fixed-width character that can fit across the
107    page. */
108 static inline unsigned
109 n_chars_width (struct outp_driver *d)
110 {
111   return d->width / d->fixed_width;
112 }
113
114 /* Writes the line S at the current position and advances to the next
115    line.  */
116 static void
117 write_line (struct outp_driver *d, const char *s)
118 {
119   struct outp_text text;
120   
121   assert (d->cp_y + d->font_height <= d->length);
122   text.font = OUTP_FIXED;
123   text.justification = OUTP_LEFT;
124   text.string = ss_cstr (s);
125   text.x = d->cp_x;
126   text.y = d->cp_y;
127   text.h = text.v = INT_MAX;
128   d->class->text_draw (d, &text);
129   d->cp_x = 0;
130   d->cp_y += d->font_height;
131 }
132     
133 /* Parses and executes the LIST procedure. */
134 int
135 cmd_list (struct lexer *lexer, struct dataset *ds)
136 {
137   struct variable casenum_var;
138   bool ok;
139
140   if (!parse_list (lexer, ds, &cmd, NULL))
141     return CMD_FAILURE;
142   
143   /* Fill in defaults. */
144   if (cmd.step == NOT_LONG)
145     cmd.step = 1;
146   if (cmd.first == NOT_LONG)
147     cmd.first = 1;
148   if (cmd.last == NOT_LONG)
149     cmd.last = LONG_MAX;
150   if (!cmd.sbc_variables)
151     dict_get_vars (dataset_dict (ds), &cmd.v_variables, &cmd.n_variables,
152                    (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
153   if (cmd.n_variables == 0)
154     {
155       msg (SE, _("No variables specified."));
156       return CMD_FAILURE;
157     }
158
159   /* Verify arguments. */
160   if (cmd.first > cmd.last)
161     {
162       int t;
163       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
164            "specified.  The values will be swapped."), cmd.first, cmd.last);
165       t = cmd.first;
166       cmd.first = cmd.last;
167       cmd.last = t;
168     }
169   if (cmd.first < 1)
170     {
171       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
172            "being reset to 1."), cmd.first);
173       cmd.first = 1;
174     }
175   if (cmd.last < 1)
176     {
177       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
178            "being reset to 1."), cmd.last);
179       cmd.last = 1;
180     }
181   if (cmd.step < 1)
182     {
183       msg (SW, _("The step value %ld is less than 1.  The value is being "
184            "reset to 1."), cmd.step);
185       cmd.step = 1;
186     }
187
188   /* Weighting variable. */
189   if (cmd.weight == LST_WEIGHT)
190     {
191       if (dict_get_weight (dataset_dict (ds)) != NULL)
192         {
193           size_t i;
194
195           for (i = 0; i < cmd.n_variables; i++)
196             if (cmd.v_variables[i] == dict_get_weight (dataset_dict (ds)))
197               break;
198           if (i >= cmd.n_variables)
199             {
200               /* Add the weight variable to the end of the variable list. */
201               cmd.n_variables++;
202               cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
203                                            sizeof *cmd.v_variables);
204               cmd.v_variables[cmd.n_variables - 1]
205                 = dict_get_weight (dataset_dict (ds));
206             }
207         }
208       else
209         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
210     }
211
212   /* Case number. */
213   if (cmd.numbering == LST_NUMBERED)
214     {
215       /* Initialize the case-number variable. */
216       strcpy (casenum_var.name, "Case#");
217       casenum_var.type = NUMERIC;
218       casenum_var.fv = -1;
219       casenum_var.print = fmt_for_output (FMT_F,
220                                           (cmd.last == LONG_MAX
221                                            ? 5 : intlog10 (cmd.last)), 0);
222
223       /* Add the weight variable at the beginning of the variable list. */
224       cmd.n_variables++;
225       cmd.v_variables = xnrealloc (cmd.v_variables,
226                                    cmd.n_variables, sizeof *cmd.v_variables);
227       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
228                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
229       cmd.v_variables[0] = &casenum_var;
230     }
231
232   determine_layout ();
233
234   case_idx = 0;
235   ok = procedure_with_splits (ds, write_all_headers, list_cases, NULL, NULL);
236   ds_destroy(&line_buffer);
237
238   clean_up ();
239
240   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
241 }
242
243 /* Writes headers to all devices.  This is done at the beginning of
244    each SPLIT FILE group. */
245 static void
246 write_all_headers (const struct ccase *c, void *aux UNUSED, const struct dataset *ds)
247 {
248   struct outp_driver *d;
249
250   output_split_file_values (ds, c);
251   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
252     {
253       if (!d->class->special)
254         {
255           d->cp_y += d->font_height;            /* Blank line. */
256           write_header (d);
257         }
258       else if (d->class == &html_class)
259         {
260           struct html_driver_ext *x = d->ext;
261   
262           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file);
263           
264           {
265             size_t i;
266
267             for (i = 0; i < cmd.n_variables; i++)
268               fprintf (x->file, "    <TH><EM>%s</EM></TH>\n",
269                        cmd.v_variables[i]->name);
270           }
271
272           fputs ("  </TR>\n", x->file);
273         }
274       else
275         NOT_REACHED ();
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     else if (d->class == &html_class)
384       {
385         if (d->page_open)
386           {
387             struct html_driver_ext *x = d->ext;
388
389             fputs ("</TABLE>\n", x->file);
390           }
391       }
392     else
393       NOT_REACHED ();
394   
395   free (cmd.v_variables);
396 }
397
398 /* Writes string STRING at the current position.  If the text would
399    fall off the side of the page, then advance to the next line,
400    indenting by amount INDENT. */
401 static void
402 write_varname (struct outp_driver *d, char *string, int indent)
403 {
404   struct outp_text text;
405   int width;
406   
407   if (d->cp_x + outp_string_width (d, string, OUTP_FIXED) > d->width)
408     {
409       d->cp_y += d->font_height;
410       if (d->cp_y + d->font_height > d->length)
411         outp_eject_page (d);
412       d->cp_x = indent;
413     }
414
415   text.font = OUTP_FIXED;
416   text.justification = OUTP_LEFT;
417   text.string = ss_cstr (string);
418   text.x = d->cp_x;
419   text.y = d->cp_y;
420   text.h = text.v = INT_MAX;
421   d->class->text_draw (d, &text);
422   d->class->text_metrics (d, &text, &width, NULL);
423   d->cp_x += width;
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       int leader_width;
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.font = OUTP_FIXED;
453       text.justification = OUTP_LEFT;
454       text.string = ss_cstr (leader);
455       text.x = 0;
456       text.y = d->cp_y;
457       text.h = text.v = INT_MAX;
458       d->class->text_draw (d, &text);
459       d->class->text_metrics (d, &text, &leader_width, NULL);
460       d->cp_x = leader_width;
461
462       goto entry;
463       do
464         {
465           width++;
466
467         entry:
468           {
469             int var_width = cmd.v_variables[index]->print.w;
470             if (width + var_width > max_width && width != 0)
471               {
472                 width = 0;
473                 d->cp_x = 0;
474                 d->cp_y += d->font_height;
475                 break;
476               }
477             width += var_width;
478           }
479           
480           {
481             char varname[LONG_NAME_LEN + 2];
482             snprintf (varname, sizeof varname,
483                       " %s", cmd.v_variables[index]->name);
484             write_varname (d, varname, leader_width);
485           }
486         }
487       while (++index < cmd.n_variables);
488
489     }
490   d->cp_x = 0;
491   d->cp_y += d->font_height;
492   
493   local_free (leader);
494 }
495
496 /* There are three possible layouts for the LIST procedure:
497
498    1. If the values and their variables' name fit across the page,
499    then they are listed across the page in that way.
500
501    2. If the values can fit across the page, but not the variable
502    names, then as many variable names as necessary are printed
503    vertically to compensate.
504
505    3. If not even the values can fit across the page, the variable
506    names are listed just once, at the beginning, in a compact format,
507    and the values are listed with a variable name label at the
508    beginning of each line for easier reference.
509
510    This is complicated by the fact that we have to do all this for
511    every output driver, not just once.  */
512 static void
513 determine_layout (void)
514 {
515   struct outp_driver *d;
516   
517   /* This is the largest page width of any driver, so we can tell what
518      size buffer to allocate. */
519   int largest_page_width = 0;
520   
521   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
522     {
523       size_t column;    /* Current column. */
524       int width;        /* Accumulated width. */
525       int height;       /* Height of vertical names. */
526       int max_width;    /* Page width. */
527
528       struct list_ext *prc;
529
530       if (d->class == &html_class)
531         continue;
532       
533       assert (d->class->special == 0);
534
535       outp_open_page (d);
536       
537       max_width = n_chars_width (d);
538       largest_page_width = MAX (largest_page_width, max_width);
539
540       prc = d->prc = xmalloc (sizeof *prc);
541       prc->type = 0;
542       prc->n_vertical = 0;
543       prc->header = NULL;
544
545       /* Try layout #1. */
546       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
547         {
548           struct variable *v = cmd.v_variables[column];
549           width += MAX (v->print.w, (int) strlen (v->name));
550         }
551       if (width <= max_width)
552         {
553           prc->header_rows = 2;
554           continue;
555         }
556
557       /* Try layout #2. */
558       for (width = cmd.n_variables - 1, height = 0, column = 0;
559            column < cmd.n_variables && width <= max_width;
560            column++) 
561         {
562           struct variable *v = cmd.v_variables[column];
563           width += v->print.w;
564           if (strlen (v->name) > height)
565             height = strlen (v->name);
566         }
567       
568       /* If it fit then we need to determine how many labels can be
569          written horizontally. */
570       if (width <= max_width && height <= SHORT_NAME_LEN)
571         {
572 #ifndef NDEBUG
573           prc->n_vertical = SIZE_MAX;
574 #endif
575           for (column = cmd.n_variables; column-- != 0; )
576             {
577               struct variable *v = cmd.v_variables[column];
578               int trial_width = (width - v->print.w
579                                  + MAX (v->print.w, (int) strlen (v->name)));
580               
581               if (trial_width > max_width)
582                 {
583                   prc->n_vertical = column + 1;
584                   break;
585                 }
586               width = trial_width;
587             }
588           assert (prc->n_vertical != SIZE_MAX);
589
590           prc->n_vertical = cmd.n_variables;
591           /* Finally determine the length of the headers. */
592           for (prc->header_rows = 0, column = 0;
593                column < prc->n_vertical;
594                column++)
595             prc->header_rows = MAX (prc->header_rows,
596                                     strlen (cmd.v_variables[column]->name));
597           prc->header_rows++;
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     }
609
610   ds_init_empty (&line_buffer);
611 }
612
613 /* Writes case C to output. */
614 static bool
615 list_cases (const struct ccase *c, void *aux UNUSED, const struct dataset *ds 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
631         if (!prc->header_rows)
632           {
633             ds_put_format(&line_buffer, "%8s: ", cmd.v_variables[0]->name);
634           }
635         
636       
637         for (column = 0; column < cmd.n_variables; column++)
638           {
639             struct variable *v = cmd.v_variables[column];
640             int width;
641
642             if (prc->type == 0 && column >= prc->n_vertical)
643               width = MAX ((int) strlen (v->name), v->print.w);
644             else
645               width = v->print.w;
646
647             if (width + ds_length(&line_buffer) > max_width && 
648                 ds_length(&line_buffer) != 0)
649               {
650                 if (!n_lines_remaining (d))
651                   {
652                     outp_eject_page (d);
653                     write_header (d);
654                   }
655               
656                 write_line (d, ds_cstr (&line_buffer));
657                 ds_clear(&line_buffer);
658
659                 if (!prc->header_rows)
660                   {
661                     ds_put_format (&line_buffer, "%8s: ", v->name);
662                   }
663               }
664
665             if (width > v->print.w)
666               {
667                 ds_put_char_multiple(&line_buffer, ' ', width - v->print.w);
668               }
669
670             if (fmt_is_string (v->print.type) || v->fv != -1)
671               {
672                 data_out (case_data (c, v->fv), &v->print,
673                           ds_put_uninit (&line_buffer, v->print.w));
674               }
675             else 
676               {
677                 union value case_idx_value;
678                 case_idx_value.f = case_idx;
679                 data_out (&case_idx_value, &v->print,
680                           ds_put_uninit (&line_buffer,v->print.w)); 
681               }
682
683             ds_put_char(&line_buffer, ' ');
684           }
685       
686         if (!n_lines_remaining (d))
687           {
688             outp_eject_page (d);
689             write_header (d);
690           }
691               
692         write_line (d, ds_cstr (&line_buffer));
693         ds_clear(&line_buffer);
694       }
695     else if (d->class == &html_class)
696       {
697         struct html_driver_ext *x = d->ext;
698         int column;
699
700         fputs ("  <TR>\n", x->file);
701         
702         for (column = 0; column < cmd.n_variables; column++)
703           {
704             struct variable *v = cmd.v_variables[column];
705             char buf[256];
706             
707             if (fmt_is_string (v->print.type) || v->fv != -1)
708               data_out (case_data (c, v->fv), &v->print, buf);
709             else 
710               {
711                 union value case_idx_value;
712                 case_idx_value.f = case_idx;
713                 data_out (&case_idx_value, &v->print, buf);
714               }
715
716             fputs ("    <TD>", x->file);
717             html_put_cell_contents (d, TAB_FIX, ss_buffer (buf, v->print.w));
718             fputs ("</TD>\n", x->file);
719           }
720           
721         fputs ("  </TR>\n", x->file);
722       }
723     else
724       NOT_REACHED ();
725
726   return true;
727 }
728
729 /* 
730    Local Variables:
731    mode: c
732    End:
733 */