1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 /* AIX requires this to be the first thing in the file. */
23 #define alloca __builtin_alloca
31 #ifndef alloca /* predefined by HP cc +Olibcalls */
55 /*#define DEBUGGING 1*/
56 #include "debug-print.h"
59 static void debug_print (void);
64 *variables=varlist("PV_NO_SCRATCH");
65 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
66 format=numbering:numbered/!unnumbered,
68 weight:weight/!noweight.
73 /* Layout for one output driver. */
76 int type; /* 0=Values and labels fit across the page. */
77 int n_vertical; /* Number of labels to list vertically. */
78 int header_rows; /* Number of header rows. */
79 char **header; /* The header itself. */
83 static struct cmd_list cmd;
85 /* Current case number. */
89 static char *line_buf;
91 /* TTY-style output functions. */
92 static int n_lines_remaining (struct outp_driver *d);
93 static int n_chars_width (struct outp_driver *d);
94 static void write_line (struct outp_driver *d, char *s);
96 /* Other functions. */
97 static int list_cases (struct ccase *);
98 static void determine_layout (void);
99 static void clean_up (void);
100 static void write_header (struct outp_driver *);
101 static void write_all_headers (void);
103 /* Returns the number of text lines that can fit on the remainder of
106 n_lines_remaining (struct outp_driver *d)
110 diff = d->length - d->cp_y;
111 return (diff > 0) ? (diff / d->font_height) : 0;
114 /* Returns the number of fixed-width character that can fit across the
117 n_chars_width (struct outp_driver *d)
119 return d->width / d->fixed_width;
122 /* Writes the line S at the current position and advances to the next
125 write_line (struct outp_driver *d, char *s)
127 struct outp_text text;
129 assert (d->cp_y + d->font_height <= d->length);
130 text.options = OUTP_T_JUST_LEFT;
131 ls_init (&text.s, s, strlen (s));
134 d->class->text_draw (d, &text);
136 d->cp_y += d->font_height;
139 /* Parses and executes the LIST procedure. */
143 struct variable casenum_var;
145 lex_match_id ("LIST");
146 if (!parse_list (&cmd))
149 /* Fill in defaults. */
150 if (cmd.step == NOT_LONG)
152 if (cmd.first == NOT_LONG)
154 if (cmd.last == NOT_LONG)
156 if (!cmd.sbc_variables)
157 fill_all_vars (&cmd.v_variables, &cmd.n_variables,
158 FV_NO_SYSTEM | FV_NO_SCRATCH);
159 if (cmd.n_variables == 0)
161 msg (SE, _("No variables specified."));
165 /* Verify arguments. */
166 if (cmd.first > cmd.last)
169 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
170 "specified. The values will be swapped."), cmd.first, cmd.last);
172 cmd.first = cmd.last;
177 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
178 "being reset to 1."), cmd.first);
183 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
184 "being reset to 1."), cmd.last);
189 msg (SW, _("The step value %ld is less than 1. The value is being "
190 "reset to 1."), cmd.step);
194 /* Weighting variable. */
195 if (cmd.weight == LST_WEIGHT)
197 update_weighting (&default_dict);
198 if (default_dict.weight_index != -1)
202 for (i = 0; i < cmd.n_variables; i++)
203 if (cmd.v_variables[i]->index == default_dict.weight_index)
205 if (i >= cmd.n_variables)
207 /* Add the weight variable to the end of the variable list. */
209 cmd.v_variables = xrealloc (cmd.v_variables,
211 * sizeof *cmd.v_variables));
212 cmd.v_variables[cmd.n_variables - 1]
213 = default_dict.var[default_dict.weight_index];
217 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
221 if (cmd.numbering == LST_NUMBERED)
223 /* Initialize the case-number variable. */
224 strcpy (casenum_var.name, "Case#");
225 casenum_var.type = NUMERIC;
227 casenum_var.print.type = FMT_F;
228 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
229 casenum_var.print.d = 0;
231 /* Add the weight variable at the beginning of the variable list. */
233 cmd.v_variables = xrealloc (cmd.v_variables,
234 cmd.n_variables * sizeof *cmd.v_variables);
235 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
236 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
237 cmd.v_variables[0] = &casenum_var;
241 /* Print out command. */
248 procedure (write_all_headers, list_cases, NULL);
256 /* Writes headers to all devices. This is done at the beginning of
257 each SPLIT FILE group. */
259 write_all_headers (void)
261 struct outp_driver *d;
263 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
265 if (!d->class->special)
267 d->cp_y += d->font_height; /* Blank line. */
270 else if (d->class == &html_class)
272 struct html_driver_ext *x = d->ext;
274 assert (d->driver_open && d->page_open);
275 if (x->sequence_no == 0 && !d->class->open_page (d))
277 msg (ME, _("Cannot open first page on HTML device %s."),
282 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
287 for (i = 0; i < cmd.n_variables; i++)
288 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
289 cmd.v_variables[i]->name);
292 fputs (" <TR>\n", x->file.file);
299 /* Writes the headers. Some of them might be vertical; most are
300 probably horizontal. */
302 write_header (struct outp_driver *d)
304 struct list_ext *prc = d->prc;
306 if (!prc->header_rows)
309 if (n_lines_remaining (d) < prc->header_rows + 1)
312 assert (n_lines_remaining (d) >= prc->header_rows + 1);
315 /* Design the header. */
320 /* Allocate, initialize header. */
321 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
323 int w = n_chars_width (d);
324 for (i = 0; i < prc->header_rows; i++)
326 prc->header[i] = xmalloc (w + 1);
327 memset (prc->header[i], ' ', w);
331 /* Put in vertical names. */
332 for (i = x = 0; i < prc->n_vertical; i++)
334 struct variable *v = cmd.v_variables[i];
337 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
339 for (j = 0; j < (int) strlen (v->name); j++)
340 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
344 /* Put in horizontal names. */
345 for (; i < cmd.n_variables; i++)
347 struct variable *v = cmd.v_variables[i];
349 memset (&prc->header[prc->header_rows - 1][x], '-',
350 max (v->print.w, (int) strlen (v->name)));
351 if ((int) strlen (v->name) < v->print.w)
352 x += v->print.w - strlen (v->name);
353 memcpy (&prc->header[0][x], v->name, strlen (v->name));
354 x += strlen (v->name) + 1;
357 /* Add null bytes. */
358 for (i = 0; i < prc->header_rows; i++)
360 for (x = n_chars_width (d); x >= 1; x--)
361 if (prc->header[i][x - 1] != ' ')
363 prc->header[i][x] = 0;
370 /* Write out the header, in back-to-front order except for the last line. */
374 for (i = prc->header_rows - 2; i >= 0; i--)
375 write_line (d, prc->header[i]);
376 write_line (d, prc->header[prc->header_rows - 1]);
381 /* Frees up all the memory we've allocated. */
385 struct outp_driver *d;
387 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
388 if (d->class->special == 0)
390 struct list_ext *prc = d->prc;
395 for (i = 0; i < prc->header_rows; i++)
396 free (prc->header[i]);
401 d->class->text_set_font_by_name (d, "PROP");
403 else if (d->class == &html_class)
405 if (d->driver_open && d->page_open)
407 struct html_driver_ext *x = d->ext;
409 fputs ("</TABLE>\n", x->file.file);
415 free (cmd.v_variables);
418 /* Writes string STRING at the current position. If the text would
419 fall off the side of the page, then advance to the next line,
420 indenting by amount INDENT. */
422 write_varname (struct outp_driver *d, char *string, int indent)
424 struct outp_text text;
426 text.options = OUTP_T_JUST_LEFT;
427 ls_init (&text.s, string, strlen (string));
428 d->class->text_metrics (d, &text);
430 if (d->cp_x + text.h > d->width)
432 d->cp_y += d->font_height;
433 if (d->cp_y + d->font_height > d->length)
440 d->class->text_draw (d, &text);
444 /* When we can't fit all the values across the page, we write out all
445 the variable names just once. This is where we do it. */
447 write_fallback_headers (struct outp_driver *d)
449 const int max_width = n_chars_width(d) - 10;
455 const char *Line = _("Line");
456 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
458 while (index < cmd.n_variables)
460 struct outp_text text;
462 /* Ensure that there is enough room for a line of text. */
463 if (d->cp_y + d->font_height > d->length)
466 /* The leader is a string like `Line 1: '. Write the leader. */
467 sprintf(leader, "%s %d:", Line, ++line_number);
468 text.options = OUTP_T_JUST_LEFT;
469 ls_init (&text.s, leader, strlen (leader));
472 d->class->text_draw (d, &text);
482 int var_width = cmd.v_variables[index]->print.w;
483 if (width + var_width > max_width && width != 0)
487 d->cp_y += d->font_height;
495 sprintf (varname, " %s", cmd.v_variables[index]->name);
496 write_varname (d, varname, text.h);
499 while (++index < cmd.n_variables);
503 d->cp_y += d->font_height;
508 /* There are three possible layouts for the LIST procedure:
510 1. If the values and their variables' name fit across the page,
511 then they are listed across the page in that way.
513 2. If the values can fit across the page, but not the variable
514 names, then as many variable names as necessary are printed
515 vertically to compensate.
517 3. If not even the values can fit across the page, the variable
518 names are listed just once, at the beginning, in a compact format,
519 and the values are listed with a variable name label at the
520 beginning of each line for easier reference.
522 This is complicated by the fact that we have to do all this for
523 every output driver, not just once. */
525 determine_layout (void)
527 struct outp_driver *d;
529 /* This is the largest page width of any driver, so we can tell what
530 size buffer to allocate. */
531 int largest_page_width = 0;
533 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
535 int column; /* Current column. */
536 int width; /* Accumulated width. */
537 int max_width; /* Page width. */
539 struct list_ext *prc;
541 if (d->class == &html_class)
544 assert (d->class->special == 0);
547 d->class->open_page (d);
549 max_width = n_chars_width (d);
550 largest_page_width = max (largest_page_width, max_width);
552 prc = d->prc = xmalloc (sizeof *prc);
558 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
560 struct variable *v = cmd.v_variables[column];
561 width += max (v->print.w, (int) strlen (v->name));
563 if (width <= max_width)
565 prc->header_rows = 2;
566 d->class->text_set_font_by_name (d, "FIXED");
571 for (width = cmd.n_variables - 1, column = 0;
572 column < cmd.n_variables && width <= max_width;
574 width += cmd.v_variables[column]->print.w;
576 /* If it fit then we need to determine how many labels can be
577 written horizontally. */
578 if (width <= max_width)
581 prc->n_vertical = -1;
583 for (column = cmd.n_variables - 1; column >= 0; column--)
585 struct variable *v = cmd.v_variables[column];
586 int trial_width = (width - v->print.w
587 + max (v->print.w, (int) strlen (v->name)));
589 if (trial_width > max_width)
591 prc->n_vertical = column + 1;
596 assert(prc->n_vertical != -1);
598 prc->n_vertical = cmd.n_variables;
599 /* Finally determine the length of the headers. */
600 for (prc->header_rows = 0, column = 0;
601 column < prc->n_vertical;
603 prc->header_rows = max (prc->header_rows,
604 (int) strlen (cmd.v_variables[column]->name));
607 d->class->text_set_font_by_name (d, "FIXED");
611 /* Otherwise use the ugly fallback listing format. */
613 prc->header_rows = 0;
615 d->cp_y += d->font_height;
616 write_fallback_headers (d);
617 d->cp_y += d->font_height;
618 d->class->text_set_font_by_name (d, "FIXED");
621 line_buf = xmalloc (max (1022, largest_page_width) + 2);
625 list_cases (struct ccase *c)
627 struct outp_driver *d;
630 if (case_num < cmd.first || case_num > cmd.last
631 || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
634 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
635 if (d->class->special == 0)
637 const struct list_ext *prc = d->prc;
638 const int max_width = n_chars_width (d);
642 if (!prc->header_rows)
643 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
645 for (column = 0; column < cmd.n_variables; column++)
647 struct variable *v = cmd.v_variables[column];
650 if (prc->type == 0 && column >= prc->n_vertical)
651 width = max ((int) strlen (v->name), v->print.w);
655 if (width + x > max_width && x != 0)
657 if (!n_lines_remaining (d))
664 write_line (d, line_buf);
667 if (!prc->header_rows)
668 x = nsprintf (line_buf, "%8s: ", v->name);
671 if (width > v->print.w)
673 memset(&line_buf[x], ' ', width - v->print.w);
674 x += width - v->print.w;
680 if (formats[v->print.type].cat & FCAT_STRING)
681 value.c = c->data[v->fv].s;
682 else if (v->fv == -1)
685 value.f = c->data[v->fv].f;
687 data_out (&line_buf[x], &v->print, &value);
694 if (!n_lines_remaining (d))
701 write_line (d, line_buf);
703 else if (d->class == &html_class)
705 struct html_driver_ext *x = d->ext;
708 fputs (" <TR>\n", x->file.file);
710 for (column = 0; column < cmd.n_variables; column++)
712 struct variable *v = cmd.v_variables[column];
716 if (formats[v->print.type].cat & FCAT_STRING)
717 value.c = c->data[v->fv].s;
718 else if (v->fv == -1)
721 value.f = c->data[v->fv].f;
723 data_out (buf, &v->print, &value);
726 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
727 &buf[strspn (buf, " ")]);
730 fputs (" </TR>\n", x->file.file);
738 /* Debugging output. */
741 /* Prints out the command as parsed by cmd_list(). */
748 printf (" VARIABLES=");
749 for (i = 0; i < cmd.n_variables; i++)
753 fputs (cmd.v_variables[i]->name, stdout);
756 printf ("\n /CASES=FROM %ld TO %ld BY %ld\n", first, last, step);
758 fputs (" /FORMAT=", stdout);
759 if (numbering == NUMBERED)
760 fputs ("NUMBERED", stdout);
762 fputs ("UNNUMBERED", stdout);
765 fputs ("WRAP", stdout);
767 fputs ("SINGLE", stdout);
769 if (weight == WEIGHT)
770 fputs ("WEIGHT", stdout);
772 fputs ("NOWEIGHT", stdout);
775 #endif /* DEBUGGING */