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
39 #include "debug-print.h"
42 static void debug_print (void);
47 *variables=varlist("PV_NO_SCRATCH");
48 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
49 format=numbering:numbered/!unnumbered,
51 weight:weight/!noweight.
56 /* Layout for one output driver. */
59 int type; /* 0=Values and labels fit across the page. */
60 int n_vertical; /* Number of labels to list vertically. */
61 int header_rows; /* Number of header rows. */
62 char **header; /* The header itself. */
66 static struct cmd_list cmd;
68 /* Current case number. */
72 static char *line_buf;
74 /* TTY-style output functions. */
75 static int n_lines_remaining (struct outp_driver *d);
76 static int n_chars_width (struct outp_driver *d);
77 static void write_line (struct outp_driver *d, char *s);
79 /* Other functions. */
80 static int list_cases (struct ccase *);
81 static void determine_layout (void);
82 static void clean_up (void);
83 static void write_header (struct outp_driver *);
84 static void write_all_headers (void);
86 /* Returns the number of text lines that can fit on the remainder of
89 n_lines_remaining (struct outp_driver *d)
93 diff = d->length - d->cp_y;
94 return (diff > 0) ? (diff / d->font_height) : 0;
97 /* Returns the number of fixed-width character that can fit across the
100 n_chars_width (struct outp_driver *d)
102 return d->width / d->fixed_width;
105 /* Writes the line S at the current position and advances to the next
108 write_line (struct outp_driver *d, char *s)
110 struct outp_text text;
112 assert (d->cp_y + d->font_height <= d->length);
113 text.options = OUTP_T_JUST_LEFT;
114 ls_init (&text.s, s, strlen (s));
117 d->class->text_draw (d, &text);
119 d->cp_y += d->font_height;
122 /* Parses and executes the LIST procedure. */
126 struct variable casenum_var;
128 lex_match_id ("LIST");
129 if (!parse_list (&cmd))
132 /* Fill in defaults. */
133 if (cmd.step == NOT_LONG)
135 if (cmd.first == NOT_LONG)
137 if (cmd.last == NOT_LONG)
139 if (!cmd.sbc_variables)
140 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
141 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
142 if (cmd.n_variables == 0)
144 msg (SE, _("No variables specified."));
148 /* Verify arguments. */
149 if (cmd.first > cmd.last)
152 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
153 "specified. The values will be swapped."), cmd.first, cmd.last);
155 cmd.first = cmd.last;
160 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
161 "being reset to 1."), cmd.first);
166 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
167 "being reset to 1."), cmd.last);
172 msg (SW, _("The step value %ld is less than 1. The value is being "
173 "reset to 1."), cmd.step);
177 /* Weighting variable. */
178 if (cmd.weight == LST_WEIGHT)
180 if (dict_get_weight (default_dict) != NULL)
184 for (i = 0; i < cmd.n_variables; i++)
185 if (cmd.v_variables[i] == dict_get_weight (default_dict))
187 if (i >= cmd.n_variables)
189 /* Add the weight variable to the end of the variable list. */
191 cmd.v_variables = xrealloc (cmd.v_variables,
193 * sizeof *cmd.v_variables));
194 cmd.v_variables[cmd.n_variables - 1]
195 = dict_get_weight (default_dict);
199 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
203 if (cmd.numbering == LST_NUMBERED)
205 /* Initialize the case-number variable. */
206 strcpy (casenum_var.name, "Case#");
207 casenum_var.type = NUMERIC;
209 casenum_var.print.type = FMT_F;
210 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
211 casenum_var.print.d = 0;
213 /* Add the weight variable at the beginning of the variable list. */
215 cmd.v_variables = xrealloc (cmd.v_variables,
216 cmd.n_variables * sizeof *cmd.v_variables);
217 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
218 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
219 cmd.v_variables[0] = &casenum_var;
223 /* Print out command. */
230 procedure (write_all_headers, list_cases, NULL);
238 /* Writes headers to all devices. This is done at the beginning of
239 each SPLIT FILE group. */
241 write_all_headers (void)
243 struct outp_driver *d;
245 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
247 if (!d->class->special)
249 d->cp_y += d->font_height; /* Blank line. */
252 else if (d->class == &html_class)
254 struct html_driver_ext *x = d->ext;
256 assert (d->driver_open);
257 if (x->sequence_no == 0 && !d->class->open_page (d))
259 msg (ME, _("Cannot open first page on HTML device %s."),
264 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
269 for (i = 0; i < cmd.n_variables; i++)
270 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
271 cmd.v_variables[i]->name);
274 fputs (" <TR>\n", x->file.file);
276 else if (d->class == &devind_class)
285 /* Writes the headers. Some of them might be vertical; most are
286 probably horizontal. */
288 write_header (struct outp_driver *d)
290 struct list_ext *prc = d->prc;
292 if (!prc->header_rows)
295 if (n_lines_remaining (d) < prc->header_rows + 1)
298 assert (n_lines_remaining (d) >= prc->header_rows + 1);
301 /* Design the header. */
306 /* Allocate, initialize header. */
307 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
309 int w = n_chars_width (d);
310 for (i = 0; i < prc->header_rows; i++)
312 prc->header[i] = xmalloc (w + 1);
313 memset (prc->header[i], ' ', w);
317 /* Put in vertical names. */
318 for (i = x = 0; i < prc->n_vertical; i++)
320 struct variable *v = cmd.v_variables[i];
323 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
325 for (j = 0; j < (int) strlen (v->name); j++)
326 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
330 /* Put in horizontal names. */
331 for (; i < cmd.n_variables; i++)
333 struct variable *v = cmd.v_variables[i];
335 memset (&prc->header[prc->header_rows - 1][x], '-',
336 max (v->print.w, (int) strlen (v->name)));
337 if ((int) strlen (v->name) < v->print.w)
338 x += v->print.w - strlen (v->name);
339 memcpy (&prc->header[0][x], v->name, strlen (v->name));
340 x += strlen (v->name) + 1;
343 /* Add null bytes. */
344 for (i = 0; i < prc->header_rows; i++)
346 for (x = n_chars_width (d); x >= 1; x--)
347 if (prc->header[i][x - 1] != ' ')
349 prc->header[i][x] = 0;
356 /* Write out the header, in back-to-front order except for the last line. */
360 for (i = prc->header_rows - 2; i >= 0; i--)
361 write_line (d, prc->header[i]);
362 write_line (d, prc->header[prc->header_rows - 1]);
367 /* Frees up all the memory we've allocated. */
371 struct outp_driver *d;
373 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
374 if (d->class->special == 0)
376 struct list_ext *prc = d->prc;
381 for (i = 0; i < prc->header_rows; i++)
382 free (prc->header[i]);
387 d->class->text_set_font_by_name (d, "PROP");
389 else if (d->class == &html_class)
391 if (d->driver_open && d->page_open)
393 struct html_driver_ext *x = d->ext;
395 fputs ("</TABLE>\n", x->file.file);
398 else if (d->class == &devind_class)
405 free (cmd.v_variables);
408 /* Writes string STRING at the current position. If the text would
409 fall off the side of the page, then advance to the next line,
410 indenting by amount INDENT. */
412 write_varname (struct outp_driver *d, char *string, int indent)
414 struct outp_text text;
416 text.options = OUTP_T_JUST_LEFT;
417 ls_init (&text.s, string, strlen (string));
418 d->class->text_metrics (d, &text);
420 if (d->cp_x + text.h > d->width)
422 d->cp_y += d->font_height;
423 if (d->cp_y + d->font_height > d->length)
430 d->class->text_draw (d, &text);
434 /* When we can't fit all the values across the page, we write out all
435 the variable names just once. This is where we do it. */
437 write_fallback_headers (struct outp_driver *d)
439 const int max_width = n_chars_width(d) - 10;
445 const char *Line = _("Line");
446 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
448 while (index < cmd.n_variables)
450 struct outp_text text;
452 /* Ensure that there is enough room for a line of text. */
453 if (d->cp_y + d->font_height > d->length)
456 /* The leader is a string like `Line 1: '. Write the leader. */
457 sprintf(leader, "%s %d:", Line, ++line_number);
458 text.options = OUTP_T_JUST_LEFT;
459 ls_init (&text.s, leader, strlen (leader));
462 d->class->text_draw (d, &text);
472 int var_width = cmd.v_variables[index]->print.w;
473 if (width + var_width > max_width && width != 0)
477 d->cp_y += d->font_height;
485 sprintf (varname, " %s", cmd.v_variables[index]->name);
486 write_varname (d, varname, text.h);
489 while (++index < cmd.n_variables);
493 d->cp_y += d->font_height;
498 /* There are three possible layouts for the LIST procedure:
500 1. If the values and their variables' name fit across the page,
501 then they are listed across the page in that way.
503 2. If the values can fit across the page, but not the variable
504 names, then as many variable names as necessary are printed
505 vertically to compensate.
507 3. If not even the values can fit across the page, the variable
508 names are listed just once, at the beginning, in a compact format,
509 and the values are listed with a variable name label at the
510 beginning of each line for easier reference.
512 This is complicated by the fact that we have to do all this for
513 every output driver, not just once. */
515 determine_layout (void)
517 struct outp_driver *d;
519 /* This is the largest page width of any driver, so we can tell what
520 size buffer to allocate. */
521 int largest_page_width = 0;
523 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
525 int column; /* Current column. */
526 int width; /* Accumulated width. */
527 int max_width; /* Page width. */
529 struct list_ext *prc;
531 if (d->class == &html_class)
533 else if (d->class == &devind_class)
536 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
540 assert (d->class->special == 0);
543 d->class->open_page (d);
545 max_width = n_chars_width (d);
546 largest_page_width = max (largest_page_width, max_width);
548 prc = d->prc = xmalloc (sizeof *prc);
554 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
556 struct variable *v = cmd.v_variables[column];
557 width += max (v->print.w, (int) strlen (v->name));
559 if (width <= max_width)
561 prc->header_rows = 2;
562 d->class->text_set_font_by_name (d, "FIXED");
567 for (width = cmd.n_variables - 1, column = 0;
568 column < cmd.n_variables && width <= max_width;
570 width += cmd.v_variables[column]->print.w;
572 /* If it fit then we need to determine how many labels can be
573 written horizontally. */
574 if (width <= max_width)
577 prc->n_vertical = -1;
579 for (column = cmd.n_variables - 1; column >= 0; column--)
581 struct variable *v = cmd.v_variables[column];
582 int trial_width = (width - v->print.w
583 + max (v->print.w, (int) strlen (v->name)));
585 if (trial_width > max_width)
587 prc->n_vertical = column + 1;
592 assert(prc->n_vertical != -1);
594 prc->n_vertical = cmd.n_variables;
595 /* Finally determine the length of the headers. */
596 for (prc->header_rows = 0, column = 0;
597 column < prc->n_vertical;
599 prc->header_rows = max (prc->header_rows,
600 (int) strlen (cmd.v_variables[column]->name));
603 d->class->text_set_font_by_name (d, "FIXED");
607 /* Otherwise use the ugly fallback listing format. */
609 prc->header_rows = 0;
611 d->cp_y += d->font_height;
612 write_fallback_headers (d);
613 d->cp_y += d->font_height;
614 d->class->text_set_font_by_name (d, "FIXED");
617 line_buf = xmalloc (max (1022, largest_page_width) + 2);
621 list_cases (struct ccase *c)
623 struct outp_driver *d;
626 if (case_num < cmd.first || case_num > cmd.last
627 || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
630 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
631 if (d->class->special == 0)
633 const struct list_ext *prc = d->prc;
634 const int max_width = n_chars_width (d);
638 if (!prc->header_rows)
639 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
641 for (column = 0; column < cmd.n_variables; column++)
643 struct variable *v = cmd.v_variables[column];
646 if (prc->type == 0 && column >= prc->n_vertical)
647 width = max ((int) strlen (v->name), v->print.w);
651 if (width + x > max_width && x != 0)
653 if (!n_lines_remaining (d))
660 write_line (d, line_buf);
663 if (!prc->header_rows)
664 x = nsprintf (line_buf, "%8s: ", v->name);
667 if (width > v->print.w)
669 memset(&line_buf[x], ' ', width - v->print.w);
670 x += width - v->print.w;
676 if (formats[v->print.type].cat & FCAT_STRING)
677 value.c = c->data[v->fv].s;
678 else if (v->fv == -1)
681 value.f = c->data[v->fv].f;
683 data_out (&line_buf[x], &v->print, &value);
690 if (!n_lines_remaining (d))
697 write_line (d, line_buf);
699 else if (d->class == &html_class)
701 struct html_driver_ext *x = d->ext;
704 fputs (" <TR>\n", x->file.file);
706 for (column = 0; column < cmd.n_variables; column++)
708 struct variable *v = cmd.v_variables[column];
712 if (formats[v->print.type].cat & FCAT_STRING)
713 value.c = c->data[v->fv].s;
714 else if (v->fv == -1)
717 value.f = c->data[v->fv].f;
719 data_out (buf, &v->print, &value);
722 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
723 &buf[strspn (buf, " ")]);
726 fputs (" </TR>\n", x->file.file);
728 else if (d->class == &devind_class)
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", cmd.first, cmd.last, cmd.step);
758 fputs (" /FORMAT=", stdout);
759 if (cmd.numbering == LST_NUMBERED)
760 fputs ("NUMBERED", stdout);
762 fputs ("UNNUMBERED", stdout);
764 if (cmd.wrap == LST_WRAP)
765 fputs ("WRAP", stdout);
767 fputs ("SINGLE", stdout);
769 if (cmd.weight == LST_WEIGHT)
770 fputs ("WEIGHT", stdout);
772 fputs ("NOWEIGHT", stdout);
775 #endif /* DEBUGGING */