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