]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssparser.c
fc6dc3e95aca131c0d9e15837bfd642415cd4227
[~andy/gtk] / gtk / gtkcssparser.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssparserprivate.h"
23 #include "gtkwin32themeprivate.h"
24
25 #include <errno.h>
26 #include <string.h>
27
28 /* just for the errors, yay! */
29 #include "gtkcssprovider.h"
30
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 "!#$%&*~"
36
37 #define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
38
39 struct _GtkCssParser
40 {
41   const char *data;
42   GtkCssParserErrorFunc error_func;
43   gpointer              user_data;
44
45   const char *line_start;
46   guint line;
47 };
48
49 GtkCssParser *
50 _gtk_css_parser_new (const char            *data,
51                      GtkCssParserErrorFunc  error_func,
52                      gpointer               user_data)
53 {
54   GtkCssParser *parser;
55
56   g_return_val_if_fail (data != NULL, NULL);
57
58   parser = g_slice_new0 (GtkCssParser);
59
60   parser->data = data;
61   parser->error_func = error_func;
62   parser->user_data = user_data;
63
64   parser->line_start = data;
65   parser->line = 0;
66
67   return parser;
68 }
69
70 void
71 _gtk_css_parser_free (GtkCssParser *parser)
72 {
73   g_return_if_fail (GTK_IS_CSS_PARSER (parser));
74
75   g_slice_free (GtkCssParser, parser);
76 }
77
78 gboolean
79 _gtk_css_parser_is_eof (GtkCssParser *parser)
80 {
81   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
82
83   return *parser->data == 0;
84 }
85
86 gboolean
87 _gtk_css_parser_begins_with (GtkCssParser *parser,
88                              char          c)
89 {
90   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
91
92   return *parser->data == c;
93 }
94
95 gboolean
96 _gtk_css_parser_has_prefix (GtkCssParser *parser,
97                             const char   *prefix)
98 {
99   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
100
101   return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
102 }
103
104 guint
105 _gtk_css_parser_get_line (GtkCssParser *parser)
106 {
107   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
108
109   return parser->line;
110 }
111
112 guint
113 _gtk_css_parser_get_position (GtkCssParser *parser)
114 {
115   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
116
117   return parser->data - parser->line_start;
118 }
119
120 void
121 _gtk_css_parser_take_error (GtkCssParser *parser,
122                             GError       *error)
123 {
124   parser->error_func (parser, error, parser->user_data);
125
126   g_error_free (error);
127 }
128
129 void
130 _gtk_css_parser_error (GtkCssParser *parser,
131                        const char   *format,
132                        ...)
133 {
134   GError *error;
135
136   va_list args;
137
138   va_start (args, format);
139   error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
140                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
141                               format, args);
142   va_end (args);
143
144   _gtk_css_parser_take_error (parser, error);
145 }
146
147 static gboolean
148 gtk_css_parser_new_line (GtkCssParser *parser)
149 {
150   gboolean result = FALSE;
151
152   if (*parser->data == '\r')
153     {
154       result = TRUE;
155       parser->data++;
156     }
157   if (*parser->data == '\n')
158     {
159       result = TRUE;
160       parser->data++;
161     }
162
163   if (result)
164     {
165       parser->line++;
166       parser->line_start = parser->data;
167     }
168
169   return result;
170 }
171
172 static gboolean
173 gtk_css_parser_skip_comment (GtkCssParser *parser)
174 {
175   if (parser->data[0] != '/' ||
176       parser->data[1] != '*')
177     return FALSE;
178
179   parser->data += 2;
180
181   while (*parser->data)
182     {
183       gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
184
185       parser->data += len;
186   
187       if (gtk_css_parser_new_line (parser))
188         continue;
189
190       parser->data++;
191
192       if (parser->data[-2] == '*')
193         return TRUE;
194       if (parser->data[0] == '*')
195         _gtk_css_parser_error (parser, "'/*' in comment block");
196     }
197
198   /* FIXME: position */
199   _gtk_css_parser_error (parser, "Unterminated comment");
200   return TRUE;
201 }
202
203 void
204 _gtk_css_parser_skip_whitespace (GtkCssParser *parser)
205 {
206   size_t len;
207
208   while (*parser->data)
209     {
210       if (gtk_css_parser_new_line (parser))
211         continue;
212
213       len = strspn (parser->data, WHITESPACE_CHARS);
214       if (len)
215         {
216           parser->data += len;
217           continue;
218         }
219       
220       if (!gtk_css_parser_skip_comment (parser))
221         break;
222     }
223 }
224
225 gboolean
226 _gtk_css_parser_try (GtkCssParser *parser,
227                      const char   *string,
228                      gboolean      skip_whitespace)
229 {
230   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
231   g_return_val_if_fail (string != NULL, FALSE);
232
233   if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
234     return FALSE;
235
236   parser->data += strlen (string);
237
238   if (skip_whitespace)
239     _gtk_css_parser_skip_whitespace (parser);
240   return TRUE;
241 }
242
243 static guint
244 get_xdigit (char c)
245 {
246   if (c >= 'a')
247     return c - 'a' + 10;
248   else if (c >= 'A')
249     return c - 'A' + 10;
250   else
251     return c - '0';
252 }
253
254 static void
255 _gtk_css_parser_unescape (GtkCssParser *parser,
256                           GString      *str)
257 {
258   guint i;
259   gunichar result = 0;
260
261   g_assert (*parser->data == '\\');
262
263   parser->data++;
264
265   for (i = 0; i < 6; i++)
266     {
267       if (!g_ascii_isxdigit (parser->data[i]))
268         break;
269
270       result = (result << 4) + get_xdigit (parser->data[i]);
271     }
272
273   if (i != 0)
274     {
275       g_string_append_unichar (str, result);
276       parser->data += i;
277
278       /* NB: gtk_css_parser_new_line() forward data pointer itself */
279       if (!gtk_css_parser_new_line (parser) &&
280           *parser->data &&
281           strchr (WHITESPACE_CHARS, *parser->data))
282         parser->data++;
283       return;
284     }
285
286   if (gtk_css_parser_new_line (parser))
287     return;
288
289   g_string_append_c (str, *parser->data);
290   parser->data++;
291
292   return;
293 }
294
295 static gboolean
296 _gtk_css_parser_read_char (GtkCssParser *parser,
297                            GString *     str,
298                            const char *  allowed)
299 {
300   if (*parser->data == 0)
301     return FALSE;
302
303   if (strchr (allowed, *parser->data))
304     {
305       g_string_append_c (str, *parser->data);
306       parser->data++;
307       return TRUE;
308     }
309   if (*parser->data >= 127)
310     {
311       gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
312
313       g_string_append_len (str, parser->data, len);
314       parser->data += len;
315       return TRUE;
316     }
317   if (*parser->data == '\\')
318     {
319       _gtk_css_parser_unescape (parser, str);
320       return TRUE;
321     }
322
323   return FALSE;
324 }
325
326 char *
327 _gtk_css_parser_try_name (GtkCssParser *parser,
328                           gboolean      skip_whitespace)
329 {
330   GString *name;
331
332   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
333
334   name = g_string_new (NULL);
335
336   while (_gtk_css_parser_read_char (parser, name, NMCHAR))
337     ;
338
339   if (skip_whitespace)
340     _gtk_css_parser_skip_whitespace (parser);
341
342   return g_string_free (name, FALSE);
343 }
344
345 char *
346 _gtk_css_parser_try_ident (GtkCssParser *parser,
347                            gboolean      skip_whitespace)
348 {
349   const char *start;
350   GString *ident;
351
352   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
353
354   start = parser->data;
355   
356   ident = g_string_new (NULL);
357
358   if (*parser->data == '-')
359     {
360       g_string_append_c (ident, '-');
361       parser->data++;
362     }
363
364   if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
365     {
366       parser->data = start;
367       g_string_free (ident, TRUE);
368       return NULL;
369     }
370
371   while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
372     ;
373
374   if (skip_whitespace)
375     _gtk_css_parser_skip_whitespace (parser);
376
377   return g_string_free (ident, FALSE);
378 }
379
380 gboolean
381 _gtk_css_parser_is_string (GtkCssParser *parser)
382 {
383   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
384
385   return *parser->data == '"' || *parser->data == '\'';
386 }
387
388 char *
389 _gtk_css_parser_read_string (GtkCssParser *parser)
390 {
391   GString *str;
392   char quote;
393
394   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
395
396   quote = *parser->data;
397   
398   if (quote != '"' && quote != '\'')
399     {
400       _gtk_css_parser_error (parser, "Expected a string.");
401       return NULL;
402     }
403   
404   parser->data++;
405   str = g_string_new (NULL);
406
407   while (TRUE)
408     {
409       gsize len = strcspn (parser->data, "\\'\"\n\r\f");
410
411       g_string_append_len (str, parser->data, len);
412
413       parser->data += len;
414
415       switch (*parser->data)
416         {
417         case '\\':
418           _gtk_css_parser_unescape (parser, str);
419           break;
420         case '"':
421         case '\'':
422           if (*parser->data == quote)
423             {
424               parser->data++;
425               _gtk_css_parser_skip_whitespace (parser);
426               return g_string_free (str, FALSE);
427             }
428           
429           g_string_append_c (str, *parser->data);
430           parser->data++;
431           break;
432         case '\0':
433           /* FIXME: position */
434           _gtk_css_parser_error (parser, "Missing end quote in string.");
435           g_string_free (str, TRUE);
436           return NULL;
437         default:
438           _gtk_css_parser_error (parser, 
439                                  "Invalid character in string. Must be escaped.");
440           g_string_free (str, TRUE);
441           return NULL;
442         }
443     }
444
445   g_assert_not_reached ();
446   return NULL;
447 }
448
449 char *
450 _gtk_css_parser_read_uri (GtkCssParser *parser)
451 {
452   char *result;
453
454   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
455
456   if (!_gtk_css_parser_try (parser, "url(", TRUE))
457     {
458       _gtk_css_parser_error (parser, "expected 'url('");
459       return NULL;
460     }
461
462   _gtk_css_parser_skip_whitespace (parser);
463
464   if (_gtk_css_parser_is_string (parser))
465     {
466       result = _gtk_css_parser_read_string (parser);
467     }
468   else
469     {
470       GString *str = g_string_new (NULL);
471
472       while (_gtk_css_parser_read_char (parser, str, URLCHAR))
473         ;
474       result = g_string_free (str, FALSE);
475       if (result == NULL)
476         _gtk_css_parser_error (parser, "not a url");
477     }
478   
479   if (result == NULL)
480     return NULL;
481
482   _gtk_css_parser_skip_whitespace (parser);
483
484   if (*parser->data != ')')
485     {
486       _gtk_css_parser_error (parser, "missing ')' for url");
487       g_free (result);
488       return NULL;
489     }
490
491   parser->data++;
492
493   _gtk_css_parser_skip_whitespace (parser);
494
495   return result;
496 }
497
498 gboolean
499 _gtk_css_parser_try_int (GtkCssParser *parser,
500                          int          *value)
501 {
502   gint64 result;
503   char *end;
504
505   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
506   g_return_val_if_fail (value != NULL, FALSE);
507
508   /* strtoll parses a plus, but we are not allowed to */
509   if (*parser->data == '+')
510     return FALSE;
511
512   errno = 0;
513   result = g_ascii_strtoll (parser->data, &end, 10);
514   if (errno)
515     return FALSE;
516   if (result > G_MAXINT || result < G_MININT)
517     return FALSE;
518   if (parser->data == end)
519     return FALSE;
520
521   parser->data = end;
522   *value = result;
523
524   _gtk_css_parser_skip_whitespace (parser);
525
526   return TRUE;
527 }
528
529 gboolean
530 _gtk_css_parser_try_uint (GtkCssParser *parser,
531                           guint        *value)
532 {
533   guint64 result;
534   char *end;
535
536   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
537   g_return_val_if_fail (value != NULL, FALSE);
538
539   errno = 0;
540   result = g_ascii_strtoull (parser->data, &end, 10);
541   if (errno)
542     return FALSE;
543   if (result > G_MAXUINT)
544     return FALSE;
545   if (parser->data == end)
546     return FALSE;
547
548   parser->data = end;
549   *value = result;
550
551   _gtk_css_parser_skip_whitespace (parser);
552
553   return TRUE;
554 }
555
556 gboolean
557 _gtk_css_parser_try_double (GtkCssParser *parser,
558                             gdouble      *value)
559 {
560   gdouble result;
561   char *end;
562
563   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
564   g_return_val_if_fail (value != NULL, FALSE);
565
566   errno = 0;
567   result = g_ascii_strtod (parser->data, &end);
568   if (errno)
569     return FALSE;
570   if (parser->data == end)
571     return FALSE;
572
573   parser->data = end;
574   *value = result;
575
576   _gtk_css_parser_skip_whitespace (parser);
577
578   return TRUE;
579 }
580
581 /* XXX: we should introduce GtkCssLenght that deals with
582  * different kind of units */
583 gboolean
584 _gtk_css_parser_try_length (GtkCssParser *parser,
585                             int          *value)
586 {
587   if (!_gtk_css_parser_try_int (parser, value))
588     return FALSE;
589
590   /* FIXME: _try_uint skips spaces while the
591    * spec forbids them
592    */
593   _gtk_css_parser_try (parser, "px", TRUE);
594
595   return TRUE;
596 }
597
598 gboolean
599 _gtk_css_parser_try_enum (GtkCssParser *parser,
600                           GType         enum_type,
601                           int          *value)
602 {
603   GEnumClass *enum_class;
604   gboolean result;
605   const char *start;
606   char *str;
607
608   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
609   g_return_val_if_fail (value != NULL, FALSE);
610
611   result = FALSE;
612
613   enum_class = g_type_class_ref (enum_type);
614
615   start = parser->data;
616
617   str = _gtk_css_parser_try_ident (parser, TRUE);
618   if (str == NULL)
619     return FALSE;
620
621   if (enum_class->n_values)
622     {
623       GEnumValue *enum_value;
624
625       for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
626         {
627           if (enum_value->value_nick &&
628               g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
629             {
630               *value = enum_value->value;
631               result = TRUE;
632               break;
633             }
634         }
635     }
636
637   g_free (str);
638   g_type_class_unref (enum_class);
639
640   if (!result)
641     parser->data = start;
642
643   return result;
644 }
645
646 typedef enum {
647   COLOR_RGBA,
648   COLOR_RGB,
649   COLOR_LIGHTER,
650   COLOR_DARKER,
651   COLOR_SHADE,
652   COLOR_ALPHA,
653   COLOR_MIX,
654   COLOR_WIN32
655 } ColorType;
656
657 static GtkSymbolicColor *
658 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
659                                              ColorType     color)
660 {
661   GtkSymbolicColor *symbolic;
662   GtkSymbolicColor *child1, *child2;
663   double value;
664
665   if (!_gtk_css_parser_try (parser, "(", TRUE))
666     {
667       _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
668       return NULL;
669     }
670
671   if (color == COLOR_RGB || color == COLOR_RGBA)
672     {
673       GdkRGBA rgba;
674       double tmp;
675       guint i;
676
677       for (i = 0; i < 3; i++)
678         {
679           if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
680             {
681               _gtk_css_parser_error (parser, "Expected ',' in color definition");
682               return NULL;
683             }
684
685           if (!_gtk_css_parser_try_double (parser, &tmp))
686             {
687               _gtk_css_parser_error (parser, "Invalid number for color value");
688               return NULL;
689             }
690           if (_gtk_css_parser_try (parser, "%", TRUE))
691             tmp /= 100.0;
692           else
693             tmp /= 255.0;
694           if (i == 0)
695             rgba.red = tmp;
696           else if (i == 1)
697             rgba.green = tmp;
698           else if (i == 2)
699             rgba.blue = tmp;
700           else
701             g_assert_not_reached ();
702         }
703
704       if (color == COLOR_RGBA)
705         {
706           if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
707             {
708               _gtk_css_parser_error (parser, "Expected ',' in color definition");
709               return NULL;
710             }
711
712           if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
713             {
714               _gtk_css_parser_error (parser, "Invalid number for alpha value");
715               return NULL;
716             }
717         }
718       else
719         rgba.alpha = 1.0;
720       
721       symbolic = gtk_symbolic_color_new_literal (&rgba);
722     }
723   else if (color == COLOR_WIN32)
724     {
725       symbolic = _gtk_win32_theme_color_parse (parser);
726       if (symbolic == NULL)
727         return NULL;
728     }
729   else
730     {
731       child1 = _gtk_css_parser_read_symbolic_color (parser);
732       if (child1 == NULL)
733         return NULL;
734
735       if (color == COLOR_MIX)
736         {
737           if (!_gtk_css_parser_try (parser, ",", TRUE))
738             {
739               _gtk_css_parser_error (parser, "Expected ',' in color definition");
740               gtk_symbolic_color_unref (child1);
741               return NULL;
742             }
743
744           child2 = _gtk_css_parser_read_symbolic_color (parser);
745           if (child2 == NULL)
746             {
747               gtk_symbolic_color_unref (child1);
748               return NULL;
749             }
750         }
751       else
752         child2 = NULL;
753
754       if (color == COLOR_LIGHTER)
755         value = 1.3;
756       else if (color == COLOR_DARKER)
757         value = 0.7;
758       else
759         {
760           if (!_gtk_css_parser_try (parser, ",", TRUE))
761             {
762               _gtk_css_parser_error (parser, "Expected ',' in color definition");
763               gtk_symbolic_color_unref (child1);
764               if (child2)
765                 gtk_symbolic_color_unref (child2);
766               return NULL;
767             }
768
769           if (!_gtk_css_parser_try_double (parser, &value))
770             {
771               _gtk_css_parser_error (parser, "Expected number in color definition");
772               gtk_symbolic_color_unref (child1);
773               if (child2)
774                 gtk_symbolic_color_unref (child2);
775               return NULL;
776             }
777         }
778       
779       switch (color)
780         {
781         case COLOR_LIGHTER:
782         case COLOR_DARKER:
783         case COLOR_SHADE:
784           symbolic = gtk_symbolic_color_new_shade (child1, value);
785           break;
786         case COLOR_ALPHA:
787           symbolic = gtk_symbolic_color_new_alpha (child1, value);
788           break;
789         case COLOR_MIX:
790           symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
791           break;
792         default:
793           g_assert_not_reached ();
794           symbolic = NULL;
795         }
796
797       gtk_symbolic_color_unref (child1);
798       if (child2)
799         gtk_symbolic_color_unref (child2);
800     }
801
802   if (!_gtk_css_parser_try (parser, ")", TRUE))
803     {
804       _gtk_css_parser_error (parser, "Expected ')' in color definition");
805       gtk_symbolic_color_unref (symbolic);
806       return NULL;
807     }
808
809   return symbolic;
810 }
811
812 static GtkSymbolicColor *
813 gtk_css_parser_try_hash_color (GtkCssParser *parser)
814 {
815   if (parser->data[0] == '#' &&
816       g_ascii_isxdigit (parser->data[1]) &&
817       g_ascii_isxdigit (parser->data[2]) &&
818       g_ascii_isxdigit (parser->data[3]))
819     {
820       GdkRGBA rgba;
821       
822       if (g_ascii_isxdigit (parser->data[4]) &&
823           g_ascii_isxdigit (parser->data[5]) &&
824           g_ascii_isxdigit (parser->data[6]))
825         {
826           rgba.red   = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
827           rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
828           rgba.blue  = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
829           rgba.alpha = 1.0;
830           parser->data += 7;
831         }
832       else
833         {
834           rgba.red   = get_xdigit (parser->data[1]) / 15.0;
835           rgba.green = get_xdigit (parser->data[2]) / 15.0;
836           rgba.blue  = get_xdigit (parser->data[3]) / 15.0;
837           rgba.alpha = 1.0;
838           parser->data += 4;
839         }
840
841       _gtk_css_parser_skip_whitespace (parser);
842
843       return gtk_symbolic_color_new_literal (&rgba);
844     }
845
846   return NULL;
847 }
848
849 GtkSymbolicColor *
850 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
851 {
852   GtkSymbolicColor *symbolic;
853   guint color;
854   const char *names[] = {"rgba", "rgb",  "lighter", "darker", "shade", "alpha", "mix",
855                          GTK_WIN32_THEME_SYMBOLIC_COLOR_NAME};
856   char *name;
857
858   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
859
860   if (_gtk_css_parser_try (parser, "transparent", TRUE))
861     {
862       GdkRGBA transparent = { 0, 0, 0, 0 };
863       
864       return gtk_symbolic_color_new_literal (&transparent);
865     }
866
867   if (_gtk_css_parser_try (parser, "@", FALSE))
868     {
869       name = _gtk_css_parser_try_name (parser, TRUE);
870
871       if (name)
872         {
873           symbolic = gtk_symbolic_color_new_name (name);
874         }
875       else
876         {
877           _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
878           symbolic = NULL;
879         }
880
881       g_free (name);
882       return symbolic;
883     }
884
885   for (color = 0; color < G_N_ELEMENTS (names); color++)
886     {
887       if (_gtk_css_parser_try (parser, names[color], TRUE))
888         break;
889     }
890
891   if (color < G_N_ELEMENTS (names))
892     return gtk_css_parser_read_symbolic_color_function (parser, color);
893
894   symbolic = gtk_css_parser_try_hash_color (parser);
895   if (symbolic)
896     return symbolic;
897
898   name = _gtk_css_parser_try_name (parser, TRUE);
899   if (name)
900     {
901       GdkRGBA rgba;
902
903       if (gdk_rgba_parse (&rgba, name))
904         {
905           symbolic = gtk_symbolic_color_new_literal (&rgba);
906         }
907       else
908         {
909           _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
910           symbolic = NULL;
911         }
912       g_free (name);
913       return symbolic;
914     }
915
916   _gtk_css_parser_error (parser, "Not a color definition");
917   return NULL;
918 }
919
920 GFile *
921 _gtk_css_parser_read_url (GtkCssParser *parser,
922                           GFile        *base)
923 {
924   gchar *path;
925   GFile *file;
926
927   if (_gtk_css_parser_try (parser, "url", FALSE))
928     {
929       if (!_gtk_css_parser_try (parser, "(", TRUE))
930         {
931           _gtk_css_parser_skip_whitespace (parser);
932           if (_gtk_css_parser_try (parser, "(", TRUE))
933             {
934               GError *error;
935               
936               error = g_error_new_literal (GTK_CSS_PROVIDER_ERROR,
937                                            GTK_CSS_PROVIDER_ERROR_DEPRECATED,
938                                            "Whitespace between 'url' and '(' is deprecated");
939                              
940               _gtk_css_parser_take_error (parser, error);
941             }
942           else
943             {
944               _gtk_css_parser_error (parser, "Expected '(' after 'url'");
945               return NULL;
946             }
947         }
948
949       path = _gtk_css_parser_read_string (parser);
950       if (path == NULL)
951         return NULL;
952
953       if (!_gtk_css_parser_try (parser, ")", TRUE))
954         {
955           _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
956           g_free (path);
957           return NULL;
958         }
959     }
960   else
961     {
962       path = _gtk_css_parser_try_name (parser, TRUE);
963       if (path == NULL)
964         {
965           _gtk_css_parser_error (parser, "Not a valid url");
966           return NULL;
967         }
968     }
969
970   file = g_file_resolve_relative_path (base, path);
971   g_free (path);
972
973   return file;
974 }
975
976 void
977 _gtk_css_parser_resync_internal (GtkCssParser *parser,
978                                  gboolean      sync_at_semicolon,
979                                  gboolean      read_sync_token,
980                                  char          terminator)
981 {
982   gsize len;
983
984   do {
985     len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
986     parser->data += len;
987
988     if (gtk_css_parser_new_line (parser))
989       continue;
990
991     if (_gtk_css_parser_is_string (parser))
992       {
993         /* Hrm, this emits errors, and i suspect it shouldn't... */
994         char *free_me = _gtk_css_parser_read_string (parser);
995         g_free (free_me);
996         continue;
997       }
998
999     if (gtk_css_parser_skip_comment (parser))
1000       continue;
1001
1002     switch (*parser->data)
1003       {
1004       case '\\':
1005         {
1006           GString *ignore = g_string_new (NULL);
1007           _gtk_css_parser_unescape (parser, ignore);
1008           g_string_free (ignore, TRUE);
1009         }
1010         break;
1011       case ';':
1012         if (sync_at_semicolon && !read_sync_token)
1013           return;
1014         parser->data++;
1015         if (sync_at_semicolon)
1016           {
1017             _gtk_css_parser_skip_whitespace (parser);
1018             return;
1019           }
1020         break;
1021       case '(':
1022         parser->data++;
1023         _gtk_css_parser_resync (parser, FALSE, ')');
1024         if (*parser->data)
1025           parser->data++;
1026         break;
1027       case '[':
1028         parser->data++;
1029         _gtk_css_parser_resync (parser, FALSE, ']');
1030         if (*parser->data)
1031           parser->data++;
1032         break;
1033       case '{':
1034         parser->data++;
1035         _gtk_css_parser_resync (parser, FALSE, '}');
1036         if (*parser->data)
1037           parser->data++;
1038         if (sync_at_semicolon || !terminator)
1039           {
1040             _gtk_css_parser_skip_whitespace (parser);
1041             return;
1042           }
1043         break;
1044       case '}':
1045       case ')':
1046       case ']':
1047         if (terminator == *parser->data)
1048           {
1049             _gtk_css_parser_skip_whitespace (parser);
1050             return;
1051           }
1052         parser->data++;
1053         continue;
1054       case '\0':
1055         break;
1056       case '/':
1057       default:
1058         parser->data++;
1059         break;
1060       }
1061   } while (*parser->data);
1062 }
1063
1064 char *
1065 _gtk_css_parser_read_value (GtkCssParser *parser)
1066 {
1067   const char *start;
1068   char *result;
1069
1070   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
1071
1072   start = parser->data;
1073
1074   /* This needs to be done better */
1075   _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
1076
1077   result = g_strndup (start, parser->data - start);
1078   if (result)
1079     {
1080       g_strchomp (result);
1081       if (result[0] == 0)
1082         {
1083           g_free (result);
1084           result = NULL;
1085         }
1086     }
1087
1088   if (result == NULL)
1089     _gtk_css_parser_error (parser, "Expected a property value");
1090
1091   return result;
1092 }
1093
1094 void
1095 _gtk_css_parser_resync (GtkCssParser *parser,
1096                         gboolean      sync_at_semicolon,
1097                         char          terminator)
1098 {
1099   g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1100
1101   _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
1102 }