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 */
54 #include "debug-print.h"
57 static void debug_print (void);
62 *variables=varlist("PV_NO_SCRATCH");
63 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
64 format=numbering:numbered/!unnumbered,
66 weight:weight/!noweight.
71 /* Layout for one output driver. */
74 int type; /* 0=Values and labels fit across the page. */
75 int n_vertical; /* Number of labels to list vertically. */
76 int header_rows; /* Number of header rows. */
77 char **header; /* The header itself. */
81 static struct cmd_list cmd;
83 /* Current case number. */
87 static char *line_buf;
89 /* TTY-style output functions. */
90 static int n_lines_remaining (struct outp_driver *d);
91 static int n_chars_width (struct outp_driver *d);
92 static void write_line (struct outp_driver *d, char *s);
94 /* Other functions. */
95 static int list_cases (struct ccase *);
96 static void determine_layout (void);
97 static void clean_up (void);
98 static void write_header (struct outp_driver *);
99 static void write_all_headers (void);
101 /* Returns the number of text lines that can fit on the remainder of
104 n_lines_remaining (struct outp_driver *d)
108 diff = d->length - d->cp_y;
109 return (diff > 0) ? (diff / d->font_height) : 0;
112 /* Returns the number of fixed-width character that can fit across the
115 n_chars_width (struct outp_driver *d)
117 return d->width / d->fixed_width;
120 /* Writes the line S at the current position and advances to the next
123 write_line (struct outp_driver *d, char *s)
125 struct outp_text text;
127 assert (d->cp_y + d->font_height <= d->length);
128 text.options = OUTP_T_JUST_LEFT;
129 ls_init (&text.s, s, strlen (s));
132 d->class->text_draw (d, &text);
134 d->cp_y += d->font_height;
137 /* Parses and executes the LIST procedure. */
141 struct variable casenum_var;
143 lex_match_id ("LIST");
144 if (!parse_list (&cmd))
147 /* Fill in defaults. */
148 if (cmd.step == NOT_LONG)
150 if (cmd.first == NOT_LONG)
152 if (cmd.last == NOT_LONG)
154 if (!cmd.sbc_variables)
155 fill_all_vars (&cmd.v_variables, &cmd.n_variables,
156 FV_NO_SYSTEM | FV_NO_SCRATCH);
157 if (cmd.n_variables == 0)
159 msg (SE, _("No variables specified."));
163 /* Verify arguments. */
164 if (cmd.first > cmd.last)
167 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
168 "specified. The values will be swapped."), cmd.first, cmd.last);
170 cmd.first = cmd.last;
175 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
176 "being reset to 1."), cmd.first);
181 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
182 "being reset to 1."), cmd.last);
187 msg (SW, _("The step value %ld is less than 1. The value is being "
188 "reset to 1."), cmd.step);
192 /* Weighting variable. */
193 if (cmd.weight == LST_WEIGHT)
195 update_weighting (&default_dict);
196 if (default_dict.weight_index != -1)
200 for (i = 0; i < cmd.n_variables; i++)
201 if (cmd.v_variables[i]->index == default_dict.weight_index)
203 if (i >= cmd.n_variables)
205 /* Add the weight variable to the end of the variable list. */
207 cmd.v_variables = xrealloc (cmd.v_variables,
209 * sizeof *cmd.v_variables));
210 cmd.v_variables[cmd.n_variables - 1]
211 = default_dict.var[default_dict.weight_index];
215 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
219 if (cmd.numbering == LST_NUMBERED)
221 /* Initialize the case-number variable. */
222 strcpy (casenum_var.name, "Case#");
223 casenum_var.type = NUMERIC;
225 casenum_var.print.type = FMT_F;
226 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
227 casenum_var.print.d = 0;
229 /* Add the weight variable at the beginning of the variable list. */
231 cmd.v_variables = xrealloc (cmd.v_variables,
232 cmd.n_variables * sizeof *cmd.v_variables);
233 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
234 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
235 cmd.v_variables[0] = &casenum_var;
239 /* Print out command. */
246 procedure (write_all_headers, list_cases, NULL);
254 /* Writes headers to all devices. This is done at the beginning of
255 each SPLIT FILE group. */
257 write_all_headers (void)
259 struct outp_driver *d;
261 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
263 if (!d->class->special)
265 d->cp_y += d->font_height; /* Blank line. */
268 else if (d->class == &html_class)
270 struct html_driver_ext *x = d->ext;
272 assert (d->driver_open && d->page_open);
273 if (x->sequence_no == 0 && !d->class->open_page (d))
275 msg (ME, _("Cannot open first page on HTML device %s."),
280 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
285 for (i = 0; i < cmd.n_variables; i++)
286 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
287 cmd.v_variables[i]->name);
290 fputs (" <TR>\n", x->file.file);
297 /* Writes the headers. Some of them might be vertical; most are
298 probably horizontal. */
300 write_header (struct outp_driver *d)
302 struct list_ext *prc = d->prc;
304 if (!prc->header_rows)
307 if (n_lines_remaining (d) < prc->header_rows + 1)
310 assert (n_lines_remaining (d) >= prc->header_rows + 1);
313 /* Design the header. */
318 /* Allocate, initialize header. */
319 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
321 int w = n_chars_width (d);
322 for (i = 0; i < prc->header_rows; i++)
324 prc->header[i] = xmalloc (w + 1);
325 memset (prc->header[i], ' ', w);
329 /* Put in vertical names. */
330 for (i = x = 0; i < prc->n_vertical; i++)
332 struct variable *v = cmd.v_variables[i];
335 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
337 for (j = 0; j < (int) strlen (v->name); j++)
338 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
342 /* Put in horizontal names. */
343 for (; i < cmd.n_variables; i++)
345 struct variable *v = cmd.v_variables[i];
347 memset (&prc->header[prc->header_rows - 1][x], '-',
348 max (v->print.w, (int) strlen (v->name)));
349 if ((int) strlen (v->name) < v->print.w)
350 x += v->print.w - strlen (v->name);
351 memcpy (&prc->header[0][x], v->name, strlen (v->name));
352 x += strlen (v->name) + 1;
355 /* Add null bytes. */
356 for (i = 0; i < prc->header_rows; i++)
358 for (x = n_chars_width (d); x >= 1; x--)
359 if (prc->header[i][x - 1] != ' ')
361 prc->header[i][x] = 0;
368 /* Write out the header, in back-to-front order except for the last line. */
372 for (i = prc->header_rows - 2; i >= 0; i--)
373 write_line (d, prc->header[i]);
374 write_line (d, prc->header[prc->header_rows - 1]);
379 /* Frees up all the memory we've allocated. */
383 struct outp_driver *d;
385 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
386 if (d->class->special == 0)
388 struct list_ext *prc = d->prc;
393 for (i = 0; i < prc->header_rows; i++)
394 free (prc->header[i]);
399 d->class->text_set_font_by_name (d, "PROP");
401 else if (d->class == &html_class)
403 if (d->driver_open && d->page_open)
405 struct html_driver_ext *x = d->ext;
407 fputs ("</TABLE>\n", x->file.file);
413 free (cmd.v_variables);
416 /* Writes string STRING at the current position. If the text would
417 fall off the side of the page, then advance to the next line,
418 indenting by amount INDENT. */
420 write_varname (struct outp_driver *d, char *string, int indent)
422 struct outp_text text;
424 text.options = OUTP_T_JUST_LEFT;
425 ls_init (&text.s, string, strlen (string));
426 d->class->text_metrics (d, &text);
428 if (d->cp_x + text.h > d->width)
430 d->cp_y += d->font_height;
431 if (d->cp_y + d->font_height > d->length)
438 d->class->text_draw (d, &text);
442 /* When we can't fit all the values across the page, we write out all
443 the variable names just once. This is where we do it. */
445 write_fallback_headers (struct outp_driver *d)
447 const int max_width = n_chars_width(d) - 10;
453 const char *Line = _("Line");
454 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
456 while (index < cmd.n_variables)
458 struct outp_text text;
460 /* Ensure that there is enough room for a line of text. */
461 if (d->cp_y + d->font_height > d->length)
464 /* The leader is a string like `Line 1: '. Write the leader. */
465 sprintf(leader, "%s %d:", Line, ++line_number);
466 text.options = OUTP_T_JUST_LEFT;
467 ls_init (&text.s, leader, strlen (leader));
470 d->class->text_draw (d, &text);
480 int var_width = cmd.v_variables[index]->print.w;
481 if (width + var_width > max_width && width != 0)
485 d->cp_y += d->font_height;
493 sprintf (varname, " %s", cmd.v_variables[index]->name);
494 write_varname (d, varname, text.h);
497 while (++index < cmd.n_variables);
501 d->cp_y += d->font_height;
506 /* There are three possible layouts for the LIST procedure:
508 1. If the values and their variables' name fit across the page,
509 then they are listed across the page in that way.
511 2. If the values can fit across the page, but not the variable
512 names, then as many variable names as necessary are printed
513 vertically to compensate.
515 3. If not even the values can fit across the page, the variable
516 names are listed just once, at the beginning, in a compact format,
517 and the values are listed with a variable name label at the
518 beginning of each line for easier reference.
520 This is complicated by the fact that we have to do all this for
521 every output driver, not just once. */
523 determine_layout (void)
525 struct outp_driver *d;
527 /* This is the largest page width of any driver, so we can tell what
528 size buffer to allocate. */
529 int largest_page_width = 0;
531 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
533 int column; /* Current column. */
534 int width; /* Accumulated width. */
535 int max_width; /* Page width. */
537 struct list_ext *prc;
539 if (d->class == &html_class)
542 assert (d->class->special == 0);
545 d->class->open_page (d);
547 max_width = n_chars_width (d);
548 largest_page_width = max (largest_page_width, max_width);
550 prc = d->prc = xmalloc (sizeof *prc);
556 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
558 struct variable *v = cmd.v_variables[column];
559 width += max (v->print.w, (int) strlen (v->name));
561 if (width <= max_width)
563 prc->header_rows = 2;
564 d->class->text_set_font_by_name (d, "FIXED");
569 for (width = cmd.n_variables - 1, column = 0;
570 column < cmd.n_variables && width <= max_width;
572 width += cmd.v_variables[column]->print.w;
574 /* If it fit then we need to determine how many labels can be
575 written horizontally. */
576 if (width <= max_width)
579 prc->n_vertical = -1;
581 for (column = cmd.n_variables - 1; column >= 0; column--)
583 struct variable *v = cmd.v_variables[column];
584 int trial_width = (width - v->print.w
585 + max (v->print.w, (int) strlen (v->name)));
587 if (trial_width > max_width)
589 prc->n_vertical = column + 1;
594 assert(prc->n_vertical != -1);
596 prc->n_vertical = cmd.n_variables;
597 /* Finally determine the length of the headers. */
598 for (prc->header_rows = 0, column = 0;
599 column < prc->n_vertical;
601 prc->header_rows = max (prc->header_rows,
602 (int) strlen (cmd.v_variables[column]->name));
605 d->class->text_set_font_by_name (d, "FIXED");
609 /* Otherwise use the ugly fallback listing format. */
611 prc->header_rows = 0;
613 d->cp_y += d->font_height;
614 write_fallback_headers (d);
615 d->cp_y += d->font_height;
616 d->class->text_set_font_by_name (d, "FIXED");
619 line_buf = xmalloc (max (1022, largest_page_width) + 2);
623 list_cases (struct ccase *c)
625 struct outp_driver *d;
628 if (case_num < cmd.first || case_num > cmd.last
629 || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
632 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
633 if (d->class->special == 0)
635 const struct list_ext *prc = d->prc;
636 const int max_width = n_chars_width (d);
640 if (!prc->header_rows)
641 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
643 for (column = 0; column < cmd.n_variables; column++)
645 struct variable *v = cmd.v_variables[column];
648 if (prc->type == 0 && column >= prc->n_vertical)
649 width = max ((int) strlen (v->name), v->print.w);
653 if (width + x > max_width && x != 0)
655 if (!n_lines_remaining (d))
662 write_line (d, line_buf);
665 if (!prc->header_rows)
666 x = nsprintf (line_buf, "%8s: ", v->name);
669 if (width > v->print.w)
671 memset(&line_buf[x], ' ', width - v->print.w);
672 x += width - v->print.w;
678 if (formats[v->print.type].cat & FCAT_STRING)
679 value.c = c->data[v->fv].s;
680 else if (v->fv == -1)
683 value.f = c->data[v->fv].f;
685 data_out (&line_buf[x], &v->print, &value);
692 if (!n_lines_remaining (d))
699 write_line (d, line_buf);
701 else if (d->class == &html_class)
703 struct html_driver_ext *x = d->ext;
706 fputs (" <TR>\n", x->file.file);
708 for (column = 0; column < cmd.n_variables; column++)
710 struct variable *v = cmd.v_variables[column];
714 if (formats[v->print.type].cat & FCAT_STRING)
715 value.c = c->data[v->fv].s;
716 else if (v->fv == -1)
719 value.f = c->data[v->fv].f;
721 data_out (buf, &v->print, &value);
724 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
725 &buf[strspn (buf, " ")]);
728 fputs (" </TR>\n", x->file.file);
736 /* Debugging output. */
739 /* Prints out the command as parsed by cmd_list(). */
746 printf (" VARIABLES=");
747 for (i = 0; i < cmd.n_variables; i++)
751 fputs (cmd.v_variables[i]->name, stdout);
754 printf ("\n /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
756 fputs (" /FORMAT=", stdout);
757 if (cmd.numbering == LST_NUMBERED)
758 fputs ("NUMBERED", stdout);
760 fputs ("UNNUMBERED", stdout);
762 if (cmd.wrap == LST_WRAP)
763 fputs ("WRAP", stdout);
765 fputs ("SINGLE", stdout);
767 if (cmd.weight == LST_WEIGHT)
768 fputs ("WEIGHT", stdout);
770 fputs ("NOWEIGHT", stdout);
773 #endif /* DEBUGGING */