work on pivot-table-test
[pspp] / tests / output / pivot-table-test.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include "data/file-handle-def.h"
27 #include "language/lexer/lexer.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/compiler.h"
30 #include "libpspp/i18n.h"
31 #include "libpspp/string-map.h"
32 #include "output/driver.h"
33 #include "output/message-item.h"
34 #include "output/options.h"
35 #include "output/pivot-table.h"
36 #include "output/table-item.h"
37
38 #include "gl/error.h"
39 #include "gl/progname.h"
40 #include "gl/xalloc.h"
41 #include "gl/xvasprintf.h"
42
43 /* --emphasis: Enable emphasis in ASCII driver? */
44 static bool emphasis;
45
46 /* --box: ASCII driver box option. */
47 static char *box;
48
49 /* -o, --output: Base name for output files. */
50 static const char *output_base = "render";
51
52 static const char *parse_options (int argc, char **argv);
53 static void usage (void) NO_RETURN;
54 static struct pivot_table *read_table (struct lexer *);
55 static void output_msg (const struct msg *, void *);
56
57 int
58 main (int argc, char **argv)
59 {
60   const char *input_file_name;
61
62   set_program_name (argv[0]);
63   i18n_init ();
64   output_engine_push ();
65   input_file_name = parse_options (argc, argv);
66
67   settings_init ();
68
69   struct lex_reader *reader = lex_reader_for_file (input_file_name, NULL,
70                                                    LEX_SYNTAX_AUTO,
71                                                    LEX_ERROR_CONTINUE);
72   if (!reader)
73     exit (1);
74
75   struct lexer *lexer = lex_create ();
76   msg_set_handler (output_msg, lexer);
77   lex_include (lexer, reader);
78   lex_get (lexer);
79
80   for (;;)
81     {
82       while (lex_match (lexer, T_ENDCMD))
83         continue;
84       if (lex_match (lexer, T_STOP))
85         break;
86
87       struct pivot_table *pt = read_table (lexer);
88       pivot_table_dump (pt, 0);
89       pivot_table_submit (pt);
90     }
91
92   lex_destroy (lexer);
93   output_engine_pop ();
94   fh_done ();
95
96   return 0;
97 }
98
99 static void PRINTF_FORMAT (2, 3)
100 register_driver (struct string_map *options,
101                  const char *output_file, ...)
102 {
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);
108
109   struct output_driver *driver = output_driver_create (options);
110   if (driver == NULL)
111     exit (EXIT_FAILURE);
112   output_driver_register (driver);
113 }
114
115 static void
116 configure_drivers (int width, int length, int min_break)
117 {
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, "-");
130
131   /* Render to <base>.txt. */
132   register_driver (&options, "%s.txt", output_base);
133
134 #ifdef HAVE_CAIRO
135   /* Render to <base>.pdf. */
136   string_map_insert (&options, "top-margin", "0");
137   string_map_insert (&options, "bottom-margin", "0");
138   string_map_insert (&options, "left-margin", "0");
139   string_map_insert (&options, "right-margin", "0");
140   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
141                             xasprintf ("%dx%dpt", width * 5, length * 8));
142   if (min_break >= 0)
143     {
144       string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
145                                 xasprintf ("%d", min_break * 5));
146       string_map_insert_nocopy (&options, xstrdup ("min-vbreak"),
147                                 xasprintf ("%d", min_break * 8));
148     }
149   register_driver (&options, "%s.pdf", output_base);
150 #endif
151
152   /* Render to <base>.csv. */
153   register_driver (&options, "%s.csv", output_base);
154
155   /* Render to <base>.odt. */
156   register_driver (&options, "%s.odt", output_base);
157
158   string_map_destroy (&options);
159 }
160
161 static const char *
162 parse_options (int argc, char **argv)
163 {
164   int width = 79;
165   int length = 66;
166   int min_break = -1;
167
168   for (;;)
169     {
170       enum {
171         OPT_WIDTH = UCHAR_MAX + 1,
172         OPT_LENGTH,
173         OPT_MIN_BREAK,
174         OPT_EMPHASIS,
175         OPT_BOX,
176         OPT_TABLE_LOOK,
177         OPT_HELP
178       };
179       static const struct option options[] =
180         {
181           {"width", required_argument, NULL, OPT_WIDTH},
182           {"length", required_argument, NULL, OPT_LENGTH},
183           {"min-break", required_argument, NULL, OPT_MIN_BREAK},
184           {"emphasis", no_argument, NULL, OPT_EMPHASIS},
185           {"box", required_argument, NULL, OPT_BOX},
186           {"output", required_argument, NULL, 'o'},
187           {"table-look", required_argument, NULL, OPT_TABLE_LOOK},
188           {"help", no_argument, NULL, OPT_HELP},
189           {NULL, 0, NULL, 0},
190         };
191
192       int c = getopt_long (argc, argv, "o:", options, NULL);
193       if (c == -1)
194         break;
195
196       switch (c)
197         {
198         case OPT_WIDTH:
199           width = atoi (optarg);
200           break;
201
202         case OPT_LENGTH:
203           length = atoi (optarg);
204           break;
205
206         case OPT_MIN_BREAK:
207           min_break = atoi (optarg);
208           break;
209
210         case OPT_EMPHASIS:
211           emphasis = true;
212           break;
213
214         case OPT_BOX:
215           box = optarg;
216           break;
217
218         case 'o':
219           output_base = optarg;
220           break;
221
222         case OPT_TABLE_LOOK:
223           {
224             struct pivot_table_look *look;
225             char *err = pivot_table_look_read (optarg, &look);
226             if (err)
227               error (1, 0, "%s", err);
228             pivot_table_look_set_default (look);
229             pivot_table_look_unref (look);
230           }
231           break;
232
233         case OPT_HELP:
234           usage ();
235
236         case 0:
237           break;
238
239         case '?':
240           exit(EXIT_FAILURE);
241           break;
242
243         default:
244           NOT_REACHED ();
245         }
246
247     }
248
249   configure_drivers (width, length, min_break);
250
251   if (optind + 1 != argc)
252     error (1, 0, "exactly one non-option argument required; "
253            "use --help for help");
254   return argv[optind];
255 }
256
257 static void
258 usage (void)
259 {
260   printf ("%s, to test rendering of PSPP tables\n"
261           "usage: %s [OPTIONS] INPUT\n"
262           "\nOptions:\n"
263           "  --width=WIDTH   set page width in characters\n"
264           "  --length=LINE   set page length in lines\n",
265           program_name, program_name);
266   exit (EXIT_SUCCESS);
267 }
268
269 static struct pivot_value *
270 read_value (struct lexer *lexer)
271 {
272   struct pivot_value *value;
273   if (lex_is_number (lexer))
274     {
275       value = pivot_value_new_number (lex_number (lexer));
276       lex_get (lexer);
277     }
278   else if (lex_is_string (lexer))
279     {
280       value = pivot_value_new_user_text (lex_tokcstr (lexer), SIZE_MAX);
281       lex_get (lexer);
282     }
283   else
284     {
285       msg (SE, "Expecting pivot_value");
286       exit (1);
287     }
288
289   if (!lex_match (lexer, T_LBRACK))
290     return value;
291   do
292     {
293       msg (SE, "Options not yet supported");
294       exit (1);
295     }
296   while (lex_match (lexer, T_COMMA));
297   if (!lex_force_match (lexer, T_RBRACK))
298     exit (1);
299 }
300
301 static void
302 read_group (struct lexer *lexer, struct pivot_table *pt,
303             struct pivot_category *group)
304 {
305   if (lex_match (lexer, T_ASTERISK))
306     group->show_label = true;
307
308   if (!lex_force_match (lexer, T_LPAREN))
309     exit (1);
310
311   if (lex_match (lexer, T_RPAREN))
312     return;
313
314   do
315     {
316       struct pivot_value *name = read_value (lexer);
317       if (lex_token (lexer) == T_ASTERISK
318           || lex_token (lexer) == T_LPAREN)
319         read_group (lexer, pt, pivot_category_create_group__ (group, name));
320       else
321         {
322           char *rc;
323           if (lex_token (lexer) == T_ID
324               && is_pivot_result_class (lex_tokcstr (lexer)))
325             {
326               rc = xstrdup (lex_tokcstr (lexer));
327               lex_get (lexer);
328             }
329           else
330             rc = NULL;
331
332           pivot_category_create_leaf_rc (group, name, rc);
333
334           free (rc);
335         }
336     }
337   while (lex_match (lexer, T_COMMA));
338   if (!lex_force_match (lexer, T_RPAREN))
339     exit (1);
340 }
341
342 static void
343 read_dimension (struct lexer *lexer, struct pivot_table *pt,
344                 enum pivot_axis_type a)
345 {
346   lex_match (lexer, T_EQUALS);
347
348   struct pivot_value *name = read_value (lexer);
349   struct pivot_dimension *dim = pivot_dimension_create__ (pt, a, name);
350   read_group (lexer, pt, dim->root);
351 }
352
353 static bool
354 parse_bool_setting (struct lexer *lexer, const char *name,
355                     const char *true_kw, const char *false_kw,
356                     bool *out)
357 {
358   if (lex_match_id (lexer, name))
359     {
360       if (!lex_force_match (lexer, T_EQUALS))
361         exit (1);
362
363       if (lex_match_id (lexer, true_kw))
364         *out = true;
365       else if (lex_match_id (lexer, false_kw))
366         *out = false;
367       else
368         {
369           lex_error_expecting (lexer, true_kw, false_kw);
370           exit (1);
371         }
372
373       return true;
374     }
375   else
376     return false;
377 }
378
379 static bool
380 parse_settings_value_show (struct lexer *lexer, const char *name,
381                            enum settings_value_show *show)
382 {
383   if (lex_match_id (lexer, name))
384     {
385       if (!lex_force_match (lexer, T_EQUALS))
386         exit (1);
387
388       if (lex_match_id (lexer, "DEFAULT"))
389         *show = SETTINGS_VALUE_SHOW_DEFAULT;
390       else if (lex_match_id (lexer, "VALUE"))
391         *show = SETTINGS_VALUE_SHOW_VALUE;
392       else if (lex_match_id (lexer, "LABEL"))
393         *show = SETTINGS_VALUE_SHOW_LABEL;
394       else if (lex_match_id (lexer, "BOTH"))
395         *show = SETTINGS_VALUE_SHOW_BOTH;
396       else
397         {
398           lex_error_expecting (lexer, "DEFAULT", "VALUE", "LABEL", "BOTH");
399           exit (1);
400         }
401
402       return true;
403     }
404   else
405     return false;
406 }
407
408 static bool
409 parse_string_setting (struct lexer *lexer, const char *name, char **stringp)
410 {
411   if (lex_match_id (lexer, name))
412     {
413       lex_match (lexer, T_EQUALS);
414       if (!lex_force_string (lexer))
415         exit (1);
416
417       free (*stringp);
418       *stringp = xstrdup (lex_tokcstr (lexer));
419
420       lex_get (lexer);
421       return true;
422     }
423   else
424     return false;
425 }
426
427 static void
428 read_look (struct lexer *lexer, struct pivot_table *pt)
429 {
430   lex_match (lexer, T_EQUALS);
431
432   if (lex_is_string (lexer))
433     {
434       struct pivot_table_look *look;
435       char *error = pivot_table_look_read (lex_tokcstr (lexer), &look);
436       if (error)
437         {
438           msg (SE, "%s", error);
439           exit (1);
440         }
441       lex_get (lexer);
442
443       pivot_table_set_look (pt, look);
444       pivot_table_look_unref (look);
445     }
446
447   struct pivot_table_look *look = pivot_table_look_unshare (
448     pivot_table_look_ref (pt->look));
449   for (;;)
450     {
451       if (!parse_bool_setting (lexer, "EMPTY", "HIDE", "SHOW",
452                                &look->omit_empty)
453           && !parse_bool_setting (lexer, "ROWLABELS", "CORNER", "NESTED",
454                                   &look->row_labels_in_corner)
455           && !parse_bool_setting (lexer, "MARKERS", "NUMERIC", "ALPHA",
456                                   &look->show_numeric_markers)
457           && !parse_bool_setting (lexer, "LEVEL", "SUPER", "SUB",
458                                   &look->footnote_marker_superscripts)
459           && !parse_bool_setting (lexer, "LAYERS", "CURRENT", "ALL",
460                                   &look->print_all_layers)
461           && !parse_bool_setting (lexer, "PAGINATELAYERS", "YES", "NO",
462                                   &look->paginate_layers)
463           && !parse_bool_setting (lexer, "HSHRINK", "YES", "NO",
464                                   &look->shrink_to_fit[TABLE_HORZ])
465           && !parse_bool_setting (lexer, "VSHRINK", "YES", "NO",
466                                   &look->shrink_to_fit[TABLE_VERT])
467           && !parse_bool_setting (lexer, "TOPCONTINUATION", "YES", "NO",
468                                   &look->top_continuation)
469           && !parse_bool_setting (lexer, "BOTTOMCONTINUATION", "YES", "NO",
470                                   &look->bottom_continuation)
471           && !parse_string_setting (lexer, "CONTINUATION",
472                                     &look->continuation))
473         break;
474     }
475   pivot_table_set_look (pt, look);
476   pivot_table_look_unref (look);
477 }
478
479 static enum table_stroke
480 read_stroke (struct lexer *lexer)
481 {
482   for (int stroke = 0; stroke < TABLE_N_STROKES; stroke++)
483     if (lex_match_id (lexer, table_stroke_to_string (stroke)))
484       return stroke;
485
486   lex_error (lexer, "expecting stroke");
487   exit (1);
488 }
489
490 static bool
491 parse_value_setting (struct lexer *lexer, const char *name,
492                      struct pivot_value **valuep)
493 {
494   if (lex_match_id (lexer, name))
495     {
496       lex_match (lexer, T_EQUALS);
497
498       pivot_value_destroy (*valuep);
499       *valuep = read_value (lexer);
500
501       return true;
502     }
503   else
504     return false;
505 }
506
507 static void
508 read_border (struct lexer *lexer, struct pivot_table *pt)
509 {
510   static const char *const pivot_border_ids[PIVOT_N_BORDERS] = {
511     [PIVOT_BORDER_TITLE] = "title",
512     [PIVOT_BORDER_OUTER_LEFT] = "outer-left",
513     [PIVOT_BORDER_OUTER_TOP] = "outer-top",
514     [PIVOT_BORDER_OUTER_RIGHT] = "outer-right",
515     [PIVOT_BORDER_OUTER_BOTTOM] = "outer-bottom",
516     [PIVOT_BORDER_INNER_LEFT] = "inner-left",
517     [PIVOT_BORDER_INNER_TOP] = "inner-top",
518     [PIVOT_BORDER_INNER_RIGHT] = "inner-right",
519     [PIVOT_BORDER_INNER_BOTTOM] = "inner-bottom",
520     [PIVOT_BORDER_DATA_LEFT] = "data-left",
521     [PIVOT_BORDER_DATA_TOP] = "data-top",
522     [PIVOT_BORDER_DIM_ROW_HORZ] = "dim-row-horz",
523     [PIVOT_BORDER_DIM_ROW_VERT] = "dim-row-vert",
524     [PIVOT_BORDER_DIM_COL_HORZ] = "dim-col-horz",
525     [PIVOT_BORDER_DIM_COL_VERT] = "dim-col-vert",
526     [PIVOT_BORDER_CAT_ROW_HORZ] = "cat-row-horz",
527     [PIVOT_BORDER_CAT_ROW_VERT] = "cat-row-vert",
528     [PIVOT_BORDER_CAT_COL_HORZ] = "cat-col-horz",
529     [PIVOT_BORDER_CAT_COL_VERT] = "cat-col-vert",
530   };
531
532   lex_match (lexer, T_EQUALS);
533
534   struct pivot_table_look *look = pivot_table_look_unshare (
535     pivot_table_look_ref (pt->look));
536   while (lex_token (lexer) == T_STRING)
537     {
538       char *s = xstrdup (lex_tokcstr (lexer));
539       lex_get (lexer);
540       if (!lex_force_match (lexer, T_LPAREN))
541         exit (1);
542
543       struct table_border_style style = TABLE_BORDER_STYLE_INITIALIZER;
544       style.stroke = read_stroke (lexer);
545       if (lex_is_string (lexer))
546         {
547           if (!parse_color__ (lex_tokcstr (lexer), &style.color))
548             {
549               msg (SE, "%s: unknown color", lex_tokcstr (lexer));
550               exit (1);
551             }
552           lex_get (lexer);
553         }
554       if (!lex_force_match (lexer, T_RPAREN))
555         exit (1);
556
557       int n = 0;
558       for (int b = 0; b < PIVOT_N_BORDERS; b++)
559         {
560           if (!strncmp (s, pivot_border_ids[b], strlen (s)))
561             {
562               look->borders[b] = style;
563               n++;
564             }
565         }
566       if (!n)
567         {
568           msg (SE, "%s: no matching borders", s);
569           exit (1);
570         }
571       free (s);
572     }
573   pivot_table_set_look (pt, look);
574   pivot_table_look_unref (look);
575 }
576
577 static struct pivot_table *
578 read_table (struct lexer *lexer)
579 {
580   struct pivot_table *pt = pivot_table_create__ (NULL, NULL);
581   while (lex_match (lexer, T_SLASH))
582     {
583       assert (!pivot_table_is_shared (pt));
584
585       if (lex_match_id (lexer, "ROW"))
586         read_dimension (lexer, pt, PIVOT_AXIS_ROW);
587       else if (lex_match_id (lexer, "COLUMN"))
588         read_dimension (lexer, pt, PIVOT_AXIS_COLUMN);
589       else if (lex_match_id (lexer, "LAYER"))
590         read_dimension (lexer, pt, PIVOT_AXIS_LAYER);
591       else if (lex_match_id (lexer, "LOOK"))
592         read_look (lexer, pt);
593       else if (lex_match_id (lexer, "ROTATE"))
594         {
595           lex_match (lexer, T_EQUALS);
596           while (lex_token (lexer) == T_ID)
597             if (!parse_bool_setting (lexer, "INNERCOLUMNS", "YES", "NO",
598                                      &pt->rotate_inner_column_labels)
599                 && !parse_bool_setting (lexer, "OUTERROWS", "YES", "NO",
600                                         &pt->rotate_outer_row_labels))
601               break;
602         }
603       else if (lex_match_id (lexer, "DISPLAY"))
604         {
605           lex_match (lexer, T_EQUALS);
606           while (lex_token (lexer) == T_ID)
607             {
608               if (parse_bool_setting (lexer, "GRID", "YES", "NO",
609                                       &pt->show_grid_lines)
610                   || parse_bool_setting (lexer, "CAPTION", "YES", "NO",
611                                          &pt->show_caption)
612                   || parse_bool_setting (lexer, "TITLE", "YES", "NO",
613                                          &pt->show_title))
614                 continue;
615
616               if (parse_settings_value_show (lexer, "VALUES", &pt->show_values)
617                   || parse_settings_value_show (lexer, "VARIABLES",
618                                                 &pt->show_variables))
619                 continue;
620
621               break;
622             }
623         }
624       else if (parse_value_setting (lexer, "TITLE", &pt->title)
625                || parse_value_setting (lexer, "SUBTYPE", &pt->subtype)
626                || parse_value_setting (lexer, "CORNER", &pt->corner_text)
627                || parse_value_setting (lexer, "CAPTION", &pt->caption)
628                || parse_string_setting (lexer, "NOTES", &pt->notes))
629         {
630           /* Nothing. */
631         }
632       else if (lex_match_id (lexer, "BORDER"))
633         read_border (lexer, pt);
634       else
635         {
636           msg (SE, "Expecting keyword");
637           exit (1);
638         }
639     }
640
641   if (!lex_force_match (lexer, T_ENDCMD))
642     exit (1);
643   return pt;
644 }
645
646 static void
647 output_msg (const struct msg *m_, void *lexer_)
648 {
649   struct lexer *lexer = lexer_;
650   struct msg m = *m_;
651
652   if (m.file_name == NULL)
653     {
654       m.file_name = CONST_CAST (char *, lex_get_file_name (lexer));
655       m.first_line = lex_get_first_line_number (lexer, 0);
656       m.last_line = lex_get_last_line_number (lexer, 0);
657     }
658
659   m.command_name = output_get_command_name ();
660
661   message_item_submit (message_item_create (&m));
662
663   free (m.command_name);
664 }