1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library 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 GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 #include "gtkcssparserprivate.h"
21 #include "gtkwin32themeprivate.h"
26 /* just for the errors, yay! */
27 #include "gtkcssprovider.h"
29 #define NEWLINE_CHARS "\r\n"
30 #define WHITESPACE_CHARS "\f \t"
31 #define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
32 #define NMCHAR NMSTART "01234567890-_"
33 #define URLCHAR NMCHAR "!#$%&*~"
35 #define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
40 GtkCssParserErrorFunc error_func;
43 const char *line_start;
48 _gtk_css_parser_new (const char *data,
49 GtkCssParserErrorFunc error_func,
54 g_return_val_if_fail (data != NULL, NULL);
56 parser = g_slice_new0 (GtkCssParser);
59 parser->error_func = error_func;
60 parser->user_data = user_data;
62 parser->line_start = data;
69 _gtk_css_parser_free (GtkCssParser *parser)
71 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
73 g_slice_free (GtkCssParser, parser);
77 _gtk_css_parser_is_eof (GtkCssParser *parser)
79 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
81 return *parser->data == 0;
85 _gtk_css_parser_begins_with (GtkCssParser *parser,
88 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
90 return *parser->data == c;
94 _gtk_css_parser_has_prefix (GtkCssParser *parser,
97 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
99 return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
103 _gtk_css_parser_get_line (GtkCssParser *parser)
105 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
111 _gtk_css_parser_get_position (GtkCssParser *parser)
113 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
115 return parser->data - parser->line_start;
119 _gtk_css_parser_take_error (GtkCssParser *parser,
122 parser->error_func (parser, error, parser->user_data);
124 g_error_free (error);
128 _gtk_css_parser_error (GtkCssParser *parser,
136 va_start (args, format);
137 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
138 GTK_CSS_PROVIDER_ERROR_SYNTAX,
142 _gtk_css_parser_take_error (parser, error);
146 _gtk_css_parser_error_full (GtkCssParser *parser,
147 GtkCssProviderError code,
155 va_start (args, format);
156 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
160 _gtk_css_parser_take_error (parser, error);
163 gtk_css_parser_new_line (GtkCssParser *parser)
165 gboolean result = FALSE;
167 if (*parser->data == '\r')
172 if (*parser->data == '\n')
181 parser->line_start = parser->data;
188 gtk_css_parser_skip_comment (GtkCssParser *parser)
190 if (parser->data[0] != '/' ||
191 parser->data[1] != '*')
196 while (*parser->data)
198 gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
202 if (gtk_css_parser_new_line (parser))
207 if (parser->data[-2] == '*')
209 if (parser->data[0] == '*')
210 _gtk_css_parser_error (parser, "'/*' in comment block");
213 /* FIXME: position */
214 _gtk_css_parser_error (parser, "Unterminated comment");
219 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
223 while (*parser->data)
225 if (gtk_css_parser_new_line (parser))
228 len = strspn (parser->data, WHITESPACE_CHARS);
235 if (!gtk_css_parser_skip_comment (parser))
241 _gtk_css_parser_try (GtkCssParser *parser,
243 gboolean skip_whitespace)
245 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
246 g_return_val_if_fail (string != NULL, FALSE);
248 if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
251 parser->data += strlen (string);
254 _gtk_css_parser_skip_whitespace (parser);
270 _gtk_css_parser_unescape (GtkCssParser *parser,
276 g_assert (*parser->data == '\\');
280 for (i = 0; i < 6; i++)
282 if (!g_ascii_isxdigit (parser->data[i]))
285 result = (result << 4) + get_xdigit (parser->data[i]);
290 g_string_append_unichar (str, result);
293 /* NB: gtk_css_parser_new_line() forward data pointer itself */
294 if (!gtk_css_parser_new_line (parser) &&
296 strchr (WHITESPACE_CHARS, *parser->data))
301 if (gtk_css_parser_new_line (parser))
304 g_string_append_c (str, *parser->data);
311 _gtk_css_parser_read_char (GtkCssParser *parser,
313 const char * allowed)
315 if (*parser->data == 0)
318 if (strchr (allowed, *parser->data))
320 g_string_append_c (str, *parser->data);
324 if (*parser->data >= 127)
326 gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
328 g_string_append_len (str, parser->data, len);
332 if (*parser->data == '\\')
334 _gtk_css_parser_unescape (parser, str);
342 _gtk_css_parser_try_name (GtkCssParser *parser,
343 gboolean skip_whitespace)
347 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
349 name = g_string_new (NULL);
351 while (_gtk_css_parser_read_char (parser, name, NMCHAR))
355 _gtk_css_parser_skip_whitespace (parser);
357 return g_string_free (name, FALSE);
361 _gtk_css_parser_try_ident (GtkCssParser *parser,
362 gboolean skip_whitespace)
367 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
369 start = parser->data;
371 ident = g_string_new (NULL);
373 if (*parser->data == '-')
375 g_string_append_c (ident, '-');
379 if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
381 parser->data = start;
382 g_string_free (ident, TRUE);
386 while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
390 _gtk_css_parser_skip_whitespace (parser);
392 return g_string_free (ident, FALSE);
396 _gtk_css_parser_is_string (GtkCssParser *parser)
398 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
400 return *parser->data == '"' || *parser->data == '\'';
404 _gtk_css_parser_read_string (GtkCssParser *parser)
409 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
411 quote = *parser->data;
413 if (quote != '"' && quote != '\'')
415 _gtk_css_parser_error (parser, "Expected a string.");
420 str = g_string_new (NULL);
424 gsize len = strcspn (parser->data, "\\'\"\n\r\f");
426 g_string_append_len (str, parser->data, len);
430 switch (*parser->data)
433 _gtk_css_parser_unescape (parser, str);
437 if (*parser->data == quote)
440 _gtk_css_parser_skip_whitespace (parser);
441 return g_string_free (str, FALSE);
444 g_string_append_c (str, *parser->data);
448 /* FIXME: position */
449 _gtk_css_parser_error (parser, "Missing end quote in string.");
450 g_string_free (str, TRUE);
453 _gtk_css_parser_error (parser,
454 "Invalid character in string. Must be escaped.");
455 g_string_free (str, TRUE);
460 g_assert_not_reached ();
465 _gtk_css_parser_try_int (GtkCssParser *parser,
471 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
472 g_return_val_if_fail (value != NULL, FALSE);
474 /* strtoll parses a plus, but we are not allowed to */
475 if (*parser->data == '+')
479 result = g_ascii_strtoll (parser->data, &end, 10);
482 if (result > G_MAXINT || result < G_MININT)
484 if (parser->data == end)
490 _gtk_css_parser_skip_whitespace (parser);
496 _gtk_css_parser_try_uint (GtkCssParser *parser,
502 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
503 g_return_val_if_fail (value != NULL, FALSE);
506 result = g_ascii_strtoull (parser->data, &end, 10);
509 if (result > G_MAXUINT)
511 if (parser->data == end)
517 _gtk_css_parser_skip_whitespace (parser);
523 _gtk_css_parser_try_double (GtkCssParser *parser,
529 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
530 g_return_val_if_fail (value != NULL, FALSE);
533 result = g_ascii_strtod (parser->data, &end);
536 if (parser->data == end)
542 _gtk_css_parser_skip_whitespace (parser);
548 _gtk_css_parser_has_number (GtkCssParser *parser)
551 return strchr ("+-0123456789.", parser->data[0]) != NULL;
555 _gtk_css_parser_read_number (GtkCssParser *parser,
556 GtkCssNumber *number,
557 GtkCssNumberParseFlags flags)
559 static const struct {
562 GtkCssNumberParseFlags required_flags;
564 { "px", GTK_CSS_PX, GTK_CSS_PARSE_LENGTH },
565 { "pt", GTK_CSS_PT, GTK_CSS_PARSE_LENGTH },
566 { "em", GTK_CSS_EM, GTK_CSS_PARSE_LENGTH },
567 { "ex", GTK_CSS_EX, GTK_CSS_PARSE_LENGTH },
568 { "pc", GTK_CSS_PC, GTK_CSS_PARSE_LENGTH },
569 { "in", GTK_CSS_IN, GTK_CSS_PARSE_LENGTH },
570 { "cm", GTK_CSS_CM, GTK_CSS_PARSE_LENGTH },
571 { "mm", GTK_CSS_MM, GTK_CSS_PARSE_LENGTH },
572 { "rad", GTK_CSS_RAD, GTK_CSS_PARSE_ANGLE },
573 { "deg", GTK_CSS_DEG, GTK_CSS_PARSE_ANGLE },
574 { "grad", GTK_CSS_GRAD, GTK_CSS_PARSE_ANGLE },
575 { "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE },
576 { "s", GTK_CSS_S, GTK_CSS_PARSE_TIME },
577 { "ms", GTK_CSS_MS, GTK_CSS_PARSE_TIME }
581 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
582 g_return_val_if_fail (number != NULL, FALSE);
585 number->unit = GTK_CSS_NUMBER;
586 number->value = g_ascii_strtod (parser->data, &end);
589 _gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
592 if (parser->data == end)
594 _gtk_css_parser_error (parser, "not a number");
600 if (flags & GTK_CSS_POSITIVE_ONLY &&
603 _gtk_css_parser_error (parser, "negative values are not allowed.");
607 unit = _gtk_css_parser_try_ident (parser, FALSE);
613 for (i = 0; i < G_N_ELEMENTS (units); i++)
615 if (flags & units[i].required_flags &&
616 g_ascii_strcasecmp (unit, units[i].name) == 0)
620 if (i >= G_N_ELEMENTS (units))
622 _gtk_css_parser_error (parser, "`%s' is not a valid unit.", unit);
627 number->unit = units[i].unit;
632 if ((flags & GTK_CSS_PARSE_PERCENT) &&
633 _gtk_css_parser_try (parser, "%", FALSE))
635 number->unit = GTK_CSS_PERCENT;
637 else if (number->value == 0.0)
639 if (flags & GTK_CSS_PARSE_NUMBER)
640 number->unit = GTK_CSS_NUMBER;
641 else if (flags & GTK_CSS_PARSE_LENGTH)
642 number->unit = GTK_CSS_PX;
644 number->unit = GTK_CSS_PERCENT;
646 else if (flags & GTK_CSS_NUMBER_AS_PIXELS)
648 _gtk_css_parser_error_full (parser,
649 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
650 "Not using units is deprecated. Assuming 'px'.");
651 number->unit = GTK_CSS_PX;
653 else if (flags & GTK_CSS_PARSE_NUMBER)
655 number->unit = GTK_CSS_NUMBER;
659 _gtk_css_parser_error (parser, "Unit is missing.");
664 _gtk_css_parser_skip_whitespace (parser);
669 /* XXX: we should introduce GtkCssLenght that deals with
670 * different kind of units */
672 _gtk_css_parser_try_length (GtkCssParser *parser,
675 if (!_gtk_css_parser_try_int (parser, value))
678 /* FIXME: _try_uint skips spaces while the
681 _gtk_css_parser_try (parser, "px", TRUE);
687 _gtk_css_parser_try_enum (GtkCssParser *parser,
691 GEnumClass *enum_class;
696 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
697 g_return_val_if_fail (value != NULL, FALSE);
701 enum_class = g_type_class_ref (enum_type);
703 start = parser->data;
705 str = _gtk_css_parser_try_ident (parser, TRUE);
709 if (enum_class->n_values)
711 GEnumValue *enum_value;
713 for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
715 if (enum_value->value_nick &&
716 g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
718 *value = enum_value->value;
726 g_type_class_unref (enum_class);
729 parser->data = start;
745 static GtkSymbolicColor *
746 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
749 GtkSymbolicColor *symbolic;
750 GtkSymbolicColor *child1, *child2;
753 if (!_gtk_css_parser_try (parser, "(", TRUE))
755 _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
759 if (color == COLOR_RGB || color == COLOR_RGBA)
765 for (i = 0; i < 3; i++)
767 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
769 _gtk_css_parser_error (parser, "Expected ',' in color definition");
773 if (!_gtk_css_parser_try_double (parser, &tmp))
775 _gtk_css_parser_error (parser, "Invalid number for color value");
778 if (_gtk_css_parser_try (parser, "%", TRUE))
789 g_assert_not_reached ();
792 if (color == COLOR_RGBA)
794 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
796 _gtk_css_parser_error (parser, "Expected ',' in color definition");
800 if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
802 _gtk_css_parser_error (parser, "Invalid number for alpha value");
809 symbolic = gtk_symbolic_color_new_literal (&rgba);
811 else if (color == COLOR_WIN32)
813 symbolic = _gtk_win32_theme_color_parse (parser);
814 if (symbolic == NULL)
819 child1 = _gtk_css_parser_read_symbolic_color (parser);
823 if (color == COLOR_MIX)
825 if (!_gtk_css_parser_try (parser, ",", TRUE))
827 _gtk_css_parser_error (parser, "Expected ',' in color definition");
828 gtk_symbolic_color_unref (child1);
832 child2 = _gtk_css_parser_read_symbolic_color (parser);
835 gtk_symbolic_color_unref (child1);
842 if (color == COLOR_LIGHTER)
844 else if (color == COLOR_DARKER)
848 if (!_gtk_css_parser_try (parser, ",", TRUE))
850 _gtk_css_parser_error (parser, "Expected ',' in color definition");
851 gtk_symbolic_color_unref (child1);
853 gtk_symbolic_color_unref (child2);
857 if (!_gtk_css_parser_try_double (parser, &value))
859 _gtk_css_parser_error (parser, "Expected number in color definition");
860 gtk_symbolic_color_unref (child1);
862 gtk_symbolic_color_unref (child2);
872 symbolic = gtk_symbolic_color_new_shade (child1, value);
875 symbolic = gtk_symbolic_color_new_alpha (child1, value);
878 symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
881 g_assert_not_reached ();
885 gtk_symbolic_color_unref (child1);
887 gtk_symbolic_color_unref (child2);
890 if (!_gtk_css_parser_try (parser, ")", TRUE))
892 _gtk_css_parser_error (parser, "Expected ')' in color definition");
893 gtk_symbolic_color_unref (symbolic);
900 static GtkSymbolicColor *
901 gtk_css_parser_try_hash_color (GtkCssParser *parser)
903 if (parser->data[0] == '#' &&
904 g_ascii_isxdigit (parser->data[1]) &&
905 g_ascii_isxdigit (parser->data[2]) &&
906 g_ascii_isxdigit (parser->data[3]))
910 if (g_ascii_isxdigit (parser->data[4]) &&
911 g_ascii_isxdigit (parser->data[5]) &&
912 g_ascii_isxdigit (parser->data[6]))
914 rgba.red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
915 rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
916 rgba.blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
922 rgba.red = get_xdigit (parser->data[1]) / 15.0;
923 rgba.green = get_xdigit (parser->data[2]) / 15.0;
924 rgba.blue = get_xdigit (parser->data[3]) / 15.0;
929 _gtk_css_parser_skip_whitespace (parser);
931 return gtk_symbolic_color_new_literal (&rgba);
938 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
940 GtkSymbolicColor *symbolic;
942 const char *names[] = {"rgba", "rgb", "lighter", "darker", "shade", "alpha", "mix",
943 GTK_WIN32_THEME_SYMBOLIC_COLOR_NAME};
946 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
948 if (_gtk_css_parser_try (parser, "transparent", TRUE))
950 GdkRGBA transparent = { 0, 0, 0, 0 };
952 return gtk_symbolic_color_new_literal (&transparent);
955 if (_gtk_css_parser_try (parser, "@", FALSE))
957 name = _gtk_css_parser_try_name (parser, TRUE);
961 symbolic = gtk_symbolic_color_new_name (name);
965 _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
973 for (color = 0; color < G_N_ELEMENTS (names); color++)
975 if (_gtk_css_parser_try (parser, names[color], TRUE))
979 if (color < G_N_ELEMENTS (names))
980 return gtk_css_parser_read_symbolic_color_function (parser, color);
982 symbolic = gtk_css_parser_try_hash_color (parser);
986 name = _gtk_css_parser_try_name (parser, TRUE);
991 if (gdk_rgba_parse (&rgba, name))
993 symbolic = gtk_symbolic_color_new_literal (&rgba);
997 _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
1004 _gtk_css_parser_error (parser, "Not a color definition");
1009 _gtk_css_parser_read_url (GtkCssParser *parser,
1016 if (_gtk_css_parser_try (parser, "url", FALSE))
1018 if (!_gtk_css_parser_try (parser, "(", TRUE))
1020 _gtk_css_parser_skip_whitespace (parser);
1021 if (_gtk_css_parser_try (parser, "(", TRUE))
1023 _gtk_css_parser_error_full (parser,
1024 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1025 "Whitespace between 'url' and '(' is deprecated");
1029 _gtk_css_parser_error (parser, "Expected '(' after 'url'");
1034 path = _gtk_css_parser_read_string (parser);
1038 if (!_gtk_css_parser_try (parser, ")", TRUE))
1040 _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
1045 scheme = g_uri_parse_scheme (path);
1048 file = g_file_new_for_uri (path);
1056 path = _gtk_css_parser_try_name (parser, TRUE);
1059 _gtk_css_parser_error (parser, "Not a valid url");
1064 file = g_file_resolve_relative_path (base, path);
1071 _gtk_css_parser_resync_internal (GtkCssParser *parser,
1072 gboolean sync_at_semicolon,
1073 gboolean read_sync_token,
1079 len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
1080 parser->data += len;
1082 if (gtk_css_parser_new_line (parser))
1085 if (_gtk_css_parser_is_string (parser))
1087 /* Hrm, this emits errors, and i suspect it shouldn't... */
1088 char *free_me = _gtk_css_parser_read_string (parser);
1093 if (gtk_css_parser_skip_comment (parser))
1096 switch (*parser->data)
1100 GString *ignore = g_string_new (NULL);
1101 _gtk_css_parser_unescape (parser, ignore);
1102 g_string_free (ignore, TRUE);
1106 if (sync_at_semicolon && !read_sync_token)
1109 if (sync_at_semicolon)
1111 _gtk_css_parser_skip_whitespace (parser);
1117 _gtk_css_parser_resync (parser, FALSE, ')');
1123 _gtk_css_parser_resync (parser, FALSE, ']');
1129 _gtk_css_parser_resync (parser, FALSE, '}');
1132 if (sync_at_semicolon || !terminator)
1134 _gtk_css_parser_skip_whitespace (parser);
1141 if (terminator == *parser->data)
1143 _gtk_css_parser_skip_whitespace (parser);
1155 } while (*parser->data);
1159 _gtk_css_parser_read_value (GtkCssParser *parser)
1164 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
1166 start = parser->data;
1168 /* This needs to be done better */
1169 _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
1171 result = g_strndup (start, parser->data - start);
1174 g_strchomp (result);
1183 _gtk_css_parser_error (parser, "Expected a property value");
1189 _gtk_css_parser_resync (GtkCssParser *parser,
1190 gboolean sync_at_semicolon,
1193 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1195 _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);