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