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