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 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
156 (1u << DC_SYSTEM) | (1u << DC_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 if (dict_get_weight (default_dict) != NULL)
199 for (i = 0; i < cmd.n_variables; i++)
200 if (cmd.v_variables[i] == dict_get_weight (default_dict))
202 if (i >= cmd.n_variables)
204 /* Add the weight variable to the end of the variable list. */
206 cmd.v_variables = xrealloc (cmd.v_variables,
208 * sizeof *cmd.v_variables));
209 cmd.v_variables[cmd.n_variables - 1]
210 = dict_get_weight (default_dict);
214 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
218 if (cmd.numbering == LST_NUMBERED)
220 /* Initialize the case-number variable. */
221 strcpy (casenum_var.name, "Case#");
222 casenum_var.type = NUMERIC;
224 casenum_var.print.type = FMT_F;
225 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
226 casenum_var.print.d = 0;
228 /* Add the weight variable at the beginning of the variable list. */
230 cmd.v_variables = xrealloc (cmd.v_variables,
231 cmd.n_variables * sizeof *cmd.v_variables);
232 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
233 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
234 cmd.v_variables[0] = &casenum_var;
238 /* Print out command. */
245 procedure (write_all_headers, list_cases, NULL);
253 /* Writes headers to all devices. This is done at the beginning of
254 each SPLIT FILE group. */
256 write_all_headers (void)
258 struct outp_driver *d;
260 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
262 if (!d->class->special)
264 d->cp_y += d->font_height; /* Blank line. */
267 else if (d->class == &html_class)
269 struct html_driver_ext *x = d->ext;
271 assert (d->driver_open && d->page_open);
272 if (x->sequence_no == 0 && !d->class->open_page (d))
274 msg (ME, _("Cannot open first page on HTML device %s."),
279 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
284 for (i = 0; i < cmd.n_variables; i++)
285 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
286 cmd.v_variables[i]->name);
289 fputs (" <TR>\n", x->file.file);
296 /* Writes the headers. Some of them might be vertical; most are
297 probably horizontal. */
299 write_header (struct outp_driver *d)
301 struct list_ext *prc = d->prc;
303 if (!prc->header_rows)
306 if (n_lines_remaining (d) < prc->header_rows + 1)
309 assert (n_lines_remaining (d) >= prc->header_rows + 1);
312 /* Design the header. */
317 /* Allocate, initialize header. */
318 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
320 int w = n_chars_width (d);
321 for (i = 0; i < prc->header_rows; i++)
323 prc->header[i] = xmalloc (w + 1);
324 memset (prc->header[i], ' ', w);
328 /* Put in vertical names. */
329 for (i = x = 0; i < prc->n_vertical; i++)
331 struct variable *v = cmd.v_variables[i];
334 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
336 for (j = 0; j < (int) strlen (v->name); j++)
337 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
341 /* Put in horizontal names. */
342 for (; i < cmd.n_variables; i++)
344 struct variable *v = cmd.v_variables[i];
346 memset (&prc->header[prc->header_rows - 1][x], '-',
347 max (v->print.w, (int) strlen (v->name)));
348 if ((int) strlen (v->name) < v->print.w)
349 x += v->print.w - strlen (v->name);
350 memcpy (&prc->header[0][x], v->name, strlen (v->name));
351 x += strlen (v->name) + 1;
354 /* Add null bytes. */
355 for (i = 0; i < prc->header_rows; i++)
357 for (x = n_chars_width (d); x >= 1; x--)
358 if (prc->header[i][x - 1] != ' ')
360 prc->header[i][x] = 0;
367 /* Write out the header, in back-to-front order except for the last line. */
371 for (i = prc->header_rows - 2; i >= 0; i--)
372 write_line (d, prc->header[i]);
373 write_line (d, prc->header[prc->header_rows - 1]);
378 /* Frees up all the memory we've allocated. */
382 struct outp_driver *d;
384 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
385 if (d->class->special == 0)
387 struct list_ext *prc = d->prc;
392 for (i = 0; i < prc->header_rows; i++)
393 free (prc->header[i]);
398 d->class->text_set_font_by_name (d, "PROP");
400 else if (d->class == &html_class)
402 if (d->driver_open && d->page_open)
404 struct html_driver_ext *x = d->ext;
406 fputs ("</TABLE>\n", x->file.file);
412 free (cmd.v_variables);
415 /* Writes string STRING at the current position. If the text would
416 fall off the side of the page, then advance to the next line,
417 indenting by amount INDENT. */
419 write_varname (struct outp_driver *d, char *string, int indent)
421 struct outp_text text;
423 text.options = OUTP_T_JUST_LEFT;
424 ls_init (&text.s, string, strlen (string));
425 d->class->text_metrics (d, &text);
427 if (d->cp_x + text.h > d->width)
429 d->cp_y += d->font_height;
430 if (d->cp_y + d->font_height > d->length)
437 d->class->text_draw (d, &text);
441 /* When we can't fit all the values across the page, we write out all
442 the variable names just once. This is where we do it. */
444 write_fallback_headers (struct outp_driver *d)
446 const int max_width = n_chars_width(d) - 10;
452 const char *Line = _("Line");
453 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
455 while (index < cmd.n_variables)
457 struct outp_text text;
459 /* Ensure that there is enough room for a line of text. */
460 if (d->cp_y + d->font_height > d->length)
463 /* The leader is a string like `Line 1: '. Write the leader. */
464 sprintf(leader, "%s %d:", Line, ++line_number);
465 text.options = OUTP_T_JUST_LEFT;
466 ls_init (&text.s, leader, strlen (leader));
469 d->class->text_draw (d, &text);
479 int var_width = cmd.v_variables[index]->print.w;
480 if (width + var_width > max_width && width != 0)
484 d->cp_y += d->font_height;
492 sprintf (varname, " %s", cmd.v_variables[index]->name);
493 write_varname (d, varname, text.h);
496 while (++index < cmd.n_variables);
500 d->cp_y += d->font_height;
505 /* There are three possible layouts for the LIST procedure:
507 1. If the values and their variables' name fit across the page,
508 then they are listed across the page in that way.
510 2. If the values can fit across the page, but not the variable
511 names, then as many variable names as necessary are printed
512 vertically to compensate.
514 3. If not even the values can fit across the page, the variable
515 names are listed just once, at the beginning, in a compact format,
516 and the values are listed with a variable name label at the
517 beginning of each line for easier reference.
519 This is complicated by the fact that we have to do all this for
520 every output driver, not just once. */
522 determine_layout (void)
524 struct outp_driver *d;
526 /* This is the largest page width of any driver, so we can tell what
527 size buffer to allocate. */
528 int largest_page_width = 0;
530 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
532 int column; /* Current column. */
533 int width; /* Accumulated width. */
534 int max_width; /* Page width. */
536 struct list_ext *prc;
538 if (d->class == &html_class)
541 assert (d->class->special == 0);
544 d->class->open_page (d);
546 max_width = n_chars_width (d);
547 largest_page_width = max (largest_page_width, max_width);
549 prc = d->prc = xmalloc (sizeof *prc);
555 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
557 struct variable *v = cmd.v_variables[column];
558 width += max (v->print.w, (int) strlen (v->name));
560 if (width <= max_width)
562 prc->header_rows = 2;
563 d->class->text_set_font_by_name (d, "FIXED");
568 for (width = cmd.n_variables - 1, column = 0;
569 column < cmd.n_variables && width <= max_width;
571 width += cmd.v_variables[column]->print.w;
573 /* If it fit then we need to determine how many labels can be
574 written horizontally. */
575 if (width <= max_width)
578 prc->n_vertical = -1;
580 for (column = cmd.n_variables - 1; column >= 0; column--)
582 struct variable *v = cmd.v_variables[column];
583 int trial_width = (width - v->print.w
584 + max (v->print.w, (int) strlen (v->name)));
586 if (trial_width > max_width)
588 prc->n_vertical = column + 1;
593 assert(prc->n_vertical != -1);
595 prc->n_vertical = cmd.n_variables;
596 /* Finally determine the length of the headers. */
597 for (prc->header_rows = 0, column = 0;
598 column < prc->n_vertical;
600 prc->header_rows = max (prc->header_rows,
601 (int) strlen (cmd.v_variables[column]->name));
604 d->class->text_set_font_by_name (d, "FIXED");
608 /* Otherwise use the ugly fallback listing format. */
610 prc->header_rows = 0;
612 d->cp_y += d->font_height;
613 write_fallback_headers (d);
614 d->cp_y += d->font_height;
615 d->class->text_set_font_by_name (d, "FIXED");
618 line_buf = xmalloc (max (1022, largest_page_width) + 2);
622 list_cases (struct ccase *c)
624 struct outp_driver *d;
627 if (case_num < cmd.first || case_num > cmd.last
628 || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
631 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
632 if (d->class->special == 0)
634 const struct list_ext *prc = d->prc;
635 const int max_width = n_chars_width (d);
639 if (!prc->header_rows)
640 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
642 for (column = 0; column < cmd.n_variables; column++)
644 struct variable *v = cmd.v_variables[column];
647 if (prc->type == 0 && column >= prc->n_vertical)
648 width = max ((int) strlen (v->name), v->print.w);
652 if (width + x > max_width && x != 0)
654 if (!n_lines_remaining (d))
661 write_line (d, line_buf);
664 if (!prc->header_rows)
665 x = nsprintf (line_buf, "%8s: ", v->name);
668 if (width > v->print.w)
670 memset(&line_buf[x], ' ', width - v->print.w);
671 x += width - v->print.w;
677 if (formats[v->print.type].cat & FCAT_STRING)
678 value.c = c->data[v->fv].s;
679 else if (v->fv == -1)
682 value.f = c->data[v->fv].f;
684 data_out (&line_buf[x], &v->print, &value);
691 if (!n_lines_remaining (d))
698 write_line (d, line_buf);
700 else if (d->class == &html_class)
702 struct html_driver_ext *x = d->ext;
705 fputs (" <TR>\n", x->file.file);
707 for (column = 0; column < cmd.n_variables; column++)
709 struct variable *v = cmd.v_variables[column];
713 if (formats[v->print.type].cat & FCAT_STRING)
714 value.c = c->data[v->fv].s;
715 else if (v->fv == -1)
718 value.f = c->data[v->fv].f;
720 data_out (buf, &v->print, &value);
723 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
724 &buf[strspn (buf, " ")]);
727 fputs (" </TR>\n", x->file.file);
735 /* Debugging output. */
738 /* Prints out the command as parsed by cmd_list(). */
745 printf (" VARIABLES=");
746 for (i = 0; i < cmd.n_variables; i++)
750 fputs (cmd.v_variables[i]->name, stdout);
753 printf ("\n /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
755 fputs (" /FORMAT=", stdout);
756 if (cmd.numbering == LST_NUMBERED)
757 fputs ("NUMBERED", stdout);
759 fputs ("UNNUMBERED", stdout);
761 if (cmd.wrap == LST_WRAP)
762 fputs ("WRAP", stdout);
764 fputs ("SINGLE", stdout);
766 if (cmd.weight == LST_WEIGHT)
767 fputs ("WEIGHT", stdout);
769 fputs ("NOWEIGHT", stdout);
772 #endif /* DEBUGGING */