Continue reforming procedure execution. In this phase, get rid of the
[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
442       /* Ensure that there is enough room for a line of text. */
443       if (d->cp_y + d->font_height > d->length)
444         outp_eject_page (d);
445       
446       /* The leader is a string like `Line 1: '.  Write the leader. */
447       sprintf (leader, "%s %d:", Line, ++line_number);
448       text.font = OUTP_FIXED;
449       text.justification = OUTP_LEFT;
450       ls_init (&text.string, leader, strlen (leader));
451       text.x = 0;
452       text.y = d->cp_y;
453       text.h = text.v = INT_MAX;
454       d->class->text_draw (d, &text);
455       d->class->text_metrics (d, &text, &d->cp_x, NULL);
456
457       goto entry;
458       do
459         {
460           width++;
461
462         entry:
463           {
464             int var_width = cmd.v_variables[index]->print.w;
465             if (width + var_width > max_width && width != 0)
466               {
467                 width = 0;
468                 d->cp_x = 0;
469                 d->cp_y += d->font_height;
470                 break;
471               }
472             width += var_width;
473           }
474           
475           {
476             char varname[10];
477             sprintf (varname, " %s", cmd.v_variables[index]->name);
478             write_varname (d, varname, text.h);
479           }
480         }
481       while (++index < cmd.n_variables);
482
483     }
484   d->cp_x = 0;
485   d->cp_y += d->font_height;
486   
487   local_free (leader);
488 }
489
490 /* There are three possible layouts for the LIST procedure:
491
492    1. If the values and their variables' name fit across the page,
493    then they are listed across the page in that way.
494
495    2. If the values can fit across the page, but not the variable
496    names, then as many variable names as necessary are printed
497    vertically to compensate.
498
499    3. If not even the values can fit across the page, the variable
500    names are listed just once, at the beginning, in a compact format,
501    and the values are listed with a variable name label at the
502    beginning of each line for easier reference.
503
504    This is complicated by the fact that we have to do all this for
505    every output driver, not just once.  */
506 static void
507 determine_layout (void)
508 {
509   struct outp_driver *d;
510   
511   /* This is the largest page width of any driver, so we can tell what
512      size buffer to allocate. */
513   int largest_page_width = 0;
514   
515   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
516     {
517       size_t column;    /* Current column. */
518       int width;        /* Accumulated width. */
519       int height;       /* Height of vertical names. */
520       int max_width;    /* Page width. */
521
522       struct list_ext *prc;
523
524       if (d->class == &html_class)
525         continue;
526       
527       assert (d->class->special == 0);
528
529       outp_open_page (d);
530       
531       max_width = n_chars_width (d);
532       largest_page_width = max (largest_page_width, max_width);
533
534       prc = d->prc = xmalloc (sizeof *prc);
535       prc->type = 0;
536       prc->n_vertical = 0;
537       prc->header = NULL;
538
539       /* Try layout #1. */
540       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
541         {
542           struct variable *v = cmd.v_variables[column];
543           width += max (v->print.w, (int) strlen (v->name));
544         }
545       if (width <= max_width)
546         {
547           prc->header_rows = 2;
548           continue;
549         }
550
551       /* Try layout #2. */
552       for (width = cmd.n_variables - 1, height = 0, column = 0;
553            column < cmd.n_variables && width <= max_width;
554            column++) 
555         {
556           struct variable *v = cmd.v_variables[column];
557           width += v->print.w;
558           if (strlen (v->name) > height)
559             height = strlen (v->name);
560         }
561       
562       /* If it fit then we need to determine how many labels can be
563          written horizontally. */
564       if (width <= max_width && height <= SHORT_NAME_LEN)
565         {
566 #ifndef NDEBUG
567           prc->n_vertical = SIZE_MAX;
568 #endif
569           for (column = cmd.n_variables; column-- != 0; )
570             {
571               struct variable *v = cmd.v_variables[column];
572               int trial_width = (width - v->print.w
573                                  + max (v->print.w, (int) strlen (v->name)));
574               
575               if (trial_width > max_width)
576                 {
577                   prc->n_vertical = column + 1;
578                   break;
579                 }
580               width = trial_width;
581             }
582           assert (prc->n_vertical != SIZE_MAX);
583
584           prc->n_vertical = cmd.n_variables;
585           /* Finally determine the length of the headers. */
586           for (prc->header_rows = 0, column = 0;
587                column < prc->n_vertical;
588                column++)
589             prc->header_rows = max (prc->header_rows,
590                                     strlen (cmd.v_variables[column]->name));
591           prc->header_rows++;
592           continue;
593         }
594
595       /* Otherwise use the ugly fallback listing format. */
596       prc->type = 1;
597       prc->header_rows = 0;
598
599       d->cp_y += d->font_height;
600       write_fallback_headers (d);
601       d->cp_y += d->font_height;
602     }
603
604   ds_init(&line_buffer, largest_page_width + 2);
605 }
606
607 /* Writes case C to output. */
608 static bool
609 list_cases (const struct ccase *c, void *aux UNUSED)
610 {
611   struct outp_driver *d;
612   
613   case_idx++;
614   if (case_idx < cmd.first || case_idx > cmd.last
615       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
616     return true;
617
618   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
619     if (d->class->special == 0)
620       {
621         const struct list_ext *prc = d->prc;
622         const int max_width = n_chars_width (d);
623         int column;
624
625         if (!prc->header_rows)
626           {
627             ds_printf(&line_buffer, "%8s: ", cmd.v_variables[0]->name);
628           }
629         
630       
631         for (column = 0; column < cmd.n_variables; column++)
632           {
633             struct variable *v = cmd.v_variables[column];
634             int width;
635
636             if (prc->type == 0 && column >= prc->n_vertical)
637               width = max ((int) strlen (v->name), v->print.w);
638             else
639               width = v->print.w;
640
641             if (width + ds_length(&line_buffer) > max_width && 
642                 ds_length(&line_buffer) != 0)
643               {
644                 if (!n_lines_remaining (d))
645                   {
646                     outp_eject_page (d);
647                     write_header (d);
648                   }
649               
650                 write_line (d, ds_c_str(&line_buffer));
651                 ds_clear(&line_buffer);
652
653                 if (!prc->header_rows)
654                   {
655                     ds_printf (&line_buffer, "%8s: ", v->name);
656                   }
657               }
658
659             if (width > v->print.w)
660               {
661                 ds_putc_multiple(&line_buffer, ' ', width - v->print.w);
662               }
663
664             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
665               {
666                 data_out (ds_append_uninit(&line_buffer, v->print.w),
667                           &v->print, case_data (c, v->fv));
668               }
669             else 
670               {
671                 union value case_idx_value;
672                 case_idx_value.f = case_idx;
673                 data_out (ds_append_uninit(&line_buffer,v->print.w), 
674                           &v->print,   &case_idx_value); 
675               }
676
677             ds_putc(&line_buffer, ' ');
678           }
679       
680         if (!n_lines_remaining (d))
681           {
682             outp_eject_page (d);
683             write_header (d);
684           }
685               
686         write_line (d, ds_c_str(&line_buffer));
687         ds_clear(&line_buffer);
688       }
689     else if (d->class == &html_class)
690       {
691         struct html_driver_ext *x = d->ext;
692         int column;
693
694         fputs ("  <TR>\n", x->file);
695         
696         for (column = 0; column < cmd.n_variables; column++)
697           {
698             struct variable *v = cmd.v_variables[column];
699             char buf[256];
700             struct fixed_string s;
701             
702             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
703               data_out (buf, &v->print, case_data (c, v->fv));
704             else 
705               {
706                 union value case_idx_value;
707                 case_idx_value.f = case_idx;
708                 data_out (buf, &v->print, &case_idx_value); 
709               }
710
711             ls_init (&s, buf, v->print.w);
712             fputs ("    <TD>", x->file);
713             html_put_cell_contents (d, TAB_FIX, &s);
714             fputs ("</TD>\n", x->file);
715           }
716           
717         fputs ("  </TR>\n", x->file);
718       }
719     else
720       assert (0);
721
722   return true;
723 }
724
725 /* 
726    Local Variables:
727    mode: c
728    End:
729 */