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