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"
27 /* just for the errors, yay! */
28 #include "gtkcssprovider.h"
30 #define NEWLINE_CHARS "\r\n"
31 #define WHITESPACE_CHARS "\f \t"
32 #define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
33 #define NMCHAR NMSTART "01234567890-_"
34 #define URLCHAR NMCHAR "!#$%&*~"
36 #define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
41 GtkCssParserErrorFunc error_func;
44 const char *line_start;
49 _gtk_css_parser_new (const char *data,
50 GtkCssParserErrorFunc error_func,
55 g_return_val_if_fail (data != NULL, NULL);
57 parser = g_slice_new0 (GtkCssParser);
60 parser->error_func = error_func;
61 parser->user_data = user_data;
63 parser->line_start = data;
70 _gtk_css_parser_free (GtkCssParser *parser)
72 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
74 g_slice_free (GtkCssParser, parser);
78 _gtk_css_parser_is_eof (GtkCssParser *parser)
80 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
82 return *parser->data == 0;
86 _gtk_css_parser_begins_with (GtkCssParser *parser,
89 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
91 return *parser->data == c;
95 _gtk_css_parser_get_line (GtkCssParser *parser)
97 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
103 _gtk_css_parser_get_position (GtkCssParser *parser)
105 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
107 return parser->data - parser->line_start;
111 _gtk_css_parser_take_error (GtkCssParser *parser,
114 parser->error_func (parser, error, parser->user_data);
116 g_error_free (error);
120 _gtk_css_parser_error (GtkCssParser *parser,
128 va_start (args, format);
129 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
130 GTK_CSS_PROVIDER_ERROR_SYNTAX,
134 _gtk_css_parser_take_error (parser, error);
138 gtk_css_parser_new_line (GtkCssParser *parser)
140 gboolean result = FALSE;
142 if (*parser->data == '\r')
147 if (*parser->data == '\n')
156 parser->line_start = parser->data;
163 gtk_css_parser_skip_comment (GtkCssParser *parser)
165 if (parser->data[0] != '/' ||
166 parser->data[1] != '*')
171 while (*parser->data)
173 gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
177 if (gtk_css_parser_new_line (parser))
182 if (parser->data[-2] == '*')
184 if (parser->data[0] == '*')
185 _gtk_css_parser_error (parser, "'/*' in comment block");
188 /* FIXME: position */
189 _gtk_css_parser_error (parser, "Unterminated comment");
194 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
198 while (*parser->data)
200 if (gtk_css_parser_new_line (parser))
203 len = strspn (parser->data, WHITESPACE_CHARS);
210 if (!gtk_css_parser_skip_comment (parser))
216 _gtk_css_parser_try (GtkCssParser *parser,
218 gboolean skip_whitespace)
220 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
221 g_return_val_if_fail (string != NULL, FALSE);
223 if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
226 parser->data += strlen (string);
229 _gtk_css_parser_skip_whitespace (parser);
245 _gtk_css_parser_unescape (GtkCssParser *parser,
251 g_assert (*parser->data == '\\');
255 for (i = 0; i < 6; i++)
257 if (!g_ascii_isxdigit (parser->data[i]))
260 result = (result << 4) + get_xdigit (parser->data[i]);
265 g_string_append_unichar (str, result);
268 /* NB: gtk_css_parser_new_line() forward data pointer itself */
269 if (!gtk_css_parser_new_line (parser) &&
271 strchr (WHITESPACE_CHARS, *parser->data))
276 if (gtk_css_parser_new_line (parser))
279 g_string_append_c (str, *parser->data);
286 _gtk_css_parser_read_char (GtkCssParser *parser,
288 const char * allowed)
290 if (*parser->data == 0)
293 if (strchr (allowed, *parser->data))
295 g_string_append_c (str, *parser->data);
299 if (*parser->data >= 127)
301 gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
303 g_string_append_len (str, parser->data, len);
307 if (*parser->data == '\\')
309 _gtk_css_parser_unescape (parser, str);
317 _gtk_css_parser_try_name (GtkCssParser *parser,
318 gboolean skip_whitespace)
322 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
324 name = g_string_new (NULL);
326 while (_gtk_css_parser_read_char (parser, name, NMCHAR))
330 _gtk_css_parser_skip_whitespace (parser);
332 return g_string_free (name, FALSE);
336 _gtk_css_parser_try_ident (GtkCssParser *parser,
337 gboolean skip_whitespace)
342 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
344 start = parser->data;
346 ident = g_string_new (NULL);
348 if (*parser->data == '-')
350 g_string_append_c (ident, '-');
354 if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
356 parser->data = start;
357 g_string_free (ident, TRUE);
361 while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
365 _gtk_css_parser_skip_whitespace (parser);
367 return g_string_free (ident, FALSE);
371 _gtk_css_parser_is_string (GtkCssParser *parser)
373 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
375 return *parser->data == '"' || *parser->data == '\'';
379 _gtk_css_parser_read_string (GtkCssParser *parser)
384 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
386 quote = *parser->data;
388 if (quote != '"' && quote != '\'')
390 _gtk_css_parser_error (parser, "Expected a string.");
395 str = g_string_new (NULL);
399 gsize len = strcspn (parser->data, "\\'\"\n\r\f");
401 g_string_append_len (str, parser->data, len);
405 switch (*parser->data)
408 _gtk_css_parser_unescape (parser, str);
412 if (*parser->data == quote)
415 _gtk_css_parser_skip_whitespace (parser);
416 return g_string_free (str, FALSE);
419 g_string_append_c (str, *parser->data);
423 /* FIXME: position */
424 _gtk_css_parser_error (parser, "Missing end quote in string.");
425 g_string_free (str, TRUE);
428 _gtk_css_parser_error (parser,
429 "Invalid character in string. Must be escaped.");
430 g_string_free (str, TRUE);
435 g_assert_not_reached ();
440 _gtk_css_parser_read_uri (GtkCssParser *parser)
444 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
446 if (!_gtk_css_parser_try (parser, "url(", TRUE))
448 _gtk_css_parser_error (parser, "expected 'url('");
452 _gtk_css_parser_skip_whitespace (parser);
454 if (_gtk_css_parser_is_string (parser))
456 result = _gtk_css_parser_read_string (parser);
460 GString *str = g_string_new (NULL);
462 while (_gtk_css_parser_read_char (parser, str, URLCHAR))
464 result = g_string_free (str, FALSE);
466 _gtk_css_parser_error (parser, "not a url");
472 _gtk_css_parser_skip_whitespace (parser);
474 if (*parser->data != ')')
476 _gtk_css_parser_error (parser, "missing ')' for url");
483 _gtk_css_parser_skip_whitespace (parser);
489 _gtk_css_parser_try_int (GtkCssParser *parser,
495 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
496 g_return_val_if_fail (value != NULL, FALSE);
498 /* strtoll parses a plus, but we are not allowed to */
499 if (*parser->data == '+')
503 result = g_ascii_strtoll (parser->data, &end, 10);
506 if (result > G_MAXINT || result < G_MININT)
508 if (parser->data == end)
514 _gtk_css_parser_skip_whitespace (parser);
520 _gtk_css_parser_try_uint (GtkCssParser *parser,
526 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
527 g_return_val_if_fail (value != NULL, FALSE);
530 result = g_ascii_strtoull (parser->data, &end, 10);
533 if (result > G_MAXUINT)
535 if (parser->data == end)
541 _gtk_css_parser_skip_whitespace (parser);
547 _gtk_css_parser_try_double (GtkCssParser *parser,
553 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
554 g_return_val_if_fail (value != NULL, FALSE);
557 result = g_ascii_strtod (parser->data, &end);
560 if (parser->data == end)
566 _gtk_css_parser_skip_whitespace (parser);
581 static GtkSymbolicColor *
582 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
585 GtkSymbolicColor *symbolic;
586 GtkSymbolicColor *child1, *child2;
589 if (!_gtk_css_parser_try (parser, "(", TRUE))
591 _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
595 if (color == COLOR_RGB || color == COLOR_RGBA)
601 for (i = 0; i < 3; i++)
603 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
605 _gtk_css_parser_error (parser, "Expected ',' in color definition");
609 if (!_gtk_css_parser_try_double (parser, &tmp))
611 _gtk_css_parser_error (parser, "Invalid number for color value");
614 if (_gtk_css_parser_try (parser, "%", TRUE))
625 g_assert_not_reached ();
628 if (color == COLOR_RGBA)
630 if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
632 _gtk_css_parser_error (parser, "Expected ',' in color definition");
636 if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
638 _gtk_css_parser_error (parser, "Invalid number for alpha value");
645 symbolic = gtk_symbolic_color_new_literal (&rgba);
649 child1 = _gtk_css_parser_read_symbolic_color (parser);
653 if (color == COLOR_MIX)
655 if (!_gtk_css_parser_try (parser, ",", TRUE))
657 _gtk_css_parser_error (parser, "Expected ',' in color definition");
658 gtk_symbolic_color_unref (child1);
662 child2 = _gtk_css_parser_read_symbolic_color (parser);
665 gtk_symbolic_color_unref (child1);
672 if (color == COLOR_LIGHTER)
674 else if (color == COLOR_DARKER)
678 if (!_gtk_css_parser_try (parser, ",", TRUE))
680 _gtk_css_parser_error (parser, "Expected ',' in color definition");
681 gtk_symbolic_color_unref (child1);
683 gtk_symbolic_color_unref (child2);
687 if (!_gtk_css_parser_try_double (parser, &value))
689 _gtk_css_parser_error (parser, "Expected number in color definition");
690 gtk_symbolic_color_unref (child1);
692 gtk_symbolic_color_unref (child2);
702 symbolic = gtk_symbolic_color_new_shade (child1, value);
705 symbolic = gtk_symbolic_color_new_alpha (child1, value);
708 symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
711 g_assert_not_reached ();
715 gtk_symbolic_color_unref (child1);
717 gtk_symbolic_color_unref (child2);
720 if (!_gtk_css_parser_try (parser, ")", TRUE))
722 _gtk_css_parser_error (parser, "Expected ')' in color definition");
723 gtk_symbolic_color_unref (symbolic);
730 static GtkSymbolicColor *
731 gtk_css_parser_try_hash_color (GtkCssParser *parser)
733 if (parser->data[0] == '#' &&
734 g_ascii_isxdigit (parser->data[1]) &&
735 g_ascii_isxdigit (parser->data[2]) &&
736 g_ascii_isxdigit (parser->data[3]))
740 if (g_ascii_isxdigit (parser->data[4]) &&
741 g_ascii_isxdigit (parser->data[5]) &&
742 g_ascii_isxdigit (parser->data[6]))
744 rgba.red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
745 rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
746 rgba.blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
752 rgba.red = get_xdigit (parser->data[1]) / 15.0;
753 rgba.green = get_xdigit (parser->data[2]) / 15.0;
754 rgba.blue = get_xdigit (parser->data[3]) / 15.0;
759 _gtk_css_parser_skip_whitespace (parser);
761 return gtk_symbolic_color_new_literal (&rgba);
768 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
770 GtkSymbolicColor *symbolic;
772 const char *names[] = {"rgba", "rgb", "lighter", "darker", "shade", "alpha", "mix" };
775 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
777 if (_gtk_css_parser_try (parser, "@", FALSE))
779 name = _gtk_css_parser_try_name (parser, TRUE);
783 symbolic = gtk_symbolic_color_new_name (name);
787 _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
795 for (color = 0; color < G_N_ELEMENTS (names); color++)
797 if (_gtk_css_parser_try (parser, names[color], TRUE))
801 if (color < G_N_ELEMENTS (names))
802 return gtk_css_parser_read_symbolic_color_function (parser, color);
804 symbolic = gtk_css_parser_try_hash_color (parser);
808 name = _gtk_css_parser_try_name (parser, TRUE);
813 if (gdk_rgba_parse (&rgba, name))
815 symbolic = gtk_symbolic_color_new_literal (&rgba);
819 _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
826 _gtk_css_parser_error (parser, "Not a color definition");
831 _gtk_css_parser_resync_internal (GtkCssParser *parser,
832 gboolean sync_at_semicolon,
833 gboolean read_sync_token,
839 len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
842 if (gtk_css_parser_new_line (parser))
845 if (_gtk_css_parser_is_string (parser))
847 /* Hrm, this emits errors, and i suspect it shouldn't... */
848 char *free_me = _gtk_css_parser_read_string (parser);
853 if (gtk_css_parser_skip_comment (parser))
856 switch (*parser->data)
860 GString *ignore = g_string_new (NULL);
861 _gtk_css_parser_unescape (parser, ignore);
862 g_string_free (ignore, TRUE);
866 if (sync_at_semicolon && !read_sync_token)
869 if (sync_at_semicolon)
871 _gtk_css_parser_skip_whitespace (parser);
877 _gtk_css_parser_resync (parser, FALSE, ')');
883 _gtk_css_parser_resync (parser, FALSE, ']');
889 _gtk_css_parser_resync (parser, FALSE, '}');
892 if (sync_at_semicolon || !terminator)
894 _gtk_css_parser_skip_whitespace (parser);
901 if (terminator == *parser->data)
903 _gtk_css_parser_skip_whitespace (parser);
915 } while (*parser->data);
919 _gtk_css_parser_read_value (GtkCssParser *parser)
924 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
926 start = parser->data;
928 /* This needs to be done better */
929 _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
931 result = g_strndup (start, parser->data - start);
943 _gtk_css_parser_error (parser, "Expected a property value");
949 _gtk_css_parser_resync (GtkCssParser *parser,
950 gboolean sync_at_semicolon,
953 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
955 _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);