1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 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/>. */
19 #include "output/options.h"
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"
36 #include "gl/xalloc.h"
39 #define _(msgid) gettext (msgid)
41 /* Creates and returns a new struct driver_option for driver DRIVER_NAME (which
42 is used only in error messages). The option named NAME is extracted from
43 OPTIONS. DEFAULT_VALUE is the default value of the option, used if the
44 given option was not supplied or was invalid. */
46 driver_option_get (struct driver_options *options,
47 const char *name, const char *default_value)
49 char *value = string_map_find_and_delete (&options->map, name);
51 string_array_append_nocopy (&options->garbage, value);
52 return (struct driver_option) {
53 .driver_name = options->driver_name,
56 .default_value = default_value,
60 /* Stores the paper size of the value of option O into *H and *V, in inches.
61 Any syntax accepted by measure_paper() may be used. */
63 parse_paper_size (struct driver_option o, double *h, double *v)
65 if (!o.value || !measure_paper (o.value, h, v))
66 measure_paper (o.default_value, h, v);
70 do_parse_boolean (const char *driver_name, const char *key,
73 if (!strcmp (value, "on") || !strcmp (value, "true")
74 || !strcmp (value, "yes") || !strcmp (value, "1"))
76 else if (!strcmp (value, "off") || !strcmp (value, "false")
77 || !strcmp (value, "no") || !strcmp (value, "0"))
81 msg (MW, _("%s: `%s' is `%s' but a Boolean value is required"),
82 driver_name, value, key);
87 /* Parses and return O's value as a Boolean value. "true" and "false", "yes"
88 and "no", "on" and "off", and "1" and "0" are acceptable boolean strings. */
90 parse_boolean (struct driver_option o)
92 bool retval = do_parse_boolean (o.driver_name, o.name, o.default_value) > 0;
95 int value = do_parse_boolean (o.driver_name, o.name, o.value);
103 /* Parses O's value as an enumeration constant. The arguments to this function
104 consist of a series of string/int pairs, terminated by a null pointer value.
105 O's value is compared to each string in turn, and parse_enum() returns the
106 int associated with the first matching string. If there is no match, or if
107 O has no user-specified value, then O's default value is treated the same
108 way. If the default value still does not match, parse_enum() returns 0.
110 Example: parse_enum (o, "a", 1, "b", 2) returns 1 if O's value if "a", 2 if
113 (parse_enum) (struct driver_option o, ...)
121 const char *s = va_arg (args, const char *);
126 struct string choices = DS_EMPTY_INITIALIZER;
129 for (int i = 0; ; i++)
131 s = va_arg (args, const char *);
137 ds_put_cstr (&choices, ", ");
138 ds_put_format (&choices, "`%s'", s);
141 msg (MW, _("%s: `%s' is `%s' but one of the following "
143 o.driver_name, o.name, o.value, ds_cstr (&choices));
144 ds_destroy (&choices);
149 int value = va_arg (args, int);
150 if (o.value && !strcmp (s, o.value))
155 else if (!strcmp (s, o.default_value))
162 /* Parses O's value as an integer in the range MIN_VALUE to MAX_VALUE
163 (inclusive) and returns the integer. */
165 parse_int (struct driver_option o, int min_value, int max_value)
167 int retval = strtol (o.default_value, NULL, 0);
174 int value = strtol (o.value, &tail, 0);
175 if (tail != o.value && *tail == '\0' && errno != ERANGE
176 && value >= min_value && value <= max_value)
178 else if (max_value == INT_MAX)
181 msg (MW, _("%s: `%s' is `%s' but a non-negative integer "
183 o.driver_name, o.name, o.value);
184 else if (min_value == 1)
185 msg (MW, _("%s: `%s' is `%s' but a positive integer is "
186 "required"), o.driver_name, o.name, o.value);
187 else if (min_value == INT_MIN)
188 msg (MW, _("%s: `%s' is `%s' but an integer is required"),
189 o.driver_name, o.name, o.value);
191 msg (MW, _("%s: `%s' is `%s' but an integer greater "
192 "than %d is required"),
193 o.driver_name, o.name, o.value, min_value - 1);
196 msg (MW, _("%s: `%s' is `%s' but an integer between %d and "
198 o.driver_name, o.name, o.value, min_value, max_value);
203 /* Parses O's value as a dimension, as understood by measure_dimension(), and
204 returns its length in inches. */
206 parse_dimension (struct driver_option o)
208 return (o.value ? measure_dimension (o.value)
209 : o.default_value ? measure_dimension (o.default_value)
213 /* Parses O's value as a string and returns it as a malloc'd string that the
214 caller is responsible for freeing. */
216 parse_string (struct driver_option o)
218 return xstrdup (o.value ? o.value : o.default_value);
222 default_chart_file_name (const char *file_name)
224 if (!strcmp (file_name, "-"))
227 const char *extension = strrchr (file_name, '.');
228 int stem_length = extension ? extension - file_name : strlen (file_name);
229 return xasprintf ("%.*s-#", stem_length, file_name);
232 /* Parses and returns a chart file name, or NULL if no charts should be output.
233 If a nonnull string is returned, it will contain at least one '#' character,
234 which the client will presumably replace by a number as part of writing
235 charts to separate files.
237 If o.value is "none", then this function returns NULL.
239 If o.value is non-NULL but not "none", returns a copy of that string (if it
242 If o.value is NULL, then O's default_value should be the name of the main
243 output file. Returns NULL if default_value is "-", and otherwise returns a
244 copy of string string with its extension stripped off and "-#.png"
247 parse_chart_file_name (struct driver_option o)
250 return default_chart_file_name (o.default_value);
251 else if (!strcmp (o.value, "none"))
253 else if (strchr (o.value, '#') != NULL)
254 return xstrdup (o.value);
257 msg (MW, _("%s: `%s' is `%s' but a file name that contains "
259 o.driver_name, o.name, o.value);
260 return default_chart_file_name (o.default_value);
265 lookup_color_name (const char *s)
269 struct hmap_node hmap_node;
274 static struct color colors[] =
276 { .name = "aliceblue", .code = 0xf0f8ff },
277 { .name = "antiquewhite", .code = 0xfaebd7 },
278 { .name = "aqua", .code = 0x00ffff },
279 { .name = "aquamarine", .code = 0x7fffd4 },
280 { .name = "azure", .code = 0xf0ffff },
281 { .name = "beige", .code = 0xf5f5dc },
282 { .name = "bisque", .code = 0xffe4c4 },
283 { .name = "black", .code = 0x000000 },
284 { .name = "blanchedalmond", .code = 0xffebcd },
285 { .name = "blue", .code = 0x0000ff },
286 { .name = "blueviolet", .code = 0x8a2be2 },
287 { .name = "brown", .code = 0xa52a2a },
288 { .name = "burlywood", .code = 0xdeb887 },
289 { .name = "cadetblue", .code = 0x5f9ea0 },
290 { .name = "chartreuse", .code = 0x7fff00 },
291 { .name = "chocolate", .code = 0xd2691e },
292 { .name = "coral", .code = 0xff7f50 },
293 { .name = "cornflowerblue", .code = 0x6495ed },
294 { .name = "cornsilk", .code = 0xfff8dc },
295 { .name = "crimson", .code = 0xdc143c },
296 { .name = "cyan", .code = 0x00ffff },
297 { .name = "darkblue", .code = 0x00008b },
298 { .name = "darkcyan", .code = 0x008b8b },
299 { .name = "darkgoldenrod", .code = 0xb8860b },
300 { .name = "darkgray", .code = 0xa9a9a9 },
301 { .name = "darkgreen", .code = 0x006400 },
302 { .name = "darkgrey", .code = 0xa9a9a9 },
303 { .name = "darkkhaki", .code = 0xbdb76b },
304 { .name = "darkmagenta", .code = 0x8b008b },
305 { .name = "darkolivegreen", .code = 0x556b2f },
306 { .name = "darkorange", .code = 0xff8c00 },
307 { .name = "darkorchid", .code = 0x9932cc },
308 { .name = "darkred", .code = 0x8b0000 },
309 { .name = "darksalmon", .code = 0xe9967a },
310 { .name = "darkseagreen", .code = 0x8fbc8f },
311 { .name = "darkslateblue", .code = 0x483d8b },
312 { .name = "darkslategray", .code = 0x2f4f4f },
313 { .name = "darkslategrey", .code = 0x2f4f4f },
314 { .name = "darkturquoise", .code = 0x00ced1 },
315 { .name = "darkviolet", .code = 0x9400d3 },
316 { .name = "deeppink", .code = 0xff1493 },
317 { .name = "deepskyblue", .code = 0x00bfff },
318 { .name = "dimgray", .code = 0x696969 },
319 { .name = "dimgrey", .code = 0x696969 },
320 { .name = "dodgerblue", .code = 0x1e90ff },
321 { .name = "firebrick", .code = 0xb22222 },
322 { .name = "floralwhite", .code = 0xfffaf0 },
323 { .name = "forestgreen", .code = 0x228b22 },
324 { .name = "fuchsia", .code = 0xff00ff },
325 { .name = "gainsboro", .code = 0xdcdcdc },
326 { .name = "ghostwhite", .code = 0xf8f8ff },
327 { .name = "gold", .code = 0xffd700 },
328 { .name = "goldenrod", .code = 0xdaa520 },
329 { .name = "gray", .code = 0x808080 },
330 { .name = "green", .code = 0x008000 },
331 { .name = "greenyellow", .code = 0xadff2f },
332 { .name = "grey", .code = 0x808080 },
333 { .name = "honeydew", .code = 0xf0fff0 },
334 { .name = "hotpink", .code = 0xff69b4 },
335 { .name = "indianred", .code = 0xcd5c5c },
336 { .name = "indigo", .code = 0x4b0082 },
337 { .name = "ivory", .code = 0xfffff0 },
338 { .name = "khaki", .code = 0xf0e68c },
339 { .name = "lavender", .code = 0xe6e6fa },
340 { .name = "lavenderblush", .code = 0xfff0f5 },
341 { .name = "lawngreen", .code = 0x7cfc00 },
342 { .name = "lemonchiffon", .code = 0xfffacd },
343 { .name = "lightblue", .code = 0xadd8e6 },
344 { .name = "lightcoral", .code = 0xf08080 },
345 { .name = "lightcyan", .code = 0xe0ffff },
346 { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
347 { .name = "lightgray", .code = 0xd3d3d3 },
348 { .name = "lightgreen", .code = 0x90ee90 },
349 { .name = "lightgrey", .code = 0xd3d3d3 },
350 { .name = "lightpink", .code = 0xffb6c1 },
351 { .name = "lightsalmon", .code = 0xffa07a },
352 { .name = "lightseagreen", .code = 0x20b2aa },
353 { .name = "lightskyblue", .code = 0x87cefa },
354 { .name = "lightslategray", .code = 0x778899 },
355 { .name = "lightslategrey", .code = 0x778899 },
356 { .name = "lightsteelblue", .code = 0xb0c4de },
357 { .name = "lightyellow", .code = 0xffffe0 },
358 { .name = "lime", .code = 0x00ff00 },
359 { .name = "limegreen", .code = 0x32cd32 },
360 { .name = "linen", .code = 0xfaf0e6 },
361 { .name = "magenta", .code = 0xff00ff },
362 { .name = "maroon", .code = 0x800000 },
363 { .name = "mediumaquamarine", .code = 0x66cdaa },
364 { .name = "mediumblue", .code = 0x0000cd },
365 { .name = "mediumorchid", .code = 0xba55d3 },
366 { .name = "mediumpurple", .code = 0x9370db },
367 { .name = "mediumseagreen", .code = 0x3cb371 },
368 { .name = "mediumslateblue", .code = 0x7b68ee },
369 { .name = "mediumspringgreen", .code = 0x00fa9a },
370 { .name = "mediumturquoise", .code = 0x48d1cc },
371 { .name = "mediumvioletred", .code = 0xc71585 },
372 { .name = "midnightblue", .code = 0x191970 },
373 { .name = "mintcream", .code = 0xf5fffa },
374 { .name = "mistyrose", .code = 0xffe4e1 },
375 { .name = "moccasin", .code = 0xffe4b5 },
376 { .name = "navajowhite", .code = 0xffdead },
377 { .name = "navy", .code = 0x000080 },
378 { .name = "oldlace", .code = 0xfdf5e6 },
379 { .name = "olive", .code = 0x808000 },
380 { .name = "olivedrab", .code = 0x6b8e23 },
381 { .name = "orange", .code = 0xffa500 },
382 { .name = "orangered", .code = 0xff4500 },
383 { .name = "orchid", .code = 0xda70d6 },
384 { .name = "palegoldenrod", .code = 0xeee8aa },
385 { .name = "palegreen", .code = 0x98fb98 },
386 { .name = "paleturquoise", .code = 0xafeeee },
387 { .name = "palevioletred", .code = 0xdb7093 },
388 { .name = "papayawhip", .code = 0xffefd5 },
389 { .name = "peachpuff", .code = 0xffdab9 },
390 { .name = "peru", .code = 0xcd853f },
391 { .name = "pink", .code = 0xffc0cb },
392 { .name = "plum", .code = 0xdda0dd },
393 { .name = "powderblue", .code = 0xb0e0e6 },
394 { .name = "purple", .code = 0x800080 },
395 { .name = "red", .code = 0xff0000 },
396 { .name = "rosybrown", .code = 0xbc8f8f },
397 { .name = "royalblue", .code = 0x4169e1 },
398 { .name = "saddlebrown", .code = 0x8b4513 },
399 { .name = "salmon", .code = 0xfa8072 },
400 { .name = "sandybrown", .code = 0xf4a460 },
401 { .name = "seagreen", .code = 0x2e8b57 },
402 { .name = "seashell", .code = 0xfff5ee },
403 { .name = "sienna", .code = 0xa0522d },
404 { .name = "silver", .code = 0xc0c0c0 },
405 { .name = "skyblue", .code = 0x87ceeb },
406 { .name = "slateblue", .code = 0x6a5acd },
407 { .name = "slategray", .code = 0x708090 },
408 { .name = "slategrey", .code = 0x708090 },
409 { .name = "snow", .code = 0xfffafa },
410 { .name = "springgreen", .code = 0x00ff7f },
411 { .name = "steelblue", .code = 0x4682b4 },
412 { .name = "tan", .code = 0xd2b48c },
413 { .name = "teal", .code = 0x008080 },
414 { .name = "thistle", .code = 0xd8bfd8 },
415 { .name = "tomato", .code = 0xff6347 },
416 { .name = "turquoise", .code = 0x40e0d0 },
417 { .name = "violet", .code = 0xee82ee },
418 { .name = "wheat", .code = 0xf5deb3 },
419 { .name = "white", .code = 0xffffff },
420 { .name = "whitesmoke", .code = 0xf5f5f5 },
421 { .name = "yellow", .code = 0xffff00 },
422 { .name = "yellowgreen", .code = 0x9acd32 },
425 static struct hmap color_table = HMAP_INITIALIZER (color_table);
427 if (hmap_is_empty (&color_table))
428 for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
429 hmap_insert (&color_table, &colors[i].hmap_node,
430 hash_string (colors[i].name, 0));
432 const struct color *color;
433 HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
434 hash_string (s, 0), &color_table)
435 if (!strcmp (color->name, s))
441 parse_color__ (const char *s, struct cell_color *color)
444 uint16_t r16, g16, b16;
446 if (sscanf (s, "#%4"SCNx16"%4"SCNx16"%4"SCNx16"%n",
447 &r16, &g16, &b16, &len) == 3
460 if (sscanf (s, "#%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
472 if (sscanf (s, "%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
484 if (sscanf (s, "rgb (%"SCNi8" , %"SCNi8" , %"SCNi8") %n",
485 &r, &g, &b, &len) == 3
495 /* rgba(r,g,b,a), ignoring a. */
497 if (sscanf (s, "rgba (%"SCNi8" , %"SCNi8" , %"SCNi8", %lf) %n",
498 &r, &g, &b, &alpha, &len) == 4
504 color->alpha = alpha <= 0 ? 0 : alpha >= 1 ? 255 : alpha * 255.0;
508 int code = lookup_color_name (s);
511 color->r = code >> 16;
512 color->g = code >> 8;
518 if (!strcmp (s, "transparent"))
520 *color = (struct cell_color) { .alpha = 0 };
527 /* Parses and returns color information from O. */
529 parse_color (struct driver_option o)
531 struct cell_color color = CELL_COLOR_BLACK;
532 parse_color__ (o.default_value, &color);
533 if (o.value && !parse_color__ (o.value, &color))
534 msg (MW, _("%s: `%s' is `%s', which could not be parsed as a color"),
535 o.driver_name, o.name, o.value);