str: Add function xstrdup_if_nonnull() and introduce many users.
[pspp] / src / output / options.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 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 "output/options.h"
20
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "libpspp/hash-functions.h"
29 #include "libpspp/hmap.h"
30 #include "libpspp/str.h"
31 #include "libpspp/string-map.h"
32 #include "output/driver-provider.h"
33 #include "output/measure.h"
34 #include "output/table.h"
35
36 #include "gl/xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 /* Creates and returns a new struct driver_option that contains copies of
42    all of the supplied arguments.  All of the arguments must be nonnull,
43    except that VALUE may be NULL (if the user did not supply a value for this
44    option).
45
46    Refer to struct driver_option for the meaning of each argument. */
47 struct driver_option *
48 driver_option_create (const char *driver_name, const char *name,
49                       const char *value, const char *default_value)
50 {
51   struct driver_option *o = xmalloc (sizeof *o);
52   o->driver_name = xstrdup (driver_name);
53   o->name = xstrdup (name);
54   o->value = xstrdup_if_nonnull (value);
55   o->default_value = xstrdup_if_nonnull (default_value);
56   return o;
57 }
58
59 /* Creates and returns a new struct driver_option for output driver DRIVER
60    (which is needed only to the extent that its name will be used in error
61    messages).  The option named NAME is extracted from OPTIONS.  DEFAULT_VALUE
62    is the default value of the option, used if the given option was not
63    supplied or was invalid. */
64 struct driver_option *
65 driver_option_get (struct output_driver *driver, struct string_map *options,
66                    const char *name, const char *default_value)
67 {
68   struct driver_option *option;
69   char *value;
70
71   value = string_map_find_and_delete (options, name);
72   option = driver_option_create (output_driver_get_name (driver), name, value,
73                                  default_value);
74   free (value);
75   return option;
76 }
77
78 /* Frees driver option O. */
79 void
80 driver_option_destroy (struct driver_option *o)
81 {
82   if (o != NULL)
83     {
84       free (o->driver_name);
85       free (o->name);
86       free (o->value);
87       free (o->default_value);
88       free (o);
89     }
90 }
91
92 /* Stores the paper size of the value of option O into *H and *V, in 1/72000"
93    units.  Any syntax accepted by measure_paper() may be used.
94
95    Destroys O. */
96 void
97 parse_paper_size (struct driver_option *o, int *h, int *v)
98 {
99   if (o->value == NULL || !measure_paper (o->value, h, v))
100     measure_paper (o->default_value, h, v);
101   driver_option_destroy (o);
102 }
103
104 static int
105 do_parse_boolean (const char *driver_name, const char *key,
106                   const char *value)
107 {
108   if (!strcmp (value, "on") || !strcmp (value, "true")
109       || !strcmp (value, "yes") || !strcmp (value, "1"))
110     return true;
111   else if (!strcmp (value, "off") || !strcmp (value, "false")
112            || !strcmp (value, "no") || !strcmp (value, "0"))
113     return false;
114   else
115     {
116       msg (MW, _("%s: `%s' is `%s' but a Boolean value is required"),
117              driver_name, value, key);
118       return -1;
119     }
120 }
121
122 /* Parses and return O's value as a Boolean value.  "true" and "false", "yes"
123    and "no", "on" and "off", and "1" and "0" are acceptable boolean strings.
124
125    Destroys O. */
126 bool
127 parse_boolean (struct driver_option *o)
128 {
129   bool retval;
130
131   retval = do_parse_boolean (o->driver_name, o->name, o->default_value) > 0;
132   if (o->value != NULL)
133     {
134       int value = do_parse_boolean (o->driver_name, o->name, o->value);
135       if (value >= 0)
136         retval = value;
137     }
138
139   driver_option_destroy (o);
140
141   return retval;
142 }
143
144 /* Parses O's value as an enumeration constant.  The arguments to this function
145    consist of a series of string/int pairs, terminated by a null pointer value.
146    O's value is compared to each string in turn, and parse_enum() returns the
147    int associated with the first matching string.  If there is no match, or if
148    O has no user-specified value, then O's default value is treated the same
149    way.  If the default value still does not match, parse_enum() returns 0.
150
151    Example: parse_enum (o, "a", 1, "b", 2, NULL_SENTINEL) returns 1 if O's
152    value if "a", 2 if O's value is "b".
153
154    Destroys O. */
155 int
156 parse_enum (struct driver_option *o, ...)
157 {
158   va_list args;
159   int retval;
160
161   retval = 0;
162   va_start (args, o);
163   for (;;)
164     {
165       const char *s;
166       int value;
167
168       s = va_arg (args, const char *);
169       if (s == NULL)
170         {
171           if (o->value != NULL)
172             {
173               struct string choices;
174               int i;
175
176               ds_init_empty (&choices);
177               va_end (args);
178               va_start (args, o);
179               for (i = 0; ; i++)
180                 {
181                   s = va_arg (args, const char *);
182                   if (s == NULL)
183                     break;
184                   value = va_arg (args, int);
185
186                   if (i > 0)
187                     ds_put_cstr (&choices, ", ");
188                   ds_put_format (&choices, "`%s'", s);
189                 }
190
191               msg (MW, _("%s: `%s' is `%s' but one of the following "
192                              "is required: %s"),
193                      o->driver_name, o->name, o->value, ds_cstr (&choices));
194               ds_destroy (&choices);
195             }
196           break;
197         }
198       value = va_arg (args, int);
199
200       if (o->value != NULL && !strcmp (s, o->value))
201         {
202           retval = value;
203           break;
204         }
205       else if (!strcmp (s, o->default_value))
206         retval = value;
207     }
208   va_end (args);
209   driver_option_destroy (o);
210   return retval;
211 }
212
213 /* Parses O's value as an integer in the range MIN_VALUE to MAX_VALUE
214    (inclusive) and returns the integer.
215
216    Destroys O. */
217 int
218 parse_int (struct driver_option *o, int min_value, int max_value)
219 {
220   int retval = strtol (o->default_value, NULL, 0);
221
222   if (o->value != NULL)
223     {
224       int value;
225       char *tail;
226
227       errno = 0;
228       value = strtol (o->value, &tail, 0);
229       if (tail != o->value && *tail == '\0' && errno != ERANGE
230           && value >= min_value && value <= max_value)
231         retval = value;
232       else if (max_value == INT_MAX)
233         {
234           if (min_value == 0)
235             msg (MW, _("%s: `%s' is `%s' but a non-negative integer "
236                            "is required"),
237                    o->driver_name, o->name, o->value);
238           else if (min_value == 1)
239             msg (MW, _("%s: `%s' is `%s' but a positive integer is "
240                            "required"), o->driver_name, o->name, o->value);
241           else if (min_value == INT_MIN)
242             msg (MW, _("%s: `%s' is `%s' but an integer is required"),
243                    o->driver_name, o->name, o->value);
244           else
245             msg (MW, _("%s: `%s' is `%s' but an integer greater "
246                            "than %d is required"),
247                    o->driver_name, o->name, o->value, min_value - 1);
248         }
249       else
250         msg (MW, _("%s: `%s' is `%s'  but an integer between %d and "
251                        "%d is required"),
252                o->driver_name, o->name, o->value, min_value, max_value);
253     }
254
255   driver_option_destroy (o);
256   return retval;
257 }
258
259 /* Parses O's value as a dimension, as understood by measure_dimension(), and
260    returns its length in units of 1/72000".
261
262    Destroys O. */
263 int
264 parse_dimension (struct driver_option *o)
265 {
266   int retval;
267
268   retval = (o->value != NULL ? measure_dimension (o->value)
269             : o->default_value != NULL ? measure_dimension (o->default_value)
270             : -1);
271
272   driver_option_destroy (o);
273   return retval;
274 }
275
276 /* Parses O's value as a string and returns it as a malloc'd string that the
277    caller is responsible for freeing.
278
279    Destroys O. */
280 char *
281 parse_string (struct driver_option *o)
282 {
283   char *retval = xstrdup (o->value != NULL ? o->value : o->default_value);
284   driver_option_destroy (o);
285   return retval;
286 }
287
288 static char *
289 default_chart_file_name (const char *file_name)
290 {
291   if (strcmp (file_name, "-"))
292     {
293       const char *extension = strrchr (file_name, '.');
294       int stem_length = extension ? extension - file_name : strlen (file_name);
295       return xasprintf ("%.*s-#", stem_length, file_name);
296     }
297   else
298     return NULL;
299 }
300
301 /* Parses and returns a chart file name, or NULL if no charts should be output.
302    If a nonnull string is returned, it will contain at least one '#' character,
303    which the client will presumably replace by a number as part of writing
304    charts to separate files.
305
306    If O->value is "none", then this function returns NULL.
307
308    If O->value is non-NULL but not "none", returns a copy of that string (if it
309    contains '#').
310
311    If O->value is NULL, then O's default_value should be the name of the main
312    output file.  Returns NULL if default_value is "-", and otherwise returns a
313    copy of string string with its extension stripped off and "-#.png" appended.
314
315    Destroys O. */
316 char *
317 parse_chart_file_name (struct driver_option *o)
318 {
319   char *chart_file_name;
320
321   if (o->value != NULL)
322     {
323       if (!strcmp (o->value, "none"))
324         chart_file_name = NULL;
325       else if (strchr (o->value, '#') != NULL)
326         chart_file_name = xstrdup (o->value);
327       else
328         {
329           msg (MW, _("%s: `%s' is `%s' but a file name that contains "
330                          "`#' is required."),
331                o->driver_name, o->name, o->value);
332           chart_file_name = default_chart_file_name (o->default_value);
333         }
334     }
335   else
336     chart_file_name = default_chart_file_name (o->default_value);
337
338   driver_option_destroy (o);
339
340   return chart_file_name;
341 }
342
343 static int
344 lookup_color_name (const char *s)
345 {
346   struct color
347     {
348       struct hmap_node hmap_node;
349       const char *name;
350       int code;
351     };
352
353   static struct color colors[] =
354     {
355       { .name = "aliceblue", .code = 0xf0f8ff },
356       { .name = "antiquewhite", .code = 0xfaebd7 },
357       { .name = "aqua", .code = 0x00ffff },
358       { .name = "aquamarine", .code = 0x7fffd4 },
359       { .name = "azure", .code = 0xf0ffff },
360       { .name = "beige", .code = 0xf5f5dc },
361       { .name = "bisque", .code = 0xffe4c4 },
362       { .name = "black", .code = 0x000000 },
363       { .name = "blanchedalmond", .code = 0xffebcd },
364       { .name = "blue", .code = 0x0000ff },
365       { .name = "blueviolet", .code = 0x8a2be2 },
366       { .name = "brown", .code = 0xa52a2a },
367       { .name = "burlywood", .code = 0xdeb887 },
368       { .name = "cadetblue", .code = 0x5f9ea0 },
369       { .name = "chartreuse", .code = 0x7fff00 },
370       { .name = "chocolate", .code = 0xd2691e },
371       { .name = "coral", .code = 0xff7f50 },
372       { .name = "cornflowerblue", .code = 0x6495ed },
373       { .name = "cornsilk", .code = 0xfff8dc },
374       { .name = "crimson", .code = 0xdc143c },
375       { .name = "cyan", .code = 0x00ffff },
376       { .name = "darkblue", .code = 0x00008b },
377       { .name = "darkcyan", .code = 0x008b8b },
378       { .name = "darkgoldenrod", .code = 0xb8860b },
379       { .name = "darkgray", .code = 0xa9a9a9 },
380       { .name = "darkgreen", .code = 0x006400 },
381       { .name = "darkgrey", .code = 0xa9a9a9 },
382       { .name = "darkkhaki", .code = 0xbdb76b },
383       { .name = "darkmagenta", .code = 0x8b008b },
384       { .name = "darkolivegreen", .code = 0x556b2f },
385       { .name = "darkorange", .code = 0xff8c00 },
386       { .name = "darkorchid", .code = 0x9932cc },
387       { .name = "darkred", .code = 0x8b0000 },
388       { .name = "darksalmon", .code = 0xe9967a },
389       { .name = "darkseagreen", .code = 0x8fbc8f },
390       { .name = "darkslateblue", .code = 0x483d8b },
391       { .name = "darkslategray", .code = 0x2f4f4f },
392       { .name = "darkslategrey", .code = 0x2f4f4f },
393       { .name = "darkturquoise", .code = 0x00ced1 },
394       { .name = "darkviolet", .code = 0x9400d3 },
395       { .name = "deeppink", .code = 0xff1493 },
396       { .name = "deepskyblue", .code = 0x00bfff },
397       { .name = "dimgray", .code = 0x696969 },
398       { .name = "dimgrey", .code = 0x696969 },
399       { .name = "dodgerblue", .code = 0x1e90ff },
400       { .name = "firebrick", .code = 0xb22222 },
401       { .name = "floralwhite", .code = 0xfffaf0 },
402       { .name = "forestgreen", .code = 0x228b22 },
403       { .name = "fuchsia", .code = 0xff00ff },
404       { .name = "gainsboro", .code = 0xdcdcdc },
405       { .name = "ghostwhite", .code = 0xf8f8ff },
406       { .name = "gold", .code = 0xffd700 },
407       { .name = "goldenrod", .code = 0xdaa520 },
408       { .name = "gray", .code = 0x808080 },
409       { .name = "green", .code = 0x008000 },
410       { .name = "greenyellow", .code = 0xadff2f },
411       { .name = "grey", .code = 0x808080 },
412       { .name = "honeydew", .code = 0xf0fff0 },
413       { .name = "hotpink", .code = 0xff69b4 },
414       { .name = "indianred", .code = 0xcd5c5c },
415       { .name = "indigo", .code = 0x4b0082 },
416       { .name = "ivory", .code = 0xfffff0 },
417       { .name = "khaki", .code = 0xf0e68c },
418       { .name = "lavender", .code = 0xe6e6fa },
419       { .name = "lavenderblush", .code = 0xfff0f5 },
420       { .name = "lawngreen", .code = 0x7cfc00 },
421       { .name = "lemonchiffon", .code = 0xfffacd },
422       { .name = "lightblue", .code = 0xadd8e6 },
423       { .name = "lightcoral", .code = 0xf08080 },
424       { .name = "lightcyan", .code = 0xe0ffff },
425       { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
426       { .name = "lightgray", .code = 0xd3d3d3 },
427       { .name = "lightgreen", .code = 0x90ee90 },
428       { .name = "lightgrey", .code = 0xd3d3d3 },
429       { .name = "lightpink", .code = 0xffb6c1 },
430       { .name = "lightsalmon", .code = 0xffa07a },
431       { .name = "lightseagreen", .code = 0x20b2aa },
432       { .name = "lightskyblue", .code = 0x87cefa },
433       { .name = "lightslategray", .code = 0x778899 },
434       { .name = "lightslategrey", .code = 0x778899 },
435       { .name = "lightsteelblue", .code = 0xb0c4de },
436       { .name = "lightyellow", .code = 0xffffe0 },
437       { .name = "lime", .code = 0x00ff00 },
438       { .name = "limegreen", .code = 0x32cd32 },
439       { .name = "linen", .code = 0xfaf0e6 },
440       { .name = "magenta", .code = 0xff00ff },
441       { .name = "maroon", .code = 0x800000 },
442       { .name = "mediumaquamarine", .code = 0x66cdaa },
443       { .name = "mediumblue", .code = 0x0000cd },
444       { .name = "mediumorchid", .code = 0xba55d3 },
445       { .name = "mediumpurple", .code = 0x9370db },
446       { .name = "mediumseagreen", .code = 0x3cb371 },
447       { .name = "mediumslateblue", .code = 0x7b68ee },
448       { .name = "mediumspringgreen", .code = 0x00fa9a },
449       { .name = "mediumturquoise", .code = 0x48d1cc },
450       { .name = "mediumvioletred", .code = 0xc71585 },
451       { .name = "midnightblue", .code = 0x191970 },
452       { .name = "mintcream", .code = 0xf5fffa },
453       { .name = "mistyrose", .code = 0xffe4e1 },
454       { .name = "moccasin", .code = 0xffe4b5 },
455       { .name = "navajowhite", .code = 0xffdead },
456       { .name = "navy", .code = 0x000080 },
457       { .name = "oldlace", .code = 0xfdf5e6 },
458       { .name = "olive", .code = 0x808000 },
459       { .name = "olivedrab", .code = 0x6b8e23 },
460       { .name = "orange", .code = 0xffa500 },
461       { .name = "orangered", .code = 0xff4500 },
462       { .name = "orchid", .code = 0xda70d6 },
463       { .name = "palegoldenrod", .code = 0xeee8aa },
464       { .name = "palegreen", .code = 0x98fb98 },
465       { .name = "paleturquoise", .code = 0xafeeee },
466       { .name = "palevioletred", .code = 0xdb7093 },
467       { .name = "papayawhip", .code = 0xffefd5 },
468       { .name = "peachpuff", .code = 0xffdab9 },
469       { .name = "peru", .code = 0xcd853f },
470       { .name = "pink", .code = 0xffc0cb },
471       { .name = "plum", .code = 0xdda0dd },
472       { .name = "powderblue", .code = 0xb0e0e6 },
473       { .name = "purple", .code = 0x800080 },
474       { .name = "red", .code = 0xff0000 },
475       { .name = "rosybrown", .code = 0xbc8f8f },
476       { .name = "royalblue", .code = 0x4169e1 },
477       { .name = "saddlebrown", .code = 0x8b4513 },
478       { .name = "salmon", .code = 0xfa8072 },
479       { .name = "sandybrown", .code = 0xf4a460 },
480       { .name = "seagreen", .code = 0x2e8b57 },
481       { .name = "seashell", .code = 0xfff5ee },
482       { .name = "sienna", .code = 0xa0522d },
483       { .name = "silver", .code = 0xc0c0c0 },
484       { .name = "skyblue", .code = 0x87ceeb },
485       { .name = "slateblue", .code = 0x6a5acd },
486       { .name = "slategray", .code = 0x708090 },
487       { .name = "slategrey", .code = 0x708090 },
488       { .name = "snow", .code = 0xfffafa },
489       { .name = "springgreen", .code = 0x00ff7f },
490       { .name = "steelblue", .code = 0x4682b4 },
491       { .name = "tan", .code = 0xd2b48c },
492       { .name = "teal", .code = 0x008080 },
493       { .name = "thistle", .code = 0xd8bfd8 },
494       { .name = "tomato", .code = 0xff6347 },
495       { .name = "turquoise", .code = 0x40e0d0 },
496       { .name = "violet", .code = 0xee82ee },
497       { .name = "wheat", .code = 0xf5deb3 },
498       { .name = "white", .code = 0xffffff },
499       { .name = "whitesmoke", .code = 0xf5f5f5 },
500       { .name = "yellow", .code = 0xffff00 },
501       { .name = "yellowgreen", .code = 0x9acd32 },
502     };
503
504   static struct hmap color_table = HMAP_INITIALIZER (color_table);
505
506   if (hmap_is_empty (&color_table))
507     for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
508       hmap_insert (&color_table, &colors[i].hmap_node,
509                    hash_string (colors[i].name, 0));
510
511   const struct color *color;
512   HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
513                            hash_string (s, 0), &color_table)
514     if (!strcmp (color->name, s))
515       return color->code;
516   return -1;
517 }
518
519 bool
520 parse_color__ (const char *s, struct cell_color *color)
521 {
522   /* #rrrrggggbbbb */
523   uint16_t r16, g16, b16;
524   int len;
525   if (sscanf (s, "#%4"SCNx16"%4"SCNx16"%4"SCNx16"%n",
526               &r16, &g16, &b16, &len) == 3
527       && len == 13
528       && !s[len])
529     {
530       color->r = r16 >> 8;
531       color->g = g16 >> 8;
532       color->b = b16 >> 8;
533       color->alpha = 255;
534       return true;
535     }
536
537   /* #rrggbb */
538   uint8_t r, g, b;
539   if (sscanf (s, "#%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
540       && len == 7
541       && !s[len])
542     {
543       color->r = r;
544       color->g = g;
545       color->b = b;
546       color->alpha = 255;
547       return true;
548     }
549
550   /* rrggbb */
551   if (sscanf (s, "%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
552       && len == 6
553       && !s[len])
554     {
555       color->r = r;
556       color->g = g;
557       color->b = b;
558       color->alpha = 255;
559       return true;
560     }
561
562   /* rgb(r,g,b) */
563   if (sscanf (s, "rgb (%"SCNi8" , %"SCNi8" , %"SCNi8") %n",
564               &r, &g, &b, &len) == 3
565       && !s[len])
566     {
567       color->r = r;
568       color->g = g;
569       color->b = b;
570       color->alpha = 255;
571       return true;
572     }
573
574   /* rgba(r,g,b,a), ignoring a. */
575   double alpha;
576   if (sscanf (s, "rgba (%"SCNi8" , %"SCNi8" , %"SCNi8", %lf) %n",
577               &r, &g, &b, &alpha, &len) == 4
578       && !s[len])
579     {
580       color->r = r;
581       color->g = g;
582       color->b = b;
583       color->alpha = alpha <= 0 ? 0 : alpha >= 1 ? 255 : alpha * 255.0;
584       return true;
585     }
586
587   int code = lookup_color_name (s);
588   if (code >= 0)
589     {
590       color->r = code >> 16;
591       color->g = code >> 8;
592       color->b = code;
593       color->alpha = 255;
594       return true;
595     }
596
597   if (!strcmp (s, "transparent"))
598     {
599       *color = (struct cell_color) { .alpha = 0 };
600       return true;
601     }
602
603   return false;
604 }
605
606 /* Parses and returns color information from O. */
607 struct cell_color
608 parse_color (struct driver_option *o)
609 {
610   struct cell_color color = CELL_COLOR_BLACK;
611   parse_color__ (o->default_value, &color);
612   if (o->value)
613     {
614       if (!parse_color__ (o->value, &color))
615         msg (MW, _("%s: `%s' is `%s', which could not be parsed as a color"),
616              o->driver_name, o->name, o->value);
617     }
618   driver_option_destroy (o);
619   return color;
620 }
621