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_get_line (GtkCssParser *parser)
98 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
104 _gtk_css_parser_get_position (GtkCssParser *parser)
106 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
108 return parser->data - parser->line_start;
112 _gtk_css_parser_take_error (GtkCssParser *parser,
115 parser->error_func (parser, error, parser->user_data);
117 g_error_free (error);
121 _gtk_css_parser_error (GtkCssParser *parser,
129 va_start (args, format);
130 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
131 GTK_CSS_PROVIDER_ERROR_SYNTAX,
135 _gtk_css_parser_take_error (parser, error);
139 gtk_css_parser_new_line (GtkCssParser *parser)
141 gboolean result = FALSE;
143 if (*parser->data == '\r')
148 if (*parser->data == '\n')
157 parser->line_start = parser->data;
164 gtk_css_parser_skip_comment (GtkCssParser *parser)
166 if (parser->data[0] != '/' ||
167 parser->data[1] != '*')
172 while (*parser->data)
174 gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
178 if (gtk_css_parser_new_line (parser))
183 if (parser->data[-2] == '*')
185 if (parser->data[0] == '*')
186 _gtk_css_parser_error (parser, "'/*' in comment block");
189 /* FIXME: position */
190 _gtk_css_parser_error (parser, "Unterminated comment");
195 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
199 while (*parser->data)
201 if (gtk_css_parser_new_line (parser))
204 len = strspn (parser->data, WHITESPACE_CHARS);
211 if (!gtk_css_parser_skip_comment (parser))
217 _gtk_css_parser_try (GtkCssParser *parser,
219 gboolean skip_whitespace)
221 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
222 g_return_val_if_fail (string != NULL, FALSE);
224 if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
227 parser->data += strlen (string);
230 _gtk_css_parser_skip_whitespace (parser);
246 _gtk_css_parser_unescape (GtkCssParser *parser,
252 g_assert (*parser->data == '\\');
256 for (i = 0; i < 6; i++)
258 if (!g_ascii_isxdigit (parser->data[i]))
261 result = (result << 4) + get_xdigit (parser->data[i]);
266 g_string_append_unichar (str, result);
269 /* NB: gtk_css_parser_new_line() forward data pointer itself */
270 if (!gtk_css_parser_new_line (parser) &&
272 strchr (WHITESPACE_CHARS, *parser->data))
277 if (gtk_css_parser_new_line (parser))
280 g_string_append_c (str, *parser->data);
287 _gtk_css_parser_read_char (GtkCssParser *parser,
289 const char * allowed)
291 if (*parser->data == 0)
294 if (strchr (allowed, *parser->data))
296 g_string_append_c (str, *parser->data);
300 if (*parser->data >= 127)
302 gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
304 g_string_append_len (str, parser->data, len);
308 if (*parser->data == '\\')
310 _gtk_css_parser_unescape (parser, str);
318 _gtk_css_parser_try_name (GtkCssParser *parser,
319 gboolean skip_whitespace)
323 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
325 name = g_string_new (NULL);
327 while (_gtk_css_parser_read_char (parser, name, NMCHAR))
331 _gtk_css_parser_skip_whitespace (parser);
333 return g_string_free (name, FALSE);
337 _gtk_css_parser_try_ident (GtkCssParser *parser,
338 gboolean skip_whitespace)
343 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
345 start = parser->data;
347 ident = g_string_new (NULL);
349 if (*parser->data == '-')
351 g_string_append_c (ident, '-');
355 if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
357 parser->data = start;
358 g_string_free (ident, TRUE);
362 while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
366 _gtk_css_parser_skip_whitespace (parser);
368 return g_string_free (ident, FALSE);
372 _gtk_css_parser_is_string (GtkCssParser *parser)
374 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
376 return *parser->data == '"' || *parser->data == '\'';
380 _gtk_css_parser_read_string (GtkCssParser *parser)
385 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
387 quote = *parser->data;
389 if (quote != '"' && quote != '\'')
391 _gtk_css_parser_error (parser, "Expected a string.");
396 str = g_string_new (NULL);
400 gsize len = strcspn (parser->data, "\\'\"\n\r\f");
402 g_string_append_len (str, parser->data, len);
406 switch (*parser->data)
409 _gtk_css_parser_unescape (parser, str);
413 if (*parser->data == quote)
416 _gtk_css_parser_skip_whitespace (parser);
417 return g_string_free (str, FALSE);
420 g_string_append_c (str, *parser->data);
424 /* FIXME: position */
425 _gtk_css_parser_error (parser, "Missing end quote in string.");
426 g_string_free (str, TRUE);
429 _gtk_css_parser_error (parser,
430 "Invalid character in string. Must be escaped.");
431 g_string_free (str, TRUE);
436 g_assert_not_reached ();
441 _gtk_css_parser_read_uri (GtkCssParser *parser)
445 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
447 if (!_gtk_css_parser_try (parser, "url(", TRUE))
449 _gtk_css_parser_error (parser, "expected 'url('");
453 _gtk_css_parser_skip_whitespace (parser);
455 if (_gtk_css_parser_is_string (parser))
457 result = _gtk_css_parser_read_string (parser);
461 GString *str = g_string_new (NULL);
463 while (_gtk_css_parser_read_char (parser, str, URLCHAR))
465 result = g_string_free (str, FALSE);
467 _gtk_css_parser_error (parser, "not a url");
473 _gtk_css_parser_skip_whitespace (parser);
475 if (*parser->data != ')')
477 _gtk_css_parser_error (parser, "missing ')' for url");
484 _gtk_css_parser_skip_whitespace (parser);
490 _gtk_css_parser_try_int (GtkCssParser *parser,
496 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
497 g_return_val_if_fail (value != NULL, FALSE);
499 /* strtoll parses a plus, but we are not allowed to */
500 if (*parser->data == '+')
504 result = g_ascii_strtoll (parser->data, &end, 10);
507 if (result > G_MAXINT || result < G_MININT)
509 if (parser->data == end)
515 _gtk_css_parser_skip_whitespace (parser);
521 _gtk_css_parser_try_uint (GtkCssParser *parser,
527 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
528 g_return_val_if_fail (value != NULL, FALSE);
531 result = g_ascii_strtoull (parser->data, &end, 10);
534 if (result > G_MAXUINT)
536 if (parser->data == end)
542 _gtk_css_parser_skip_whitespace (parser);
548 _gtk_css_parser_try_double (GtkCssParser *parser,
554 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
555 g_return_val_if_fail (value != NULL, FALSE);
558 result = g_ascii_strtod (parser->data, &end);
561 if (parser->data == end)
567 _gtk_css_parser_skip_whitespace (parser);
573 _gtk_css_parser_try_enum (GtkCssParser *parser,
577 GEnumClass *enum_class;
582 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
583 g_return_val_if_fail (value != NULL, FALSE);
587 enum_class = g_type_class_ref (enum_type);
589 start = parser->data;
591 str = _gtk_css_parser_try_ident (parser, TRUE);
595 if (enum_class->n_values)
597 GEnumValue *enum_value;
599 for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
601 if (enum_value->value_nick &&
602 g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
604 *value = enum_value->value;
612 g_type_class_unref (enum_class);
615 parser->data = start;
631 static GtkSymbolicColor *
632 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
635 GtkSymbolicColor *symbolic;
636 GtkSymbolicColor *child1, *child2;
639 if (!_gtk_css_parser_try (parser, "(", TRUE))
641 _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
645 if (color == COLOR_RGB || color == COLOR_RGBA)
651 for (i = 0; i < 3; i++)
653 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
655 _gtk_css_parser_error (parser, "Expected ',' in color definition");
659 if (!_gtk_css_parser_try_double (parser, &tmp))
661 _gtk_css_parser_error (parser, "Invalid number for color value");
664 if (_gtk_css_parser_try (parser, "%", TRUE))
675 g_assert_not_reached ();
678 if (color == COLOR_RGBA)
680 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
682 _gtk_css_parser_error (parser, "Expected ',' in color definition");
686 if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
688 _gtk_css_parser_error (parser, "Invalid number for alpha value");
695 symbolic = gtk_symbolic_color_new_literal (&rgba);
697 else if (color == COLOR_WIN32)
699 symbolic = _gtk_win32_theme_color_parse (parser);
700 if (symbolic == NULL)
705 child1 = _gtk_css_parser_read_symbolic_color (parser);
709 if (color == COLOR_MIX)
711 if (!_gtk_css_parser_try (parser, ",", TRUE))
713 _gtk_css_parser_error (parser, "Expected ',' in color definition");
714 gtk_symbolic_color_unref (child1);
718 child2 = _gtk_css_parser_read_symbolic_color (parser);
721 gtk_symbolic_color_unref (child1);
728 if (color == COLOR_LIGHTER)
730 else if (color == COLOR_DARKER)
734 if (!_gtk_css_parser_try (parser, ",", TRUE))
736 _gtk_css_parser_error (parser, "Expected ',' in color definition");
737 gtk_symbolic_color_unref (child1);
739 gtk_symbolic_color_unref (child2);
743 if (!_gtk_css_parser_try_double (parser, &value))
745 _gtk_css_parser_error (parser, "Expected number in color definition");
746 gtk_symbolic_color_unref (child1);
748 gtk_symbolic_color_unref (child2);
758 symbolic = gtk_symbolic_color_new_shade (child1, value);
761 symbolic = gtk_symbolic_color_new_alpha (child1, value);
764 symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
767 g_assert_not_reached ();
771 gtk_symbolic_color_unref (child1);
773 gtk_symbolic_color_unref (child2);
776 if (!_gtk_css_parser_try (parser, ")", TRUE))
778 _gtk_css_parser_error (parser, "Expected ')' in color definition");
779 gtk_symbolic_color_unref (symbolic);
786 static GtkSymbolicColor *
787 gtk_css_parser_try_hash_color (GtkCssParser *parser)
789 if (parser->data[0] == '#' &&
790 g_ascii_isxdigit (parser->data[1]) &&
791 g_ascii_isxdigit (parser->data[2]) &&
792 g_ascii_isxdigit (parser->data[3]))
796 if (g_ascii_isxdigit (parser->data[4]) &&
797 g_ascii_isxdigit (parser->data[5]) &&
798 g_ascii_isxdigit (parser->data[6]))
800 rgba.red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
801 rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
802 rgba.blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
808 rgba.red = get_xdigit (parser->data[1]) / 15.0;
809 rgba.green = get_xdigit (parser->data[2]) / 15.0;
810 rgba.blue = get_xdigit (parser->data[3]) / 15.0;
815 _gtk_css_parser_skip_whitespace (parser);
817 return gtk_symbolic_color_new_literal (&rgba);
824 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
826 GtkSymbolicColor *symbolic;
828 const char *names[] = {"rgba", "rgb", "lighter", "darker", "shade", "alpha", "mix",
829 GTK_WIN32_THEME_SYMBOLIC_COLOR_NAME};
832 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
834 if (_gtk_css_parser_try (parser, "transparent", TRUE))
836 GdkRGBA transparent = { 0, 0, 0, 0 };
838 return gtk_symbolic_color_new_literal (&transparent);
841 if (_gtk_css_parser_try (parser, "@", FALSE))
843 name = _gtk_css_parser_try_name (parser, TRUE);
847 symbolic = gtk_symbolic_color_new_name (name);
851 _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
859 for (color = 0; color < G_N_ELEMENTS (names); color++)
861 if (_gtk_css_parser_try (parser, names[color], TRUE))
865 if (color < G_N_ELEMENTS (names))
866 return gtk_css_parser_read_symbolic_color_function (parser, color);
868 symbolic = gtk_css_parser_try_hash_color (parser);
872 name = _gtk_css_parser_try_name (parser, TRUE);
877 if (gdk_rgba_parse (&rgba, name))
879 symbolic = gtk_symbolic_color_new_literal (&rgba);
883 _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
890 _gtk_css_parser_error (parser, "Not a color definition");
895 _gtk_css_parser_resync_internal (GtkCssParser *parser,
896 gboolean sync_at_semicolon,
897 gboolean read_sync_token,
903 len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
906 if (gtk_css_parser_new_line (parser))
909 if (_gtk_css_parser_is_string (parser))
911 /* Hrm, this emits errors, and i suspect it shouldn't... */
912 char *free_me = _gtk_css_parser_read_string (parser);
917 if (gtk_css_parser_skip_comment (parser))
920 switch (*parser->data)
924 GString *ignore = g_string_new (NULL);
925 _gtk_css_parser_unescape (parser, ignore);
926 g_string_free (ignore, TRUE);
930 if (sync_at_semicolon && !read_sync_token)
933 if (sync_at_semicolon)
935 _gtk_css_parser_skip_whitespace (parser);
941 _gtk_css_parser_resync (parser, FALSE, ')');
947 _gtk_css_parser_resync (parser, FALSE, ']');
953 _gtk_css_parser_resync (parser, FALSE, '}');
956 if (sync_at_semicolon || !terminator)
958 _gtk_css_parser_skip_whitespace (parser);
965 if (terminator == *parser->data)
967 _gtk_css_parser_skip_whitespace (parser);
979 } while (*parser->data);
983 _gtk_css_parser_read_value (GtkCssParser *parser)
988 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
990 start = parser->data;
992 /* This needs to be done better */
993 _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
995 result = g_strndup (start, parser->data - start);
1007 _gtk_css_parser_error (parser, "Expected a property value");
1013 _gtk_css_parser_resync (GtkCssParser *parser,
1014 gboolean sync_at_semicolon,
1017 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1019 _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);