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