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