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