1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000, 2006 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
27 #include <data/case.h>
28 #include <data/dictionary.h>
29 #include <data/format.h>
30 #include <data/procedure.h>
31 #include <data/variable.h>
32 #include <language/command.h>
33 #include <language/dictionary/split-file.h>
34 #include <language/lexer/lexer.h>
35 #include <libpspp/alloc.h>
36 #include <libpspp/compiler.h>
37 #include <libpspp/magic.h>
38 #include <libpspp/message.h>
39 #include <libpspp/message.h>
40 #include <libpspp/misc.h>
41 #include <output/htmlP.h>
42 #include <output/manager.h>
43 #include <output/output.h>
44 #include <output/table.h>
47 #define _(msgid) gettext (msgid)
53 *variables=varlist("PV_NO_SCRATCH");
54 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
55 +format=numbering:numbered/!unnumbered,
57 weight:weight/!noweight.
62 /* Layout for one output driver. */
65 int type; /* 0=Values and labels fit across the page. */
66 size_t n_vertical; /* Number of labels to list vertically. */
67 size_t header_rows; /* Number of header rows. */
68 char **header; /* The header itself. */
72 static struct cmd_list cmd;
74 /* Current case number. */
78 static struct string line_buffer;
80 /* TTY-style output functions. */
81 static unsigned n_lines_remaining (struct outp_driver *d);
82 static unsigned n_chars_width (struct outp_driver *d);
83 static void write_line (struct outp_driver *d, const char *s);
85 /* Other functions. */
86 static bool list_cases (const struct ccase *, void *);
87 static void determine_layout (void);
88 static void clean_up (void);
89 static void write_header (struct outp_driver *);
90 static void write_all_headers (const struct ccase *, void *);
92 /* Returns the number of text lines that can fit on the remainder of
94 static inline unsigned
95 n_lines_remaining (struct outp_driver *d)
99 diff = d->length - d->cp_y;
100 return (diff > 0) ? (diff / d->font_height) : 0;
103 /* Returns the number of fixed-width character that can fit across the
105 static inline unsigned
106 n_chars_width (struct outp_driver *d)
108 return d->width / d->fixed_width;
111 /* Writes the line S at the current position and advances to the next
114 write_line (struct outp_driver *d, const char *s)
116 struct outp_text text;
118 assert (d->cp_y + d->font_height <= d->length);
119 text.font = OUTP_FIXED;
120 text.justification = OUTP_LEFT;
121 text.string = ss_cstr (s);
124 text.h = text.v = INT_MAX;
125 d->class->text_draw (d, &text);
127 d->cp_y += d->font_height;
130 /* Parses and executes the LIST procedure. */
134 struct variable casenum_var;
137 if (!parse_list (&cmd, NULL))
140 /* Fill in defaults. */
141 if (cmd.step == NOT_LONG)
143 if (cmd.first == NOT_LONG)
145 if (cmd.last == NOT_LONG)
147 if (!cmd.sbc_variables)
148 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
149 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
150 if (cmd.n_variables == 0)
152 msg (SE, _("No variables specified."));
156 /* Verify arguments. */
157 if (cmd.first > cmd.last)
160 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
161 "specified. The values will be swapped."), cmd.first, cmd.last);
163 cmd.first = cmd.last;
168 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
169 "being reset to 1."), cmd.first);
174 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
175 "being reset to 1."), cmd.last);
180 msg (SW, _("The step value %ld is less than 1. The value is being "
181 "reset to 1."), cmd.step);
185 /* Weighting variable. */
186 if (cmd.weight == LST_WEIGHT)
188 if (dict_get_weight (default_dict) != NULL)
192 for (i = 0; i < cmd.n_variables; i++)
193 if (cmd.v_variables[i] == dict_get_weight (default_dict))
195 if (i >= cmd.n_variables)
197 /* Add the weight variable to the end of the variable list. */
199 cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
200 sizeof *cmd.v_variables);
201 cmd.v_variables[cmd.n_variables - 1]
202 = dict_get_weight (default_dict);
206 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
210 if (cmd.numbering == LST_NUMBERED)
212 /* Initialize the case-number variable. */
213 strcpy (casenum_var.name, "Case#");
214 casenum_var.type = NUMERIC;
216 casenum_var.print = make_output_format (FMT_F,
217 (cmd.last == LONG_MAX
218 ? 5 : intlog10 (cmd.last)), 0);
220 /* Add the weight variable at the beginning of the variable list. */
222 cmd.v_variables = xnrealloc (cmd.v_variables,
223 cmd.n_variables, sizeof *cmd.v_variables);
224 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
225 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
226 cmd.v_variables[0] = &casenum_var;
232 ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
233 ds_destroy(&line_buffer);
237 return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
240 /* Writes headers to all devices. This is done at the beginning of
241 each SPLIT FILE group. */
243 write_all_headers (const struct ccase *c, void *aux UNUSED)
245 struct outp_driver *d;
247 output_split_file_values (c);
248 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
250 if (!d->class->special)
252 d->cp_y += d->font_height; /* Blank line. */
255 else if (d->class == &html_class)
257 struct html_driver_ext *x = d->ext;
259 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file);
264 for (i = 0; i < cmd.n_variables; i++)
265 fprintf (x->file, " <TH><EM>%s</EM></TH>\n",
266 cmd.v_variables[i]->name);
269 fputs (" </TR>\n", x->file);
276 /* Writes the headers. Some of them might be vertical; most are
277 probably horizontal. */
279 write_header (struct outp_driver *d)
281 struct list_ext *prc = d->prc;
283 if (!prc->header_rows)
286 if (n_lines_remaining (d) < prc->header_rows + 1)
289 assert (n_lines_remaining (d) >= prc->header_rows + 1);
292 /* Design the header. */
298 /* Allocate, initialize header. */
299 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
301 int w = n_chars_width (d);
302 for (i = 0; i < prc->header_rows; i++)
304 prc->header[i] = xmalloc (w + 1);
305 memset (prc->header[i], ' ', w);
309 /* Put in vertical names. */
310 for (i = x = 0; i < prc->n_vertical; i++)
312 struct variable *v = cmd.v_variables[i];
315 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
317 for (j = 0; j < strlen (v->name); j++)
318 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
322 /* Put in horizontal names. */
323 for (; i < cmd.n_variables; i++)
325 struct variable *v = cmd.v_variables[i];
327 memset (&prc->header[prc->header_rows - 1][x], '-',
328 max (v->print.w, (int) strlen (v->name)));
329 if ((int) strlen (v->name) < v->print.w)
330 x += v->print.w - strlen (v->name);
331 memcpy (&prc->header[0][x], v->name, strlen (v->name));
332 x += strlen (v->name) + 1;
335 /* Add null bytes. */
336 for (i = 0; i < prc->header_rows; i++)
338 for (x = n_chars_width (d); x >= 1; x--)
339 if (prc->header[i][x - 1] != ' ')
341 prc->header[i][x] = 0;
348 /* Write out the header, in back-to-front order except for the last line. */
349 if (prc->header_rows >= 2)
353 for (i = prc->header_rows - 1; i-- != 0; )
354 write_line (d, prc->header[i]);
356 write_line (d, prc->header[prc->header_rows - 1]);
360 /* Frees up all the memory we've allocated. */
364 struct outp_driver *d;
366 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
367 if (d->class->special == 0)
369 struct list_ext *prc = d->prc;
374 for (i = 0; i < prc->header_rows; i++)
375 free (prc->header[i]);
380 else if (d->class == &html_class)
384 struct html_driver_ext *x = d->ext;
386 fputs ("</TABLE>\n", x->file);
392 free (cmd.v_variables);
395 /* Writes string STRING at the current position. If the text would
396 fall off the side of the page, then advance to the next line,
397 indenting by amount INDENT. */
399 write_varname (struct outp_driver *d, char *string, int indent)
401 struct outp_text text;
404 if (d->cp_x + outp_string_width (d, string, OUTP_FIXED) > d->width)
406 d->cp_y += d->font_height;
407 if (d->cp_y + d->font_height > d->length)
412 text.font = OUTP_FIXED;
413 text.justification = OUTP_LEFT;
414 text.string = ss_cstr (string);
417 text.h = text.v = INT_MAX;
418 d->class->text_draw (d, &text);
419 d->class->text_metrics (d, &text, &width, NULL);
423 /* When we can't fit all the values across the page, we write out all
424 the variable names just once. This is where we do it. */
426 write_fallback_headers (struct outp_driver *d)
428 const int max_width = n_chars_width(d) - 10;
434 const char *Line = _("Line");
435 char *leader = local_alloc (strlen (Line)
436 + INT_STRLEN_BOUND (line_number) + 1 + 1);
438 while (index < cmd.n_variables)
440 struct outp_text text;
443 /* Ensure that there is enough room for a line of text. */
444 if (d->cp_y + d->font_height > d->length)
447 /* The leader is a string like `Line 1: '. Write the leader. */
448 sprintf (leader, "%s %d:", Line, ++line_number);
449 text.font = OUTP_FIXED;
450 text.justification = OUTP_LEFT;
451 text.string = ss_cstr (leader);
454 text.h = text.v = INT_MAX;
455 d->class->text_draw (d, &text);
456 d->class->text_metrics (d, &text, &leader_width, NULL);
457 d->cp_x = leader_width;
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;
478 char varname[LONG_NAME_LEN + 2];
479 snprintf (varname, sizeof varname,
480 " %s", cmd.v_variables[index]->name);
481 write_varname (d, varname, leader_width);
484 while (++index < cmd.n_variables);
488 d->cp_y += d->font_height;
493 /* There are three possible layouts for the LIST procedure:
495 1. If the values and their variables' name fit across the page,
496 then they are listed across the page in that way.
498 2. If the values can fit across the page, but not the variable
499 names, then as many variable names as necessary are printed
500 vertically to compensate.
502 3. If not even the values can fit across the page, the variable
503 names are listed just once, at the beginning, in a compact format,
504 and the values are listed with a variable name label at the
505 beginning of each line for easier reference.
507 This is complicated by the fact that we have to do all this for
508 every output driver, not just once. */
510 determine_layout (void)
512 struct outp_driver *d;
514 /* This is the largest page width of any driver, so we can tell what
515 size buffer to allocate. */
516 int largest_page_width = 0;
518 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
520 size_t column; /* Current column. */
521 int width; /* Accumulated width. */
522 int height; /* Height of vertical names. */
523 int max_width; /* Page width. */
525 struct list_ext *prc;
527 if (d->class == &html_class)
530 assert (d->class->special == 0);
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;
555 for (width = cmd.n_variables - 1, height = 0, column = 0;
556 column < cmd.n_variables && width <= max_width;
559 struct variable *v = cmd.v_variables[column];
561 if (strlen (v->name) > height)
562 height = strlen (v->name);
565 /* If it fit then we need to determine how many labels can be
566 written horizontally. */
567 if (width <= max_width && height <= SHORT_NAME_LEN)
570 prc->n_vertical = SIZE_MAX;
572 for (column = cmd.n_variables; column-- != 0; )
574 struct variable *v = cmd.v_variables[column];
575 int trial_width = (width - v->print.w
576 + max (v->print.w, (int) strlen (v->name)));
578 if (trial_width > max_width)
580 prc->n_vertical = column + 1;
585 assert (prc->n_vertical != SIZE_MAX);
587 prc->n_vertical = cmd.n_variables;
588 /* Finally determine the length of the headers. */
589 for (prc->header_rows = 0, column = 0;
590 column < prc->n_vertical;
592 prc->header_rows = max (prc->header_rows,
593 strlen (cmd.v_variables[column]->name));
598 /* Otherwise use the ugly fallback listing format. */
600 prc->header_rows = 0;
602 d->cp_y += d->font_height;
603 write_fallback_headers (d);
604 d->cp_y += d->font_height;
607 ds_init_empty (&line_buffer);
610 /* Writes case C to output. */
612 list_cases (const struct ccase *c, void *aux UNUSED)
614 struct outp_driver *d;
617 if (case_idx < cmd.first || case_idx > cmd.last
618 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
621 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
622 if (d->class->special == 0)
624 const struct list_ext *prc = d->prc;
625 const int max_width = n_chars_width (d);
628 if (!prc->header_rows)
630 ds_put_format(&line_buffer, "%8s: ", cmd.v_variables[0]->name);
634 for (column = 0; column < cmd.n_variables; column++)
636 struct variable *v = cmd.v_variables[column];
639 if (prc->type == 0 && column >= prc->n_vertical)
640 width = max ((int) strlen (v->name), v->print.w);
644 if (width + ds_length(&line_buffer) > max_width &&
645 ds_length(&line_buffer) != 0)
647 if (!n_lines_remaining (d))
653 write_line (d, ds_cstr (&line_buffer));
654 ds_clear(&line_buffer);
656 if (!prc->header_rows)
658 ds_put_format (&line_buffer, "%8s: ", v->name);
662 if (width > v->print.w)
664 ds_put_char_multiple(&line_buffer, ' ', width - v->print.w);
667 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
669 data_out (ds_put_uninit(&line_buffer, v->print.w),
670 &v->print, case_data (c, v->fv));
674 union value case_idx_value;
675 case_idx_value.f = case_idx;
676 data_out (ds_put_uninit(&line_buffer,v->print.w),
677 &v->print, &case_idx_value);
680 ds_put_char(&line_buffer, ' ');
683 if (!n_lines_remaining (d))
689 write_line (d, ds_cstr (&line_buffer));
690 ds_clear(&line_buffer);
692 else if (d->class == &html_class)
694 struct html_driver_ext *x = d->ext;
697 fputs (" <TR>\n", x->file);
699 for (column = 0; column < cmd.n_variables; column++)
701 struct variable *v = cmd.v_variables[column];
704 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
705 data_out (buf, &v->print, case_data (c, v->fv));
708 union value case_idx_value;
709 case_idx_value.f = case_idx;
710 data_out (buf, &v->print, &case_idx_value);
713 fputs (" <TD>", x->file);
714 html_put_cell_contents (d, TAB_FIX, ss_buffer (buf, v->print.w));
715 fputs ("</TD>\n", x->file);
718 fputs (" </TR>\n", x->file);