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