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