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., 51 Franklin Street, Fifth Floor, Boston, MA
21 #include <libpspp/message.h>
24 #include <libpspp/alloc.h>
25 #include <data/case.h>
26 #include <language/command.h>
27 #include <libpspp/compiler.h>
28 #include <data/dictionary.h>
30 #include <language/lexer/lexer.h>
31 #include <libpspp/message.h>
32 #include <libpspp/magic.h>
33 #include <libpspp/misc.h>
34 #include <output/htmlP.h>
35 #include <output/output.h>
37 #include <output/manager.h>
38 #include <output/table.h>
39 #include <data/variable.h>
40 #include <procedure.h>
41 #include <data/format.h>
44 #define _(msgid) gettext (msgid)
48 #include <libpspp/debug-print.h>
52 *variables=varlist("PV_NO_SCRATCH");
53 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
54 format=numbering:numbered/!unnumbered,
56 weight:weight/!noweight.
61 /* Layout for one output driver. */
64 int type; /* 0=Values and labels fit across the page. */
65 size_t n_vertical; /* Number of labels to list vertically. */
66 size_t header_rows; /* Number of header rows. */
67 char **header; /* The header itself. */
71 static struct cmd_list cmd;
73 /* Current case number. */
77 static char *line_buf;
79 /* TTY-style output functions. */
80 static unsigned n_lines_remaining (struct outp_driver *d);
81 static unsigned n_chars_width (struct outp_driver *d);
82 static void write_line (struct outp_driver *d, char *s);
84 /* Other functions. */
85 static bool list_cases (struct ccase *, void *);
86 static void determine_layout (void);
87 static void clean_up (void);
88 static void write_header (struct outp_driver *);
89 static void write_all_headers (void *);
91 /* Returns the number of text lines that can fit on the remainder of
93 static inline unsigned
94 n_lines_remaining (struct outp_driver *d)
98 diff = d->length - d->cp_y;
99 return (diff > 0) ? (diff / d->font_height) : 0;
102 /* Returns the number of fixed-width character that can fit across the
104 static inline unsigned
105 n_chars_width (struct outp_driver *d)
107 return d->width / d->fixed_width;
110 /* Writes the line S at the current position and advances to the next
113 write_line (struct outp_driver *d, char *s)
115 struct outp_text text;
117 assert (d->cp_y + d->font_height <= d->length);
118 text.options = OUTP_T_JUST_LEFT;
119 ls_init (&text.s, s, strlen (s));
122 d->class->text_draw (d, &text);
124 d->cp_y += d->font_height;
127 /* Parses and executes the LIST procedure. */
131 struct variable casenum_var;
134 if (!parse_list (&cmd))
137 /* Fill in defaults. */
138 if (cmd.step == NOT_LONG)
140 if (cmd.first == NOT_LONG)
142 if (cmd.last == NOT_LONG)
144 if (!cmd.sbc_variables)
145 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
146 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
147 if (cmd.n_variables == 0)
149 msg (SE, _("No variables specified."));
153 /* Verify arguments. */
154 if (cmd.first > cmd.last)
157 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
158 "specified. The values will be swapped."), cmd.first, cmd.last);
160 cmd.first = cmd.last;
165 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
166 "being reset to 1."), cmd.first);
171 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
172 "being reset to 1."), cmd.last);
177 msg (SW, _("The step value %ld is less than 1. The value is being "
178 "reset to 1."), cmd.step);
182 /* Weighting variable. */
183 if (cmd.weight == LST_WEIGHT)
185 if (dict_get_weight (default_dict) != NULL)
189 for (i = 0; i < cmd.n_variables; i++)
190 if (cmd.v_variables[i] == dict_get_weight (default_dict))
192 if (i >= cmd.n_variables)
194 /* Add the weight variable to the end of the variable list. */
196 cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
197 sizeof *cmd.v_variables);
198 cmd.v_variables[cmd.n_variables - 1]
199 = dict_get_weight (default_dict);
203 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
207 if (cmd.numbering == LST_NUMBERED)
209 /* Initialize the case-number variable. */
210 strcpy (casenum_var.name, "Case#");
211 casenum_var.type = NUMERIC;
213 casenum_var.print = make_output_format (FMT_F,
214 (cmd.last == LONG_MAX
215 ? 5 : intlog10 (cmd.last)), 0);
217 /* Add the weight variable at the beginning of the variable list. */
219 cmd.v_variables = xnrealloc (cmd.v_variables,
220 cmd.n_variables, sizeof *cmd.v_variables);
221 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
222 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
223 cmd.v_variables[0] = &casenum_var;
229 ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
234 return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
237 /* Writes headers to all devices. This is done at the beginning of
238 each SPLIT FILE group. */
240 write_all_headers (void *aux UNUSED)
242 struct outp_driver *d;
244 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
246 if (!d->class->special)
248 d->cp_y += d->font_height; /* Blank line. */
251 else if (d->class == &html_class)
253 struct html_driver_ext *x = d->ext;
255 assert (d->driver_open);
256 if (x->sequence_no == 0 && !d->class->open_page (d))
258 msg (ME, _("Cannot open first page on HTML device %s."),
263 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
268 for (i = 0; i < cmd.n_variables; i++)
269 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
270 cmd.v_variables[i]->name);
273 fputs (" <TR>\n", x->file.file);
280 /* Writes the headers. Some of them might be vertical; most are
281 probably horizontal. */
283 write_header (struct outp_driver *d)
285 struct list_ext *prc = d->prc;
287 if (!prc->header_rows)
290 if (n_lines_remaining (d) < prc->header_rows + 1)
293 assert (n_lines_remaining (d) >= prc->header_rows + 1);
296 /* Design the header. */
302 /* Allocate, initialize header. */
303 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
305 int w = n_chars_width (d);
306 for (i = 0; i < prc->header_rows; i++)
308 prc->header[i] = xmalloc (w + 1);
309 memset (prc->header[i], ' ', w);
313 /* Put in vertical names. */
314 for (i = x = 0; i < prc->n_vertical; i++)
316 struct variable *v = cmd.v_variables[i];
319 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
321 for (j = 0; j < strlen (v->name); j++)
322 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
326 /* Put in horizontal names. */
327 for (; i < cmd.n_variables; i++)
329 struct variable *v = cmd.v_variables[i];
331 memset (&prc->header[prc->header_rows - 1][x], '-',
332 max (v->print.w, (int) strlen (v->name)));
333 if ((int) strlen (v->name) < v->print.w)
334 x += v->print.w - strlen (v->name);
335 memcpy (&prc->header[0][x], v->name, strlen (v->name));
336 x += strlen (v->name) + 1;
339 /* Add null bytes. */
340 for (i = 0; i < prc->header_rows; i++)
342 for (x = n_chars_width (d); x >= 1; x--)
343 if (prc->header[i][x - 1] != ' ')
345 prc->header[i][x] = 0;
352 /* Write out the header, in back-to-front order except for the last line. */
353 if (prc->header_rows >= 2)
357 for (i = prc->header_rows - 1; i-- != 0; )
358 write_line (d, prc->header[i]);
360 write_line (d, prc->header[prc->header_rows - 1]);
364 /* Frees up all the memory we've allocated. */
368 struct outp_driver *d;
370 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
371 if (d->class->special == 0)
373 struct list_ext *prc = d->prc;
378 for (i = 0; i < prc->header_rows; i++)
379 free (prc->header[i]);
384 d->class->text_set_font_by_name (d, "PROP");
386 else if (d->class == &html_class)
388 if (d->driver_open && d->page_open)
390 struct html_driver_ext *x = d->ext;
392 fputs ("</TABLE>\n", x->file.file);
398 free (cmd.v_variables);
401 /* Writes string STRING at the current position. If the text would
402 fall off the side of the page, then advance to the next line,
403 indenting by amount INDENT. */
405 write_varname (struct outp_driver *d, char *string, int indent)
407 struct outp_text text;
409 text.options = OUTP_T_JUST_LEFT;
410 ls_init (&text.s, string, strlen (string));
411 d->class->text_metrics (d, &text);
413 if (d->cp_x + text.h > d->width)
415 d->cp_y += d->font_height;
416 if (d->cp_y + d->font_height > d->length)
423 d->class->text_draw (d, &text);
427 /* When we can't fit all the values across the page, we write out all
428 the variable names just once. This is where we do it. */
430 write_fallback_headers (struct outp_driver *d)
432 const int max_width = n_chars_width(d) - 10;
438 const char *Line = _("Line");
439 char *leader = local_alloc (strlen (Line)
440 + INT_STRLEN_BOUND (line_number) + 1 + 1);
442 while (index < cmd.n_variables)
444 struct outp_text text;
446 /* Ensure that there is enough room for a line of text. */
447 if (d->cp_y + d->font_height > d->length)
450 /* The leader is a string like `Line 1: '. Write the leader. */
451 sprintf(leader, "%s %d:", Line, ++line_number);
452 text.options = OUTP_T_JUST_LEFT;
453 ls_init (&text.s, leader, strlen (leader));
456 d->class->text_draw (d, &text);
466 int var_width = cmd.v_variables[index]->print.w;
467 if (width + var_width > max_width && width != 0)
471 d->cp_y += d->font_height;
479 sprintf (varname, " %s", cmd.v_variables[index]->name);
480 write_varname (d, varname, text.h);
483 while (++index < cmd.n_variables);
487 d->cp_y += d->font_height;
492 /* There are three possible layouts for the LIST procedure:
494 1. If the values and their variables' name fit across the page,
495 then they are listed across the page in that way.
497 2. If the values can fit across the page, but not the variable
498 names, then as many variable names as necessary are printed
499 vertically to compensate.
501 3. If not even the values can fit across the page, the variable
502 names are listed just once, at the beginning, in a compact format,
503 and the values are listed with a variable name label at the
504 beginning of each line for easier reference.
506 This is complicated by the fact that we have to do all this for
507 every output driver, not just once. */
509 determine_layout (void)
511 struct outp_driver *d;
513 /* This is the largest page width of any driver, so we can tell what
514 size buffer to allocate. */
515 int largest_page_width = 0;
517 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
519 size_t column; /* Current column. */
520 int width; /* Accumulated width. */
521 int height; /* Height of vertical names. */
522 int max_width; /* Page width. */
524 struct list_ext *prc;
526 if (d->class == &html_class)
529 assert (d->class->special == 0);
532 d->class->open_page (d);
534 max_width = n_chars_width (d);
535 largest_page_width = max (largest_page_width, max_width);
537 prc = d->prc = xmalloc (sizeof *prc);
543 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
545 struct variable *v = cmd.v_variables[column];
546 width += max (v->print.w, (int) strlen (v->name));
548 if (width <= max_width)
550 prc->header_rows = 2;
551 d->class->text_set_font_by_name (d, "FIXED");
556 for (width = cmd.n_variables - 1, height = 0, column = 0;
557 column < cmd.n_variables && width <= max_width;
560 struct variable *v = cmd.v_variables[column];
562 if (strlen (v->name) > height)
563 height = strlen (v->name);
566 /* If it fit then we need to determine how many labels can be
567 written horizontally. */
568 if (width <= max_width && height <= SHORT_NAME_LEN)
571 prc->n_vertical = SIZE_MAX;
573 for (column = cmd.n_variables; column-- != 0; )
575 struct variable *v = cmd.v_variables[column];
576 int trial_width = (width - v->print.w
577 + max (v->print.w, (int) strlen (v->name)));
579 if (trial_width > max_width)
581 prc->n_vertical = column + 1;
586 assert (prc->n_vertical != SIZE_MAX);
588 prc->n_vertical = cmd.n_variables;
589 /* Finally determine the length of the headers. */
590 for (prc->header_rows = 0, column = 0;
591 column < prc->n_vertical;
593 prc->header_rows = max (prc->header_rows,
594 strlen (cmd.v_variables[column]->name));
597 d->class->text_set_font_by_name (d, "FIXED");
601 /* Otherwise use the ugly fallback listing format. */
603 prc->header_rows = 0;
605 d->cp_y += d->font_height;
606 write_fallback_headers (d);
607 d->cp_y += d->font_height;
608 d->class->text_set_font_by_name (d, "FIXED");
611 line_buf = xmalloc (max (1022, largest_page_width) + 2);
614 /* Writes case C to output. */
616 list_cases (struct ccase *c, void *aux UNUSED)
618 struct outp_driver *d;
621 if (case_idx < cmd.first || case_idx > cmd.last
622 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
625 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
626 if (d->class->special == 0)
628 const struct list_ext *prc = d->prc;
629 const int max_width = n_chars_width (d);
633 if (!prc->header_rows)
634 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
636 for (column = 0; column < cmd.n_variables; column++)
638 struct variable *v = cmd.v_variables[column];
641 if (prc->type == 0 && column >= prc->n_vertical)
642 width = max ((int) strlen (v->name), v->print.w);
646 if (width + x > max_width && x != 0)
648 if (!n_lines_remaining (d))
655 write_line (d, line_buf);
658 if (!prc->header_rows)
659 x = nsprintf (line_buf, "%8s: ", v->name);
662 if (width > v->print.w)
664 memset(&line_buf[x], ' ', width - v->print.w);
665 x += width - v->print.w;
668 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
669 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
672 union value case_idx_value;
673 case_idx_value.f = case_idx;
674 data_out (&line_buf[x], &v->print, &case_idx_value);
681 if (!n_lines_remaining (d))
688 write_line (d, line_buf);
690 else if (d->class == &html_class)
692 struct html_driver_ext *x = d->ext;
695 fputs (" <TR>\n", x->file.file);
697 for (column = 0; column < cmd.n_variables; column++)
699 struct variable *v = cmd.v_variables[column];
702 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
703 data_out (buf, &v->print, case_data (c, v->fv));
706 union value case_idx_value;
707 case_idx_value.f = case_idx;
708 data_out (buf, &v->print, &case_idx_value);
712 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
713 &buf[strspn (buf, " ")]);
716 fputs (" </TR>\n", x->file.file);