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"
22 #include "gtkcssnumbervalueprivate.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)
43 GtkCssParserErrorFunc error_func;
46 const char *line_start;
51 _gtk_css_parser_new (const char *data,
53 GtkCssParserErrorFunc error_func,
58 g_return_val_if_fail (data != NULL, NULL);
59 g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
61 parser = g_slice_new0 (GtkCssParser);
65 parser->file = g_object_ref (file);
66 parser->error_func = error_func;
67 parser->user_data = user_data;
69 parser->line_start = data;
76 _gtk_css_parser_free (GtkCssParser *parser)
78 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
81 g_object_unref (parser->file);
83 g_slice_free (GtkCssParser, parser);
87 _gtk_css_parser_is_eof (GtkCssParser *parser)
89 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
91 return *parser->data == 0;
95 _gtk_css_parser_begins_with (GtkCssParser *parser,
98 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
100 return *parser->data == c;
104 _gtk_css_parser_has_prefix (GtkCssParser *parser,
107 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
109 return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
113 _gtk_css_parser_get_line (GtkCssParser *parser)
115 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
121 _gtk_css_parser_get_position (GtkCssParser *parser)
123 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
125 return parser->data - parser->line_start;
129 gtk_css_parser_get_base_file (GtkCssParser *parser)
135 base = g_file_get_parent (parser->file);
139 char *dir = g_get_current_dir ();
140 base = g_file_new_for_path (dir);
148 _gtk_css_parser_get_file_for_path (GtkCssParser *parser,
153 g_return_val_if_fail (parser != NULL, NULL);
154 g_return_val_if_fail (path != NULL, NULL);
156 base = gtk_css_parser_get_base_file (parser);
157 file = g_file_resolve_relative_path (base, path);
158 g_object_unref (base);
164 _gtk_css_parser_get_file (GtkCssParser *parser)
166 g_return_val_if_fail (parser != NULL, NULL);
172 _gtk_css_parser_take_error (GtkCssParser *parser,
175 parser->error_func (parser, error, parser->user_data);
177 g_error_free (error);
181 _gtk_css_parser_error (GtkCssParser *parser,
189 va_start (args, format);
190 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
191 GTK_CSS_PROVIDER_ERROR_SYNTAX,
195 _gtk_css_parser_take_error (parser, error);
199 _gtk_css_parser_error_full (GtkCssParser *parser,
200 GtkCssProviderError code,
208 va_start (args, format);
209 error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
213 _gtk_css_parser_take_error (parser, error);
216 gtk_css_parser_new_line (GtkCssParser *parser)
218 gboolean result = FALSE;
220 if (*parser->data == '\r')
225 if (*parser->data == '\n')
234 parser->line_start = parser->data;
241 gtk_css_parser_skip_comment (GtkCssParser *parser)
243 if (parser->data[0] != '/' ||
244 parser->data[1] != '*')
249 while (*parser->data)
251 gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
255 if (gtk_css_parser_new_line (parser))
260 if (len > 0 && parser->data[-2] == '*')
262 if (parser->data[0] == '*')
263 _gtk_css_parser_error (parser, "'/*' in comment block");
266 /* FIXME: position */
267 _gtk_css_parser_error (parser, "Unterminated comment");
272 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
276 while (*parser->data)
278 if (gtk_css_parser_new_line (parser))
281 len = strspn (parser->data, WHITESPACE_CHARS);
288 if (!gtk_css_parser_skip_comment (parser))
294 _gtk_css_parser_try (GtkCssParser *parser,
296 gboolean skip_whitespace)
298 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
299 g_return_val_if_fail (string != NULL, FALSE);
301 if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
304 parser->data += strlen (string);
307 _gtk_css_parser_skip_whitespace (parser);
323 _gtk_css_parser_unescape (GtkCssParser *parser,
329 g_assert (*parser->data == '\\');
333 for (i = 0; i < 6; i++)
335 if (!g_ascii_isxdigit (parser->data[i]))
338 result = (result << 4) + get_xdigit (parser->data[i]);
343 g_string_append_unichar (str, result);
346 /* NB: gtk_css_parser_new_line() forward data pointer itself */
347 if (!gtk_css_parser_new_line (parser) &&
349 strchr (WHITESPACE_CHARS, *parser->data))
354 if (gtk_css_parser_new_line (parser))
357 g_string_append_c (str, *parser->data);
364 _gtk_css_parser_read_char (GtkCssParser *parser,
366 const char * allowed)
368 if (*parser->data == 0)
371 if (strchr (allowed, *parser->data))
373 g_string_append_c (str, *parser->data);
377 if (*parser->data >= 127)
379 gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
381 g_string_append_len (str, parser->data, len);
385 if (*parser->data == '\\')
387 _gtk_css_parser_unescape (parser, str);
395 _gtk_css_parser_try_name (GtkCssParser *parser,
396 gboolean skip_whitespace)
400 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
402 name = g_string_new (NULL);
404 while (_gtk_css_parser_read_char (parser, name, NMCHAR))
408 _gtk_css_parser_skip_whitespace (parser);
410 return g_string_free (name, FALSE);
414 _gtk_css_parser_try_ident (GtkCssParser *parser,
415 gboolean skip_whitespace)
420 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
422 start = parser->data;
424 ident = g_string_new (NULL);
426 if (*parser->data == '-')
428 g_string_append_c (ident, '-');
432 if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
434 parser->data = start;
435 g_string_free (ident, TRUE);
439 while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
443 _gtk_css_parser_skip_whitespace (parser);
445 return g_string_free (ident, FALSE);
449 _gtk_css_parser_is_string (GtkCssParser *parser)
451 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
453 return *parser->data == '"' || *parser->data == '\'';
457 _gtk_css_parser_read_string (GtkCssParser *parser)
462 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
464 quote = *parser->data;
466 if (quote != '"' && quote != '\'')
468 _gtk_css_parser_error (parser, "Expected a string.");
473 str = g_string_new (NULL);
477 gsize len = strcspn (parser->data, "\\'\"\n\r\f");
479 g_string_append_len (str, parser->data, len);
483 switch (*parser->data)
486 _gtk_css_parser_unescape (parser, str);
490 if (*parser->data == quote)
493 _gtk_css_parser_skip_whitespace (parser);
494 return g_string_free (str, FALSE);
497 g_string_append_c (str, *parser->data);
501 /* FIXME: position */
502 _gtk_css_parser_error (parser, "Missing end quote in string.");
503 g_string_free (str, TRUE);
506 _gtk_css_parser_error (parser,
507 "Invalid character in string. Must be escaped.");
508 g_string_free (str, TRUE);
513 g_assert_not_reached ();
518 _gtk_css_parser_try_int (GtkCssParser *parser,
524 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
525 g_return_val_if_fail (value != NULL, FALSE);
527 /* strtoll parses a plus, but we are not allowed to */
528 if (*parser->data == '+')
532 result = g_ascii_strtoll (parser->data, &end, 10);
535 if (result > G_MAXINT || result < G_MININT)
537 if (parser->data == end)
543 _gtk_css_parser_skip_whitespace (parser);
549 _gtk_css_parser_try_uint (GtkCssParser *parser,
555 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
556 g_return_val_if_fail (value != NULL, FALSE);
559 result = g_ascii_strtoull (parser->data, &end, 10);
562 if (result > G_MAXUINT)
564 if (parser->data == end)
570 _gtk_css_parser_skip_whitespace (parser);
576 _gtk_css_parser_try_double (GtkCssParser *parser,
582 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
583 g_return_val_if_fail (value != NULL, FALSE);
586 result = g_ascii_strtod (parser->data, &end);
589 if (parser->data == end)
595 _gtk_css_parser_skip_whitespace (parser);
601 _gtk_css_parser_has_number (GtkCssParser *parser)
604 return strchr ("+-0123456789.", parser->data[0]) != NULL;
608 _gtk_css_number_value_parse (GtkCssParser *parser,
609 GtkCssNumberParseFlags flags)
611 static const struct {
614 GtkCssNumberParseFlags required_flags;
616 { "px", GTK_CSS_PX, GTK_CSS_PARSE_LENGTH },
617 { "pt", GTK_CSS_PT, GTK_CSS_PARSE_LENGTH },
618 { "em", GTK_CSS_EM, GTK_CSS_PARSE_LENGTH },
619 { "ex", GTK_CSS_EX, GTK_CSS_PARSE_LENGTH },
620 { "pc", GTK_CSS_PC, GTK_CSS_PARSE_LENGTH },
621 { "in", GTK_CSS_IN, GTK_CSS_PARSE_LENGTH },
622 { "cm", GTK_CSS_CM, GTK_CSS_PARSE_LENGTH },
623 { "mm", GTK_CSS_MM, GTK_CSS_PARSE_LENGTH },
624 { "rad", GTK_CSS_RAD, GTK_CSS_PARSE_ANGLE },
625 { "deg", GTK_CSS_DEG, GTK_CSS_PARSE_ANGLE },
626 { "grad", GTK_CSS_GRAD, GTK_CSS_PARSE_ANGLE },
627 { "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE },
628 { "s", GTK_CSS_S, GTK_CSS_PARSE_TIME },
629 { "ms", GTK_CSS_MS, GTK_CSS_PARSE_TIME }
631 char *end, *unit_name;
635 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
638 value = g_ascii_strtod (parser->data, &end);
641 _gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
644 if (parser->data == end)
646 _gtk_css_parser_error (parser, "not a number");
652 if (flags & GTK_CSS_POSITIVE_ONLY &&
655 _gtk_css_parser_error (parser, "negative values are not allowed.");
659 unit_name = _gtk_css_parser_try_ident (parser, FALSE);
665 for (i = 0; i < G_N_ELEMENTS (units); i++)
667 if (flags & units[i].required_flags &&
668 g_ascii_strcasecmp (unit_name, units[i].name) == 0)
672 if (i >= G_N_ELEMENTS (units))
674 _gtk_css_parser_error (parser, "`%s' is not a valid unit.", unit_name);
679 unit = units[i].unit;
685 if ((flags & GTK_CSS_PARSE_PERCENT) &&
686 _gtk_css_parser_try (parser, "%", FALSE))
688 unit = GTK_CSS_PERCENT;
690 else if (value == 0.0)
692 if (flags & GTK_CSS_PARSE_NUMBER)
693 unit = GTK_CSS_NUMBER;
694 else if (flags & GTK_CSS_PARSE_LENGTH)
696 else if (flags & GTK_CSS_PARSE_ANGLE)
698 else if (flags & GTK_CSS_PARSE_TIME)
701 unit = GTK_CSS_PERCENT;
703 else if (flags & GTK_CSS_NUMBER_AS_PIXELS)
705 _gtk_css_parser_error_full (parser,
706 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
707 "Not using units is deprecated. Assuming 'px'.");
710 else if (flags & GTK_CSS_PARSE_NUMBER)
712 unit = GTK_CSS_NUMBER;
716 _gtk_css_parser_error (parser, "Unit is missing.");
721 _gtk_css_parser_skip_whitespace (parser);
723 return _gtk_css_number_value_new (value, unit);
726 /* XXX: we should introduce GtkCssLenght that deals with
727 * different kind of units */
729 _gtk_css_parser_try_length (GtkCssParser *parser,
732 if (!_gtk_css_parser_try_int (parser, value))
735 /* FIXME: _try_uint skips spaces while the
738 _gtk_css_parser_try (parser, "px", TRUE);
744 _gtk_css_parser_try_enum (GtkCssParser *parser,
748 GEnumClass *enum_class;
753 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
754 g_return_val_if_fail (value != NULL, FALSE);
758 enum_class = g_type_class_ref (enum_type);
760 start = parser->data;
762 str = _gtk_css_parser_try_ident (parser, TRUE);
766 if (enum_class->n_values)
768 GEnumValue *enum_value;
770 for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
772 if (enum_value->value_nick &&
773 g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
775 *value = enum_value->value;
783 g_type_class_unref (enum_class);
786 parser->data = start;
792 _gtk_css_parser_try_hash_color (GtkCssParser *parser,
795 if (parser->data[0] == '#' &&
796 g_ascii_isxdigit (parser->data[1]) &&
797 g_ascii_isxdigit (parser->data[2]) &&
798 g_ascii_isxdigit (parser->data[3]))
800 if (g_ascii_isxdigit (parser->data[4]) &&
801 g_ascii_isxdigit (parser->data[5]) &&
802 g_ascii_isxdigit (parser->data[6]))
804 rgba->red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
805 rgba->green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
806 rgba->blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
812 rgba->red = get_xdigit (parser->data[1]) / 15.0;
813 rgba->green = get_xdigit (parser->data[2]) / 15.0;
814 rgba->blue = get_xdigit (parser->data[3]) / 15.0;
819 _gtk_css_parser_skip_whitespace (parser);
828 _gtk_css_parser_read_url (GtkCssParser *parser)
834 if (_gtk_css_parser_try (parser, "url", FALSE))
836 if (!_gtk_css_parser_try (parser, "(", TRUE))
838 _gtk_css_parser_skip_whitespace (parser);
839 if (_gtk_css_parser_try (parser, "(", TRUE))
841 _gtk_css_parser_error_full (parser,
842 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
843 "Whitespace between 'url' and '(' is deprecated");
847 _gtk_css_parser_error (parser, "Expected '(' after 'url'");
852 path = _gtk_css_parser_read_string (parser);
856 if (!_gtk_css_parser_try (parser, ")", TRUE))
858 _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
863 scheme = g_uri_parse_scheme (path);
866 file = g_file_new_for_uri (path);
874 path = _gtk_css_parser_try_name (parser, TRUE);
877 _gtk_css_parser_error (parser, "Not a valid url");
882 file = _gtk_css_parser_get_file_for_path (parser, path);
889 gtk_css_parser_resync_internal (GtkCssParser *parser,
890 gboolean sync_at_semicolon,
891 gboolean read_sync_token,
897 len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
900 if (gtk_css_parser_new_line (parser))
903 if (_gtk_css_parser_is_string (parser))
905 /* Hrm, this emits errors, and i suspect it shouldn't... */
906 char *free_me = _gtk_css_parser_read_string (parser);
911 if (gtk_css_parser_skip_comment (parser))
914 switch (*parser->data)
918 GString *ignore = g_string_new (NULL);
919 _gtk_css_parser_unescape (parser, ignore);
920 g_string_free (ignore, TRUE);
924 if (sync_at_semicolon && !read_sync_token)
927 if (sync_at_semicolon)
929 _gtk_css_parser_skip_whitespace (parser);
935 _gtk_css_parser_resync (parser, FALSE, ')');
941 _gtk_css_parser_resync (parser, FALSE, ']');
947 _gtk_css_parser_resync (parser, FALSE, '}');
950 if (sync_at_semicolon || !terminator)
952 _gtk_css_parser_skip_whitespace (parser);
959 if (terminator == *parser->data)
961 _gtk_css_parser_skip_whitespace (parser);
973 } while (*parser->data);
977 _gtk_css_parser_read_value (GtkCssParser *parser)
982 g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
984 start = parser->data;
986 /* This needs to be done better */
987 gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
989 result = g_strndup (start, parser->data - start);
1001 _gtk_css_parser_error (parser, "Expected a property value");
1007 _gtk_css_parser_resync (GtkCssParser *parser,
1008 gboolean sync_at_semicolon,
1011 g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1013 gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);