pivot-table: Add more tests for pivot table rendering.
[pspp.git] / tests / output / pivot-table-test.c
blob60053e9ba8a6234850543e52daf14e2c54e23082
1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 #include <config.h>
19 #include <errno.h>
20 #include <fnmatch.h>
21 #include <getopt.h>
22 #include <limits.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
27 #include "data/file-handle-def.h"
28 #include "language/lexer/lexer.h"
29 #include "language/lexer/format-parser.h"
30 #include "libpspp/assertion.h"
31 #include "libpspp/compiler.h"
32 #include "libpspp/i18n.h"
33 #include "libpspp/string-map.h"
34 #include "output/driver.h"
35 #include "output/message-item.h"
36 #include "output/options.h"
37 #include "output/pivot-table.h"
38 #include "output/table-item.h"
40 #include "gl/error.h"
41 #include "gl/progname.h"
42 #include "gl/xalloc.h"
43 #include "gl/xvasprintf.h"
45 /* --emphasis: Enable emphasis in ASCII driver? */
46 static bool emphasis;
48 /* --box: ASCII driver box option. */
49 static char *box;
51 /* -o, --output: Base name for output files. */
52 static const char *output_base = "render";
54 static const char *parse_options (int argc, char **argv);
55 static void usage (void) NO_RETURN;
56 static void read_table (struct lexer *);
57 static void output_msg (const struct msg *, void *);
59 int
60 main (int argc, char **argv)
62 const char *input_file_name;
64 set_program_name (argv[0]);
65 i18n_init ();
66 output_engine_push ();
67 input_file_name = parse_options (argc, argv);
69 settings_init ();
71 struct lex_reader *reader = lex_reader_for_file (input_file_name, NULL,
72 LEX_SYNTAX_AUTO,
73 LEX_ERROR_CONTINUE);
74 if (!reader)
75 exit (1);
77 struct lexer *lexer = lex_create ();
78 msg_set_handler (output_msg, lexer);
79 lex_include (lexer, reader);
80 lex_get (lexer);
82 for (;;)
84 while (lex_match (lexer, T_ENDCMD))
85 continue;
86 if (lex_match (lexer, T_STOP))
87 break;
89 read_table (lexer);
92 lex_destroy (lexer);
93 output_engine_pop ();
94 fh_done ();
96 return 0;
99 static void PRINTF_FORMAT (2, 3)
100 register_driver (struct string_map *options,
101 const char *output_file, ...)
103 va_list args;
104 va_start (args, output_file);
105 string_map_insert_nocopy (options, xstrdup ("output-file"),
106 xvasprintf (output_file, args));
107 va_end (args);
109 struct output_driver *driver = output_driver_create (options);
110 if (driver == NULL)
111 exit (EXIT_FAILURE);
112 output_driver_register (driver);
115 static void
116 configure_drivers (int width, int length UNUSED, int min_break)
118 /* Render to stdout. */
119 struct string_map options = STRING_MAP_INITIALIZER (options);
120 string_map_insert (&options, "format", "txt");
121 string_map_insert_nocopy (&options, xstrdup ("width"),
122 xasprintf ("%d", width));
123 if (min_break >= 0)
124 string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
125 xasprintf ("%d", min_break));
126 string_map_insert (&options, "emphasis", emphasis ? "true" : "false");
127 if (box != NULL)
128 string_map_insert (&options, "box", box);
129 register_driver (&options, "-");
132 #ifdef HAVE_CAIRO
133 /* Render to <base>.pdf. */
134 string_map_insert (&options, "top-margin", "0");
135 string_map_insert (&options, "bottom-margin", "0");
136 string_map_insert (&options, "left-margin", "0");
137 string_map_insert (&options, "right-margin", "0");
138 string_map_insert (&options, "paper-size", "99x99in");
139 string_map_insert (&options, "trim", "true");
140 register_driver (&options, "%s.pdf", output_base);
141 #endif
143 string_map_insert (&options, "box", "unicode");
144 register_driver (&options, "%s.txt", output_base);
146 string_map_insert (&options, "box", "ascii");
147 register_driver (&options, "%s-ascii.txt", output_base);
149 register_driver (&options, "%s.csv", output_base);
150 register_driver (&options, "%s.odt", output_base);
151 register_driver (&options, "%s.spv", output_base);
152 register_driver (&options, "%s.html", output_base);
153 register_driver (&options, "%s.tex", output_base);
155 string_map_destroy (&options);
158 static const char *
159 parse_options (int argc, char **argv)
161 int width = 79;
162 int length = 66;
163 int min_break = -1;
165 for (;;)
167 enum {
168 OPT_WIDTH = UCHAR_MAX + 1,
169 OPT_LENGTH,
170 OPT_MIN_BREAK,
171 OPT_EMPHASIS,
172 OPT_BOX,
173 OPT_TABLE_LOOK,
174 OPT_HELP
176 static const struct option options[] =
178 {"width", required_argument, NULL, OPT_WIDTH},
179 {"length", required_argument, NULL, OPT_LENGTH},
180 {"min-break", required_argument, NULL, OPT_MIN_BREAK},
181 {"emphasis", no_argument, NULL, OPT_EMPHASIS},
182 {"box", required_argument, NULL, OPT_BOX},
183 {"output", required_argument, NULL, 'o'},
184 {"table-look", required_argument, NULL, OPT_TABLE_LOOK},
185 {"help", no_argument, NULL, OPT_HELP},
186 {NULL, 0, NULL, 0},
189 int c = getopt_long (argc, argv, "o:", options, NULL);
190 if (c == -1)
191 break;
193 switch (c)
195 case OPT_WIDTH:
196 width = atoi (optarg);
197 break;
199 case OPT_LENGTH:
200 length = atoi (optarg);
201 break;
203 case OPT_MIN_BREAK:
204 min_break = atoi (optarg);
205 break;
207 case OPT_EMPHASIS:
208 emphasis = true;
209 break;
211 case OPT_BOX:
212 box = optarg;
213 break;
215 case 'o':
216 output_base = optarg;
217 break;
219 case OPT_TABLE_LOOK:
221 struct pivot_table_look *look;
222 char *err = pivot_table_look_read (optarg, &look);
223 if (err)
224 error (1, 0, "%s", err);
225 pivot_table_look_set_default (look);
226 pivot_table_look_unref (look);
228 break;
230 case OPT_HELP:
231 usage ();
233 case 0:
234 break;
236 case '?':
237 exit (EXIT_FAILURE);
238 break;
240 default:
241 NOT_REACHED ();
246 configure_drivers (width, length, min_break);
248 if (optind + 1 != argc)
249 error (1, 0, "exactly one non-option argument required; "
250 "use --help for help");
251 return argv[optind];
254 static void
255 usage (void)
257 printf ("%s, to test rendering of PSPP tables\n"
258 "usage: %s [OPTIONS] INPUT\n"
259 "\nOptions:\n"
260 " --width=WIDTH set page width in characters\n"
261 " --length=LINE set page length in lines\n",
262 program_name, program_name);
263 exit (EXIT_SUCCESS);
266 static void
267 force_match (struct lexer *lexer, enum token_type type)
269 if (!lex_force_match (lexer, type))
270 exit (1);
273 static void
274 force_string (struct lexer *lexer)
276 if (!lex_force_string (lexer))
277 exit (1);
280 static void
281 force_int (struct lexer *lexer)
283 if (!lex_force_int (lexer))
284 exit (1);
287 static void
288 force_num (struct lexer *lexer)
290 if (!lex_force_num (lexer))
291 exit (1);
294 static bool
295 parse_settings_value_show (struct lexer *lexer, const char *name,
296 enum settings_value_show *show)
298 if (lex_match_id (lexer, name))
300 lex_match (lexer, T_EQUALS);
302 if (lex_match_id (lexer, "DEFAULT"))
303 *show = SETTINGS_VALUE_SHOW_DEFAULT;
304 else if (lex_match_id (lexer, "VALUE"))
305 *show = SETTINGS_VALUE_SHOW_VALUE;
306 else if (lex_match_id (lexer, "LABEL"))
307 *show = SETTINGS_VALUE_SHOW_LABEL;
308 else if (lex_match_id (lexer, "BOTH"))
309 *show = SETTINGS_VALUE_SHOW_BOTH;
310 else
312 lex_error_expecting (lexer, "DEFAULT", "VALUE", "LABEL", "BOTH");
313 exit (1);
316 return true;
318 else
319 return false;
322 static bool
323 parse_string_setting (struct lexer *lexer, const char *name, char **stringp)
325 if (lex_match_id (lexer, name))
327 lex_match (lexer, T_EQUALS);
328 force_string (lexer);
330 free (*stringp);
331 *stringp = xstrdup (lex_tokcstr (lexer));
333 lex_get (lexer);
334 return true;
336 else
337 return false;
340 static bool
341 match_kw (struct lexer *lexer, const char *kw)
343 return (!strcmp (kw, "ALL")
344 ? lex_match (lexer, T_ALL)
345 : lex_match_id (lexer, kw));
348 static bool
349 parse_bool_setting_with_default (struct lexer *lexer, const char *name,
350 const char *true_kw, const char *false_kw,
351 int default_value, bool *out)
353 if (lex_match_id (lexer, name))
355 if (default_value >= 0)
357 if (!lex_match (lexer, T_EQUALS))
358 *out = default_value;
359 return true;
361 else
362 force_match (lexer, T_EQUALS);
364 if (match_kw (lexer, true_kw))
365 *out = true;
366 else if (match_kw (lexer, false_kw))
367 *out = false;
368 else
370 lex_error_expecting (lexer, true_kw, false_kw);
371 exit (1);
374 return true;
376 else
377 return false;
380 static bool
381 parse_bool_setting (struct lexer *lexer, const char *name,
382 const char *true_kw, const char *false_kw,
383 bool *out)
385 return parse_bool_setting_with_default (lexer, name, true_kw, false_kw, -1,
386 out);
389 static bool
390 parse_yesno_setting (struct lexer *lexer, const char *name, bool *out)
392 return parse_bool_setting_with_default (lexer, name, "YES", "NO", true, out);
395 static struct cell_color
396 read_color (struct lexer *lexer)
398 struct cell_color color;
399 if (!parse_color__ (lex_tokcstr (lexer), &color))
401 msg (SE, "%s: unknown color", lex_tokcstr (lexer));
402 exit (1);
404 lex_get (lexer);
405 return color;
408 static bool
409 parse_color_pair_setting (struct lexer *lexer, const char *name,
410 struct cell_color out[2])
412 if (lex_match_id (lexer, name))
414 lex_match (lexer, T_EQUALS);
415 out[0] = read_color (lexer);
416 out[1] = lex_is_string (lexer) ? read_color (lexer) : out[0];
417 return true;
419 else
420 return false;
423 static bool
424 parse_int_setting (struct lexer *lexer, const char *name, int *out)
426 if (lex_match_id (lexer, name))
428 lex_match (lexer, T_EQUALS);
429 force_int (lexer);
430 *out = lex_integer (lexer);
431 lex_get (lexer);
432 return true;
434 else
435 return false;
438 static void
439 read_font_style (struct lexer *lexer, struct font_style *fs)
441 while (parse_yesno_setting (lexer, "BOLD", &fs->bold)
442 || parse_yesno_setting (lexer, "ITALIC", &fs->italic)
443 || parse_yesno_setting (lexer, "UNDERLINE", &fs->underline)
444 || parse_yesno_setting (lexer, "MARKUP", &fs->markup)
445 || parse_color_pair_setting (lexer, "FG", fs->fg)
446 || parse_color_pair_setting (lexer, "BG", fs->bg)
447 || parse_string_setting (lexer, "FACE", &fs->typeface)
448 || parse_int_setting (lexer, "SIZE", &fs->size))
449 continue;
452 static bool
453 parse_halign_setting (struct lexer *lexer, enum table_halign *halign,
454 double *decimal_offset)
456 if (lex_match_id (lexer, "RIGHT"))
457 *halign = TABLE_HALIGN_RIGHT;
458 else if (lex_match_id (lexer, "LEFT"))
459 *halign = TABLE_HALIGN_LEFT;
460 else if (lex_match_id (lexer, "CELL"))
461 *halign = TABLE_HALIGN_CENTER;
462 else if (lex_match_id (lexer, "MIXED"))
463 *halign = TABLE_HALIGN_MIXED;
464 else if (lex_match_id (lexer, "DECIMAL"))
466 if (lex_is_number (lexer))
468 *decimal_offset = lex_number (lexer);
469 lex_get (lexer);
472 else
473 return false;
475 return true;
478 static bool
479 parse_valign_setting (struct lexer *lexer, enum table_valign *valign)
481 if (lex_match_id (lexer, "TOP"))
482 *valign = TABLE_VALIGN_TOP;
483 else if (lex_match_id (lexer, "MIDDLE"))
484 *valign = TABLE_VALIGN_CENTER;
485 else if (lex_match_id (lexer, "BOTTOM"))
486 *valign = TABLE_VALIGN_BOTTOM;
487 else
488 return false;
490 return true;
493 static bool
494 parse_margin_setting (struct lexer *lexer, int margin[TABLE_N_AXES][2])
496 if (lex_match_id (lexer, "MARGINS"))
498 int values[4];
499 int n = 0;
501 lex_match (lexer, T_EQUALS);
502 force_num (lexer);
503 while (lex_is_number (lexer) && n < 4)
505 values[n++] = lex_number (lexer);
506 lex_get (lexer);
509 if (n == 1)
511 margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[0];
512 margin[TABLE_VERT][0] = margin[TABLE_VERT][1] = values[0];
514 else if (n == 2)
516 margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[1];
517 margin[TABLE_VERT][0] = margin[TABLE_VERT][1] = values[0];
519 else if (n == 3)
521 margin[TABLE_VERT][0] = values[0];
522 margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[1];
523 margin[TABLE_VERT][1] = values[2];
525 else
527 assert (n == 4);
528 margin[TABLE_VERT][0] = values[0];
529 margin[TABLE_HORZ][1] = values[1];
530 margin[TABLE_VERT][1] = values[2];
531 margin[TABLE_HORZ][0] = values[3];
534 return true;
536 else
537 return false;
540 static void
541 read_cell_style (struct lexer *lexer, struct cell_style *cs)
543 while (parse_halign_setting (lexer, &cs->halign, &cs->decimal_offset)
544 || parse_valign_setting (lexer, &cs->valign)
545 || parse_margin_setting (lexer, cs->margin))
546 continue;
549 static void
550 read_value_option (struct lexer *lexer, const struct pivot_table *pt,
551 struct pivot_value *value,
552 const struct table_area_style *base_style)
554 enum settings_value_show *show
555 = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.show
556 : value->type == PIVOT_VALUE_STRING ? &value->string.show
557 : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.show
558 : NULL);
559 if (show && parse_settings_value_show (lexer, "SHOW", show))
560 return;
562 char **var_name
563 = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.var_name
564 : value->type == PIVOT_VALUE_STRING ? &value->string.var_name
565 : NULL);
566 if (var_name && parse_string_setting (lexer, "VAR", var_name))
567 return;
569 char **label
570 = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.value_label
571 : value->type == PIVOT_VALUE_STRING ? &value->string.value_label
572 : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.var_label
573 : NULL);
574 if (label && parse_string_setting (lexer, "LABEL", label))
575 return;
577 if (value->type == PIVOT_VALUE_STRING && lex_match_id (lexer, "HEX"))
579 value->string.hex = true;
580 return;
583 if (value->type == PIVOT_VALUE_NUMERIC)
585 msg_disable ();
586 struct fmt_spec fmt;
587 bool ok = parse_format_specifier (lexer, &fmt);
588 msg_enable ();
590 if (ok)
592 if (!fmt_check_output (&fmt)
593 || !fmt_check_type_compat (&fmt, VAL_NUMERIC))
594 exit (1);
596 value->numeric.format = fmt;
597 return;
601 if (lex_match_id (lexer, "SUBSCRIPTS"))
603 lex_match (lexer, T_EQUALS);
604 size_t allocated_subscripts = value->n_subscripts;
605 while (lex_token (lexer) == T_STRING)
607 if (value->n_subscripts >= allocated_subscripts)
608 value->subscripts = x2nrealloc (value->subscripts,
609 &allocated_subscripts,
610 sizeof *value->subscripts);
612 value->subscripts[value->n_subscripts++] = xstrdup (
613 lex_tokcstr (lexer));
614 lex_get (lexer);
616 return;
619 if (lex_match_id (lexer, "FONT") && base_style)
621 lex_match (lexer, T_EQUALS);
623 if (!value->font_style)
625 value->font_style = xmalloc (sizeof *value->font_style);
626 font_style_copy (NULL, value->font_style, &base_style->font_style);
628 read_font_style (lexer, value->font_style);
629 return;
632 if (lex_match_id (lexer, "CELL") && base_style)
634 lex_match (lexer, T_EQUALS);
636 if (!value->cell_style)
638 value->cell_style = xmalloc (sizeof *value->cell_style);
639 *value->cell_style = base_style->cell_style;
641 read_cell_style (lexer, value->cell_style);
642 return;
645 if (lex_match_id (lexer, "FOOTNOTE"))
647 lex_match (lexer, T_EQUALS);
649 while (lex_is_integer (lexer))
651 size_t idx = lex_integer (lexer);
652 lex_get (lexer);
654 if (idx >= pt->n_footnotes)
656 msg (SE, "Footnote %zu not available "
657 "(only %zu footnotes defined)", idx, pt->n_footnotes);
658 exit (1);
660 pivot_value_add_footnote (value, pt->footnotes[idx]);
662 return;
665 lex_error (lexer, "Expecting valid value option");
666 exit (1);
669 static struct pivot_value *
670 read_value (struct lexer *lexer, const struct pivot_table *pt,
671 const struct table_area_style *base_style)
673 struct pivot_value *value;
674 if (lex_is_number (lexer))
676 value = pivot_value_new_number (lex_number (lexer));
677 lex_get (lexer);
679 else if (lex_is_string (lexer))
681 value = xmalloc (sizeof *value);
682 *value = (struct pivot_value) {
683 .type = PIVOT_VALUE_STRING,
684 .string = { .s = xstrdup (lex_tokcstr (lexer)) },
686 lex_get (lexer);
688 else if (lex_token (lexer) == T_ID)
690 value = xmalloc (sizeof *value);
691 *value = (struct pivot_value) {
692 .type = PIVOT_VALUE_VARIABLE,
693 .variable = { .var_name = xstrdup (lex_tokcstr (lexer)) },
695 lex_get (lexer);
697 else
699 msg (SE, "Expecting pivot_value");
700 exit (1);
703 while (lex_match (lexer, T_LBRACK))
705 read_value_option (lexer, pt, value, base_style);
706 force_match (lexer, T_RBRACK);
709 return value;
712 static void
713 read_group (struct lexer *lexer, struct pivot_table *pt,
714 struct pivot_category *group,
715 const struct table_area_style *label_style)
717 if (lex_match (lexer, T_ASTERISK))
718 group->show_label = true;
720 force_match (lexer, T_LPAREN);
721 if (lex_match (lexer, T_RPAREN))
722 return;
726 struct pivot_value *name = read_value (lexer, pt, label_style);
727 if (lex_token (lexer) == T_ASTERISK
728 || lex_token (lexer) == T_LPAREN)
729 read_group (lexer, pt, pivot_category_create_group__ (group, name),
730 label_style);
731 else
733 char *rc;
734 if (lex_token (lexer) == T_ID
735 && is_pivot_result_class (lex_tokcstr (lexer)))
737 rc = xstrdup (lex_tokcstr (lexer));
738 lex_get (lexer);
740 else
741 rc = NULL;
743 pivot_category_create_leaf_rc (group, name, rc);
745 free (rc);
748 while (lex_match (lexer, T_COMMA));
749 force_match (lexer, T_RPAREN);
752 static void
753 read_dimension (struct lexer *lexer, struct pivot_table *pt,
754 enum pivot_axis_type a,
755 const struct table_area_style *label_style)
757 if (!pivot_table_is_empty (pt))
758 error (1, 0, "can't add dimensions after adding data");
760 lex_match (lexer, T_EQUALS);
762 struct pivot_value *name = read_value (lexer, pt, label_style);
763 struct pivot_dimension *dim = pivot_dimension_create__ (pt, a, name);
764 read_group (lexer, pt, dim->root, label_style);
767 static void
768 read_look (struct lexer *lexer, struct pivot_table *pt)
770 lex_match (lexer, T_EQUALS);
772 if (lex_is_string (lexer))
774 struct pivot_table_look *look;
775 char *error = pivot_table_look_read (lex_tokcstr (lexer), &look);
776 if (error)
778 msg (SE, "%s", error);
779 exit (1);
781 lex_get (lexer);
783 pivot_table_set_look (pt, look);
784 pivot_table_look_unref (look);
787 struct pivot_table_look *look = pivot_table_look_unshare (
788 pivot_table_look_ref (pt->look));
789 for (;;)
791 if (!parse_bool_setting (lexer, "EMPTY", "HIDE", "SHOW",
792 &look->omit_empty)
793 && !parse_bool_setting (lexer, "ROWLABELS", "CORNER", "NESTED",
794 &look->row_labels_in_corner)
795 && !parse_bool_setting (lexer, "MARKERS", "NUMERIC", "ALPHA",
796 &look->show_numeric_markers)
797 && !parse_bool_setting (lexer, "LEVEL", "SUPER", "SUB",
798 &look->footnote_marker_superscripts)
799 && !parse_bool_setting (lexer, "LAYERS", "ALL", "CURRENT",
800 &look->print_all_layers)
801 && !parse_bool_setting (lexer, "PAGINATELAYERS", "YES", "NO",
802 &look->paginate_layers)
803 && !parse_bool_setting (lexer, "HSHRINK", "YES", "NO",
804 &look->shrink_to_fit[TABLE_HORZ])
805 && !parse_bool_setting (lexer, "VSHRINK", "YES", "NO",
806 &look->shrink_to_fit[TABLE_VERT])
807 && !parse_bool_setting (lexer, "TOPCONTINUATION", "YES", "NO",
808 &look->top_continuation)
809 && !parse_bool_setting (lexer, "BOTTOMCONTINUATION", "YES", "NO",
810 &look->bottom_continuation)
811 && !parse_string_setting (lexer, "CONTINUATION",
812 &look->continuation))
813 break;
815 pivot_table_set_look (pt, look);
816 pivot_table_look_unref (look);
819 static enum table_stroke
820 read_stroke (struct lexer *lexer)
822 for (int stroke = 0; stroke < TABLE_N_STROKES; stroke++)
823 if (lex_match_id (lexer, table_stroke_to_string (stroke)))
824 return stroke;
826 lex_error (lexer, "expecting stroke");
827 exit (1);
830 static bool
831 parse_value_setting (struct lexer *lexer, const struct pivot_table *pt,
832 const char *name,
833 struct pivot_value **valuep,
834 struct table_area_style *base_style)
836 if (lex_match_id (lexer, name))
838 lex_match (lexer, T_EQUALS);
840 pivot_value_destroy (*valuep);
841 *valuep = read_value (lexer, pt, base_style);
843 return true;
845 else
846 return false;
849 static void
850 read_border (struct lexer *lexer, struct pivot_table *pt)
852 static const char *const pivot_border_ids[PIVOT_N_BORDERS] = {
853 [PIVOT_BORDER_TITLE] = "title",
854 [PIVOT_BORDER_OUTER_LEFT] = "outer-left",
855 [PIVOT_BORDER_OUTER_TOP] = "outer-top",
856 [PIVOT_BORDER_OUTER_RIGHT] = "outer-right",
857 [PIVOT_BORDER_OUTER_BOTTOM] = "outer-bottom",
858 [PIVOT_BORDER_INNER_LEFT] = "inner-left",
859 [PIVOT_BORDER_INNER_TOP] = "inner-top",
860 [PIVOT_BORDER_INNER_RIGHT] = "inner-right",
861 [PIVOT_BORDER_INNER_BOTTOM] = "inner-bottom",
862 [PIVOT_BORDER_DATA_LEFT] = "data-left",
863 [PIVOT_BORDER_DATA_TOP] = "data-top",
864 [PIVOT_BORDER_DIM_ROW_HORZ] = "dim-row-horz",
865 [PIVOT_BORDER_DIM_ROW_VERT] = "dim-row-vert",
866 [PIVOT_BORDER_DIM_COL_HORZ] = "dim-col-horz",
867 [PIVOT_BORDER_DIM_COL_VERT] = "dim-col-vert",
868 [PIVOT_BORDER_CAT_ROW_HORZ] = "cat-row-horz",
869 [PIVOT_BORDER_CAT_ROW_VERT] = "cat-row-vert",
870 [PIVOT_BORDER_CAT_COL_HORZ] = "cat-col-horz",
871 [PIVOT_BORDER_CAT_COL_VERT] = "cat-col-vert",
874 lex_match (lexer, T_EQUALS);
876 struct pivot_table_look *look = pivot_table_look_unshare (
877 pivot_table_look_ref (pt->look));
878 while (lex_token (lexer) == T_STRING)
880 char *s = xstrdup (lex_tokcstr (lexer));
881 lex_get (lexer);
882 force_match (lexer, T_LPAREN);
884 struct table_border_style style = TABLE_BORDER_STYLE_INITIALIZER;
885 style.stroke = read_stroke (lexer);
886 if (lex_is_string (lexer))
887 style.color = read_color (lexer);
888 force_match (lexer, T_RPAREN);
890 int n = 0;
891 for (int b = 0; b < PIVOT_N_BORDERS; b++)
893 if (!fnmatch (s, pivot_border_ids[b], 0))
895 look->borders[b] = style;
896 n++;
899 if (!n)
901 msg (SE, "%s: no matching borders", s);
902 exit (1);
904 free (s);
906 pivot_table_set_look (pt, look);
907 pivot_table_look_unref (look);
910 static void
911 read_footnote (struct lexer *lexer, struct pivot_table *pt)
913 size_t idx;
914 if (lex_match (lexer, T_LBRACK))
916 force_int (lexer);
918 idx = lex_integer (lexer);
919 lex_get (lexer);
921 force_match (lexer, T_RBRACK);
923 else
924 idx = pt->n_footnotes;
925 lex_match (lexer, T_EQUALS);
927 struct pivot_value *content
928 = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_FOOTER]);
930 struct pivot_value *marker;
931 if (lex_match_id (lexer, "MARKER"))
933 lex_match (lexer, T_EQUALS);
934 marker = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_FOOTER]);
936 else
937 marker = NULL;
939 pivot_table_create_footnote__ (pt, idx, marker, content);
942 static void
943 read_cell (struct lexer *lexer, struct pivot_table *pt)
945 force_match (lexer, T_LBRACK);
947 size_t *lo = xnmalloc (pt->n_dimensions, sizeof *lo);
948 size_t *hi = xnmalloc (pt->n_dimensions, sizeof *hi);
949 for (size_t i = 0; i < pt->n_dimensions; i++)
951 const struct pivot_dimension *d = pt->dimensions[i];
953 if (i)
954 force_match (lexer, T_COMMA);
956 if (!d->n_leaves)
958 msg (SE, "can't define data because dimension %zu has no categories",
960 exit (1);
963 if (lex_match (lexer, T_ALL))
965 lo[i] = 0;
966 hi[i] = d->n_leaves - 1;
968 else
970 force_int (lexer);
971 lo[i] = hi[i] = lex_integer (lexer);
972 lex_get (lexer);
974 if (lex_match_id (lexer, "THRU"))
976 force_int (lexer);
977 hi[i] = lex_integer (lexer);
978 lex_get (lexer);
981 if (hi[i] < lo[i])
983 msg (SE, "%zu THRU %zu is not a valid range", lo[i], hi[i]);
984 exit (1);
986 if (hi[i] >= d->n_leaves)
988 msg (SE, "dimension %zu (%s) has only %zu categories",
989 i, pivot_value_to_string (d->root->name, pt),
990 d->n_leaves);
991 exit (1);
995 force_match (lexer, T_RBRACK);
997 struct pivot_value *value = NULL;
998 bool delete = false;
999 if (lex_match (lexer, T_EQUALS))
1001 if (lex_match_id (lexer, "DELETE"))
1002 delete = true;
1003 else
1004 value = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_DATA]);
1007 size_t *dindexes = xmemdup (lo, pt->n_dimensions * sizeof *lo);
1008 for (size_t i = 0; ; i++)
1010 if (delete)
1011 pivot_table_delete (pt, dindexes);
1012 else
1013 pivot_table_put (pt, dindexes, pt->n_dimensions,
1014 (value
1015 ? pivot_value_clone (value)
1016 : pivot_value_new_integer (i)));
1018 for (size_t j = 0; j < pt->n_dimensions; j++)
1020 if (++dindexes[j] <= hi[j])
1021 goto next;
1022 dindexes[j] = lo[j];
1024 break;
1025 next:;
1027 free (dindexes);
1029 pivot_value_destroy (value);
1031 free (lo);
1032 free (hi);
1035 static struct pivot_dimension *
1036 parse_dim_name (struct lexer *lexer, struct pivot_table *table)
1038 force_string (lexer);
1039 for (size_t i = 0; i < table->n_dimensions; i++)
1041 struct pivot_dimension *dim = table->dimensions[i];
1043 struct string s = DS_EMPTY_INITIALIZER;
1044 pivot_value_format_body (dim->root->name, table, &s);
1045 bool match = !strcmp (ds_cstr (&s), lex_tokcstr (lexer));
1046 ds_destroy (&s);
1048 if (match)
1050 lex_get (lexer);
1051 return dim;
1055 lex_error (lexer, "unknown dimension");
1056 exit (1);
1059 static enum pivot_axis_type
1060 parse_axis_type (struct lexer *lexer)
1062 if (lex_match_id (lexer, "ROW"))
1063 return PIVOT_AXIS_ROW;
1064 else if (lex_match_id (lexer, "COLUMN"))
1065 return PIVOT_AXIS_COLUMN;
1066 else if (lex_match_id (lexer, "LAYER"))
1067 return PIVOT_AXIS_LAYER;
1068 else
1070 lex_error_expecting (lexer, "ROW", "COLUMN", "LAYER");
1071 exit (1);
1075 static void
1076 move_dimension (struct lexer *lexer, struct pivot_table *table)
1078 struct pivot_dimension *dim = parse_dim_name (lexer, table);
1080 enum pivot_axis_type axis = parse_axis_type (lexer);
1082 size_t position;
1083 if (lex_is_integer (lexer))
1085 position = lex_integer (lexer);
1086 lex_get (lexer);
1088 else
1089 position = 0;
1091 pivot_table_move_dimension (table, dim, axis, position);
1094 static void
1095 swap_axes (struct lexer *lexer, struct pivot_table *table)
1097 enum pivot_axis_type a = parse_axis_type (lexer);
1098 enum pivot_axis_type b = parse_axis_type (lexer);
1099 pivot_table_swap_axes (table, a, b);
1102 static void
1103 read_current_layer (struct lexer *lexer, struct pivot_table *table)
1105 lex_match (lexer, T_EQUALS);
1107 const struct pivot_axis *layer_axis = &table->axes[PIVOT_AXIS_LAYER];
1108 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
1110 const struct pivot_dimension *dim = layer_axis->dimensions[i];
1112 force_int (lexer);
1113 size_t index = lex_integer (lexer);
1114 if (index >= dim->n_leaves)
1116 lex_error (lexer, "only %zu dimensions", dim->n_leaves);
1117 exit (1);
1119 lex_get (lexer);
1121 table->current_layer[i] = index;
1125 static void
1126 read_table (struct lexer *lexer)
1128 bool displayed = false;
1130 struct pivot_table *pt = pivot_table_create ("Default Title");
1131 while (lex_match (lexer, T_SLASH))
1133 assert (!pivot_table_is_shared (pt));
1134 displayed = false;
1136 if (lex_match_id (lexer, "ROW"))
1137 read_dimension (lexer, pt, PIVOT_AXIS_ROW,
1138 &pt->look->areas[PIVOT_AREA_ROW_LABELS]);
1139 else if (lex_match_id (lexer, "COLUMN"))
1140 read_dimension (lexer, pt, PIVOT_AXIS_COLUMN,
1141 &pt->look->areas[PIVOT_AREA_COLUMN_LABELS]);
1142 else if (lex_match_id (lexer, "LAYER"))
1143 read_dimension (lexer, pt, PIVOT_AXIS_LAYER,
1144 &pt->look->areas[PIVOT_AREA_LAYERS]);
1145 else if (lex_match_id (lexer, "LOOK"))
1146 read_look (lexer, pt);
1147 else if (lex_match_id (lexer, "ROTATE"))
1149 lex_match (lexer, T_EQUALS);
1150 while (lex_token (lexer) == T_ID)
1151 if (!parse_bool_setting (lexer, "INNERCOLUMNS", "YES", "NO",
1152 &pt->rotate_inner_column_labels)
1153 && !parse_bool_setting (lexer, "OUTERROWS", "YES", "NO",
1154 &pt->rotate_outer_row_labels))
1155 break;
1157 else if (lex_match_id (lexer, "SHOW"))
1159 lex_match (lexer, T_EQUALS);
1160 while (lex_token (lexer) == T_ID)
1162 if (parse_bool_setting (lexer, "GRID", "YES", "NO",
1163 &pt->show_grid_lines)
1164 || parse_bool_setting (lexer, "CAPTION", "YES", "NO",
1165 &pt->show_caption)
1166 || parse_bool_setting (lexer, "TITLE", "YES", "NO",
1167 &pt->show_title))
1168 continue;
1170 if (parse_settings_value_show (lexer, "VALUES", &pt->show_values)
1171 || parse_settings_value_show (lexer, "VARIABLES",
1172 &pt->show_variables))
1173 continue;
1175 if (lex_match_id (lexer, "LAYER"))
1176 read_current_layer (lexer, pt);
1178 break;
1181 else if (parse_value_setting (lexer, pt, "TITLE", &pt->title,
1182 &pt->look->areas[PIVOT_AREA_TITLE])
1183 || parse_value_setting (lexer, pt, "SUBTYPE", &pt->subtype,
1184 NULL)
1185 || parse_value_setting (lexer, pt, "CORNER", &pt->corner_text,
1186 &pt->look->areas[PIVOT_AREA_CORNER])
1187 || parse_value_setting (lexer, pt, "CAPTION", &pt->caption,
1188 &pt->look->areas[PIVOT_AREA_CAPTION])
1189 || parse_string_setting (lexer, "NOTES", &pt->notes))
1191 /* Nothing. */
1193 else if (lex_match_id (lexer, "BORDER"))
1194 read_border (lexer, pt);
1195 else if (lex_match_id (lexer, "TRANSPOSE"))
1196 pivot_table_transpose (pt);
1197 else if (lex_match_id (lexer, "SWAP"))
1198 swap_axes (lexer, pt);
1199 else if (lex_match_id (lexer, "MOVE"))
1200 move_dimension (lexer, pt);
1201 else if (lex_match_id (lexer, "CELLS"))
1202 read_cell (lexer, pt);
1203 else if (lex_match_id (lexer, "FOOTNOTE"))
1204 read_footnote (lexer, pt);
1205 else if (lex_match_id (lexer, "DUMP"))
1206 pivot_table_dump (pt, 0);
1207 else if (lex_match_id (lexer, "DISPLAY"))
1209 pivot_table_submit (pivot_table_ref (pt));
1210 pt = pivot_table_unshare (pt);
1211 displayed = true;
1213 else
1215 msg (SE, "Expecting keyword");
1216 exit (1);
1220 if (!displayed)
1221 pivot_table_submit (pt);
1222 else
1223 pivot_table_unshare (pt);
1225 force_match (lexer, T_ENDCMD);
1228 static void
1229 output_msg (const struct msg *m_, void *lexer_)
1231 struct lexer *lexer = lexer_;
1232 struct msg m = *m_;
1234 if (m.file_name == NULL)
1236 m.file_name = CONST_CAST (char *, lex_get_file_name (lexer));
1237 m.first_line = lex_get_first_line_number (lexer, 0);
1238 m.last_line = lex_get_last_line_number (lexer, 0);
1241 m.command_name = output_get_command_name ();
1243 message_item_submit (message_item_create (&m));
1245 free (m.command_name);