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, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
22 #include "gtkcssparserprivate.h"
23 #include "gtkwin32themeprivate.h"
28 /* just for the errors, yay! */
29 #include "gtkcssprovider.h"
31 #define NEWLINE_CHARS "\r\n"
32 #define WHITESPACE_CHARS "\f \t"
33 #define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
34 #define NMCHAR NMSTART "01234567890-_"
35 #define URLCHAR NMCHAR "!#$%&*~"
37 #define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
42 GtkCssParserErrorFunc error_func;
45 const char *line_start;
50 _gtk_css_parser_new (const char *data,
51 GtkCssParserErrorFunc error_func,
56 g_return_val_if_fail (data != NULL, NULL);
58 parser = g_slice_new0 (GtkCssParser);
61 parser->error_func = error_func;
62 parser->user_data = user_data;
64 parser->line_start = data;
71 _gtk_css_parser_free (GtkCssParser *parser)
73 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
75 g_slice_free (GtkCssParser, parser);
79 _gtk_css_parser_is_eof (GtkCssParser *parser)
81 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
83 return *parser->data == 0;
87 _gtk_css_parser_begins_with (GtkCssParser *parser,
90 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
92 return *parser->data == c;
96 _gtk_css_parser_has_prefix (GtkCssParser *parser,
99 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
101 return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
105 _gtk_css_parser_get_line (GtkCssParser *parser)
107 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
113 _gtk_css_parser_get_position (GtkCssParser *parser)
115 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
117 return parser->data - parser->line_start;
121 _gtk_css_parser_take_error (GtkCssParser *parser,
124 parser->error_func (parser, error, parser->user_data);
126 g_error_free (error);
130 _gtk_css_parser_error (GtkCssParser *parser,
138 va_start (args, format);
139 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
140 GTK_CSS_PROVIDER_ERROR_SYNTAX,
144 _gtk_css_parser_take_error (parser, error);
148 gtk_css_parser_new_line (GtkCssParser *parser)
150 gboolean result = FALSE;
152 if (*parser->data == '\r')
157 if (*parser->data == '\n')
166 parser->line_start = parser->data;
173 gtk_css_parser_skip_comment (GtkCssParser *parser)
175 if (parser->data[0] != '/' ||
176 parser->data[1] != '*')
181 while (*parser->data)
183 gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
187 if (gtk_css_parser_new_line (parser))
192 if (parser->data[-2] == '*')
194 if (parser->data[0] == '*')
195 _gtk_css_parser_error (parser, "'/*' in comment block");
198 /* FIXME: position */
199 _gtk_css_parser_error (parser, "Unterminated comment");
204 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
208 while (*parser->data)
210 if (gtk_css_parser_new_line (parser))
213 len = strspn (parser->data, WHITESPACE_CHARS);
220 if (!gtk_css_parser_skip_comment (parser))
226 _gtk_css_parser_try (GtkCssParser *parser,
228 gboolean skip_whitespace)
230 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
231 g_return_val_if_fail (string != NULL, FALSE);
233 if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
236 parser->data += strlen (string);
239 _gtk_css_parser_skip_whitespace (parser);
255 _gtk_css_parser_unescape (GtkCssParser *parser,
261 g_assert (*parser->data == '\\');
265 for (i = 0; i < 6; i++)
267 if (!g_ascii_isxdigit (parser->data[i]))
270 result = (result << 4) + get_xdigit (parser->data[i]);
275 g_string_append_unichar (str, result);
278 /* NB: gtk_css_parser_new_line() forward data pointer itself */
279 if (!gtk_css_parser_new_line (parser) &&
281 strchr (WHITESPACE_CHARS, *parser->data))
286 if (gtk_css_parser_new_line (parser))
289 g_string_append_c (str, *parser->data);
296 _gtk_css_parser_read_char (GtkCssParser *parser,
298 const char * allowed)
300 if (*parser->data == 0)
303 if (strchr (allowed, *parser->data))
305 g_string_append_c (str, *parser->data);
309 if (*parser->data >= 127)
311 gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
313 g_string_append_len (str, parser->data, len);
317 if (*parser->data == '\\')
319 _gtk_css_parser_unescape (parser, str);
327 _gtk_css_parser_try_name (GtkCssParser *parser,
328 gboolean skip_whitespace)
332 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
334 name = g_string_new (NULL);
336 while (_gtk_css_parser_read_char (parser, name, NMCHAR))
340 _gtk_css_parser_skip_whitespace (parser);
342 return g_string_free (name, FALSE);
346 _gtk_css_parser_try_ident (GtkCssParser *parser,
347 gboolean skip_whitespace)
352 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
354 start = parser->data;
356 ident = g_string_new (NULL);
358 if (*parser->data == '-')
360 g_string_append_c (ident, '-');
364 if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
366 parser->data = start;
367 g_string_free (ident, TRUE);
371 while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
375 _gtk_css_parser_skip_whitespace (parser);
377 return g_string_free (ident, FALSE);
381 _gtk_css_parser_is_string (GtkCssParser *parser)
383 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
385 return *parser->data == '"' || *parser->data == '\'';
389 _gtk_css_parser_read_string (GtkCssParser *parser)
394 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
396 quote = *parser->data;
398 if (quote != '"' && quote != '\'')
400 _gtk_css_parser_error (parser, "Expected a string.");
405 str = g_string_new (NULL);
409 gsize len = strcspn (parser->data, "\\'\"\n\r\f");
411 g_string_append_len (str, parser->data, len);
415 switch (*parser->data)
418 _gtk_css_parser_unescape (parser, str);
422 if (*parser->data == quote)
425 _gtk_css_parser_skip_whitespace (parser);
426 return g_string_free (str, FALSE);
429 g_string_append_c (str, *parser->data);
433 /* FIXME: position */
434 _gtk_css_parser_error (parser, "Missing end quote in string.");
435 g_string_free (str, TRUE);
438 _gtk_css_parser_error (parser,
439 "Invalid character in string. Must be escaped.");
440 g_string_free (str, TRUE);
445 g_assert_not_reached ();
450 _gtk_css_parser_try_int (GtkCssParser *parser,
456 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
457 g_return_val_if_fail (value != NULL, FALSE);
459 /* strtoll parses a plus, but we are not allowed to */
460 if (*parser->data == '+')
464 result = g_ascii_strtoll (parser->data, &end, 10);
467 if (result > G_MAXINT || result < G_MININT)
469 if (parser->data == end)
475 _gtk_css_parser_skip_whitespace (parser);
481 _gtk_css_parser_try_uint (GtkCssParser *parser,
487 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
488 g_return_val_if_fail (value != NULL, FALSE);
491 result = g_ascii_strtoull (parser->data, &end, 10);
494 if (result > G_MAXUINT)
496 if (parser->data == end)
502 _gtk_css_parser_skip_whitespace (parser);
508 _gtk_css_parser_try_double (GtkCssParser *parser,
514 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
515 g_return_val_if_fail (value != NULL, FALSE);
518 result = g_ascii_strtod (parser->data, &end);
521 if (parser->data == end)
527 _gtk_css_parser_skip_whitespace (parser);
533 _gtk_css_parser_has_number (GtkCssParser *parser)
536 return strchr ("+-0123456789.", parser->data[0]) != NULL;
540 _gtk_css_parser_read_number (GtkCssParser *parser,
541 GtkCssNumber *number,
542 GtkCssNumberParseFlags flags)
544 static const struct {
547 GtkCssNumberParseFlags required_flags;
549 { "px", GTK_CSS_PX, GTK_CSS_PARSE_LENGTH },
550 { "pt", GTK_CSS_PT, GTK_CSS_PARSE_LENGTH },
551 { "em", GTK_CSS_EM, GTK_CSS_PARSE_LENGTH },
552 { "ex", GTK_CSS_EX, GTK_CSS_PARSE_LENGTH },
553 { "pc", GTK_CSS_PC, GTK_CSS_PARSE_LENGTH },
554 { "in", GTK_CSS_IN, GTK_CSS_PARSE_LENGTH },
555 { "cm", GTK_CSS_CM, GTK_CSS_PARSE_LENGTH },
556 { "mm", GTK_CSS_MM, GTK_CSS_PARSE_LENGTH },
557 { "rad", GTK_CSS_RAD, GTK_CSS_PARSE_ANGLE },
558 { "deg", GTK_CSS_DEG, GTK_CSS_PARSE_ANGLE },
559 { "grad", GTK_CSS_GRAD, GTK_CSS_PARSE_ANGLE },
560 { "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE }
564 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
565 g_return_val_if_fail (number != NULL, FALSE);
568 number->unit = GTK_CSS_NUMBER;
569 number->value = g_ascii_strtod (parser->data, &end);
572 _gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
575 if (parser->data == end)
577 _gtk_css_parser_error (parser, "not a number");
583 if (flags & GTK_CSS_POSITIVE_ONLY &&
586 _gtk_css_parser_error (parser, "negative values are not allowed.");
590 unit = _gtk_css_parser_try_ident (parser, FALSE);
596 for (i = 0; i < G_N_ELEMENTS (units); i++)
598 if (flags & units[i].required_flags &&
599 g_ascii_strcasecmp (unit, units[i].name) == 0)
603 if (i >= G_N_ELEMENTS (units))
605 _gtk_css_parser_error (parser, "`%s' is not a valid unit.", unit);
610 number->unit = units[i].unit;
615 if ((flags & GTK_CSS_PARSE_PERCENT) &&
616 _gtk_css_parser_try (parser, "%", FALSE))
618 number->unit = GTK_CSS_PERCENT;
620 else if (number->value == 0.0)
622 if (flags & GTK_CSS_PARSE_NUMBER)
623 number->unit = GTK_CSS_NUMBER;
624 else if (flags & GTK_CSS_PARSE_LENGTH)
625 number->unit = GTK_CSS_PX;
627 number->unit = GTK_CSS_PERCENT;
629 else if (flags & GTK_CSS_NUMBER_AS_PIXELS)
631 GError *error = g_error_new_literal (GTK_CSS_PROVIDER_ERROR,
632 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
633 "Not using units is deprecated. Assuming 'px'.");
634 _gtk_css_parser_take_error (parser, error);
635 number->unit = GTK_CSS_PX;
637 else if (flags & GTK_CSS_PARSE_NUMBER)
639 number->unit = GTK_CSS_NUMBER;
643 _gtk_css_parser_error (parser, "Unit is missing.");
648 _gtk_css_parser_skip_whitespace (parser);
653 /* XXX: we should introduce GtkCssLenght that deals with
654 * different kind of units */
656 _gtk_css_parser_try_length (GtkCssParser *parser,
659 if (!_gtk_css_parser_try_int (parser, value))
662 /* FIXME: _try_uint skips spaces while the
665 _gtk_css_parser_try (parser, "px", TRUE);
671 _gtk_css_parser_try_enum (GtkCssParser *parser,
675 GEnumClass *enum_class;
680 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
681 g_return_val_if_fail (value != NULL, FALSE);
685 enum_class = g_type_class_ref (enum_type);
687 start = parser->data;
689 str = _gtk_css_parser_try_ident (parser, TRUE);
693 if (enum_class->n_values)
695 GEnumValue *enum_value;
697 for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
699 if (enum_value->value_nick &&
700 g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
702 *value = enum_value->value;
710 g_type_class_unref (enum_class);
713 parser->data = start;
729 static GtkSymbolicColor *
730 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
733 GtkSymbolicColor *symbolic;
734 GtkSymbolicColor *child1, *child2;
737 if (!_gtk_css_parser_try (parser, "(", TRUE))
739 _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
743 if (color == COLOR_RGB || color == COLOR_RGBA)
749 for (i = 0; i < 3; i++)
751 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
753 _gtk_css_parser_error (parser, "Expected ',' in color definition");
757 if (!_gtk_css_parser_try_double (parser, &tmp))
759 _gtk_css_parser_error (parser, "Invalid number for color value");
762 if (_gtk_css_parser_try (parser, "%", TRUE))
773 g_assert_not_reached ();
776 if (color == COLOR_RGBA)
778 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
780 _gtk_css_parser_error (parser, "Expected ',' in color definition");
784 if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
786 _gtk_css_parser_error (parser, "Invalid number for alpha value");
793 symbolic = gtk_symbolic_color_new_literal (&rgba);
795 else if (color == COLOR_WIN32)
797 symbolic = _gtk_win32_theme_color_parse (parser);
798 if (symbolic == NULL)
803 child1 = _gtk_css_parser_read_symbolic_color (parser);
807 if (color == COLOR_MIX)
809 if (!_gtk_css_parser_try (parser, ",", TRUE))
811 _gtk_css_parser_error (parser, "Expected ',' in color definition");
812 gtk_symbolic_color_unref (child1);
816 child2 = _gtk_css_parser_read_symbolic_color (parser);
819 gtk_symbolic_color_unref (child1);
826 if (color == COLOR_LIGHTER)
828 else if (color == COLOR_DARKER)
832 if (!_gtk_css_parser_try (parser, ",", TRUE))
834 _gtk_css_parser_error (parser, "Expected ',' in color definition");
835 gtk_symbolic_color_unref (child1);
837 gtk_symbolic_color_unref (child2);
841 if (!_gtk_css_parser_try_double (parser, &value))
843 _gtk_css_parser_error (parser, "Expected number in color definition");
844 gtk_symbolic_color_unref (child1);
846 gtk_symbolic_color_unref (child2);
856 symbolic = gtk_symbolic_color_new_shade (child1, value);
859 symbolic = gtk_symbolic_color_new_alpha (child1, value);
862 symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
865 g_assert_not_reached ();
869 gtk_symbolic_color_unref (child1);
871 gtk_symbolic_color_unref (child2);
874 if (!_gtk_css_parser_try (parser, ")", TRUE))
876 _gtk_css_parser_error (parser, "Expected ')' in color definition");
877 gtk_symbolic_color_unref (symbolic);
884 static GtkSymbolicColor *
885 gtk_css_parser_try_hash_color (GtkCssParser *parser)
887 if (parser->data[0] == '#' &&
888 g_ascii_isxdigit (parser->data[1]) &&
889 g_ascii_isxdigit (parser->data[2]) &&
890 g_ascii_isxdigit (parser->data[3]))
894 if (g_ascii_isxdigit (parser->data[4]) &&
895 g_ascii_isxdigit (parser->data[5]) &&
896 g_ascii_isxdigit (parser->data[6]))
898 rgba.red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
899 rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
900 rgba.blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
906 rgba.red = get_xdigit (parser->data[1]) / 15.0;
907 rgba.green = get_xdigit (parser->data[2]) / 15.0;
908 rgba.blue = get_xdigit (parser->data[3]) / 15.0;
913 _gtk_css_parser_skip_whitespace (parser);
915 return gtk_symbolic_color_new_literal (&rgba);
922 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
924 GtkSymbolicColor *symbolic;
926 const char *names[] = {"rgba", "rgb", "lighter", "darker", "shade", "alpha", "mix",
927 GTK_WIN32_THEME_SYMBOLIC_COLOR_NAME};
930 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
932 if (_gtk_css_parser_try (parser, "transparent", TRUE))
934 GdkRGBA transparent = { 0, 0, 0, 0 };
936 return gtk_symbolic_color_new_literal (&transparent);
939 if (_gtk_css_parser_try (parser, "@", FALSE))
941 name = _gtk_css_parser_try_name (parser, TRUE);
945 symbolic = gtk_symbolic_color_new_name (name);
949 _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
957 for (color = 0; color < G_N_ELEMENTS (names); color++)
959 if (_gtk_css_parser_try (parser, names[color], TRUE))
963 if (color < G_N_ELEMENTS (names))
964 return gtk_css_parser_read_symbolic_color_function (parser, color);
966 symbolic = gtk_css_parser_try_hash_color (parser);
970 name = _gtk_css_parser_try_name (parser, TRUE);
975 if (gdk_rgba_parse (&rgba, name))
977 symbolic = gtk_symbolic_color_new_literal (&rgba);
981 _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
988 _gtk_css_parser_error (parser, "Not a color definition");
993 _gtk_css_parser_read_url (GtkCssParser *parser,
1000 if (_gtk_css_parser_try (parser, "url", FALSE))
1002 if (!_gtk_css_parser_try (parser, "(", TRUE))
1004 _gtk_css_parser_skip_whitespace (parser);
1005 if (_gtk_css_parser_try (parser, "(", TRUE))
1009 error = g_error_new_literal (GTK_CSS_PROVIDER_ERROR,
1010 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1011 "Whitespace between 'url' and '(' is deprecated");
1013 _gtk_css_parser_take_error (parser, error);
1017 _gtk_css_parser_error (parser, "Expected '(' after 'url'");
1022 path = _gtk_css_parser_read_string (parser);
1026 if (!_gtk_css_parser_try (parser, ")", TRUE))
1028 _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
1033 scheme = g_uri_parse_scheme (path);
1036 file = g_file_new_for_uri (path);
1044 path = _gtk_css_parser_try_name (parser, TRUE);
1047 _gtk_css_parser_error (parser, "Not a valid url");
1052 file = g_file_resolve_relative_path (base, path);
1059 _gtk_css_parser_resync_internal (GtkCssParser *parser,
1060 gboolean sync_at_semicolon,
1061 gboolean read_sync_token,
1067 len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
1068 parser->data += len;
1070 if (gtk_css_parser_new_line (parser))
1073 if (_gtk_css_parser_is_string (parser))
1075 /* Hrm, this emits errors, and i suspect it shouldn't... */
1076 char *free_me = _gtk_css_parser_read_string (parser);
1081 if (gtk_css_parser_skip_comment (parser))
1084 switch (*parser->data)
1088 GString *ignore = g_string_new (NULL);
1089 _gtk_css_parser_unescape (parser, ignore);
1090 g_string_free (ignore, TRUE);
1094 if (sync_at_semicolon && !read_sync_token)
1097 if (sync_at_semicolon)
1099 _gtk_css_parser_skip_whitespace (parser);
1105 _gtk_css_parser_resync (parser, FALSE, ')');
1111 _gtk_css_parser_resync (parser, FALSE, ']');
1117 _gtk_css_parser_resync (parser, FALSE, '}');
1120 if (sync_at_semicolon || !terminator)
1122 _gtk_css_parser_skip_whitespace (parser);
1129 if (terminator == *parser->data)
1131 _gtk_css_parser_skip_whitespace (parser);
1143 } while (*parser->data);
1147 _gtk_css_parser_read_value (GtkCssParser *parser)
1152 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
1154 start = parser->data;
1156 /* This needs to be done better */
1157 _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
1159 result = g_strndup (start, parser->data - start);
1162 g_strchomp (result);
1171 _gtk_css_parser_error (parser, "Expected a property value");
1177 _gtk_css_parser_resync (GtkCssParser *parser,
1178 gboolean sync_at_semicolon,
1181 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1183 _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);