]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssparser.c
Ensure we can load images via resource:// uris from CSS
[~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 gboolean
450 _gtk_css_parser_try_int (GtkCssParser *parser,
451                          int          *value)
452 {
453   gint64 result;
454   char *end;
455
456   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
457   g_return_val_if_fail (value != NULL, FALSE);
458
459   /* strtoll parses a plus, but we are not allowed to */
460   if (*parser->data == '+')
461     return FALSE;
462
463   errno = 0;
464   result = g_ascii_strtoll (parser->data, &end, 10);
465   if (errno)
466     return FALSE;
467   if (result > G_MAXINT || result < G_MININT)
468     return FALSE;
469   if (parser->data == end)
470     return FALSE;
471
472   parser->data = end;
473   *value = result;
474
475   _gtk_css_parser_skip_whitespace (parser);
476
477   return TRUE;
478 }
479
480 gboolean
481 _gtk_css_parser_try_uint (GtkCssParser *parser,
482                           guint        *value)
483 {
484   guint64 result;
485   char *end;
486
487   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
488   g_return_val_if_fail (value != NULL, FALSE);
489
490   errno = 0;
491   result = g_ascii_strtoull (parser->data, &end, 10);
492   if (errno)
493     return FALSE;
494   if (result > G_MAXUINT)
495     return FALSE;
496   if (parser->data == end)
497     return FALSE;
498
499   parser->data = end;
500   *value = result;
501
502   _gtk_css_parser_skip_whitespace (parser);
503
504   return TRUE;
505 }
506
507 gboolean
508 _gtk_css_parser_try_double (GtkCssParser *parser,
509                             gdouble      *value)
510 {
511   gdouble result;
512   char *end;
513
514   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
515   g_return_val_if_fail (value != NULL, FALSE);
516
517   errno = 0;
518   result = g_ascii_strtod (parser->data, &end);
519   if (errno)
520     return FALSE;
521   if (parser->data == end)
522     return FALSE;
523
524   parser->data = end;
525   *value = result;
526
527   _gtk_css_parser_skip_whitespace (parser);
528
529   return TRUE;
530 }
531
532 /* XXX: we should introduce GtkCssLenght that deals with
533  * different kind of units */
534 gboolean
535 _gtk_css_parser_try_length (GtkCssParser *parser,
536                             int          *value)
537 {
538   if (!_gtk_css_parser_try_int (parser, value))
539     return FALSE;
540
541   /* FIXME: _try_uint skips spaces while the
542    * spec forbids them
543    */
544   _gtk_css_parser_try (parser, "px", TRUE);
545
546   return TRUE;
547 }
548
549 gboolean
550 _gtk_css_parser_try_enum (GtkCssParser *parser,
551                           GType         enum_type,
552                           int          *value)
553 {
554   GEnumClass *enum_class;
555   gboolean result;
556   const char *start;
557   char *str;
558
559   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
560   g_return_val_if_fail (value != NULL, FALSE);
561
562   result = FALSE;
563
564   enum_class = g_type_class_ref (enum_type);
565
566   start = parser->data;
567
568   str = _gtk_css_parser_try_ident (parser, TRUE);
569   if (str == NULL)
570     return FALSE;
571
572   if (enum_class->n_values)
573     {
574       GEnumValue *enum_value;
575
576       for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
577         {
578           if (enum_value->value_nick &&
579               g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
580             {
581               *value = enum_value->value;
582               result = TRUE;
583               break;
584             }
585         }
586     }
587
588   g_free (str);
589   g_type_class_unref (enum_class);
590
591   if (!result)
592     parser->data = start;
593
594   return result;
595 }
596
597 typedef enum {
598   COLOR_RGBA,
599   COLOR_RGB,
600   COLOR_LIGHTER,
601   COLOR_DARKER,
602   COLOR_SHADE,
603   COLOR_ALPHA,
604   COLOR_MIX,
605   COLOR_WIN32
606 } ColorType;
607
608 static GtkSymbolicColor *
609 gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
610                                              ColorType     color)
611 {
612   GtkSymbolicColor *symbolic;
613   GtkSymbolicColor *child1, *child2;
614   double value;
615
616   if (!_gtk_css_parser_try (parser, "(", TRUE))
617     {
618       _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
619       return NULL;
620     }
621
622   if (color == COLOR_RGB || color == COLOR_RGBA)
623     {
624       GdkRGBA rgba;
625       double tmp;
626       guint i;
627
628       for (i = 0; i < 3; i++)
629         {
630           if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
631             {
632               _gtk_css_parser_error (parser, "Expected ',' in color definition");
633               return NULL;
634             }
635
636           if (!_gtk_css_parser_try_double (parser, &tmp))
637             {
638               _gtk_css_parser_error (parser, "Invalid number for color value");
639               return NULL;
640             }
641           if (_gtk_css_parser_try (parser, "%", TRUE))
642             tmp /= 100.0;
643           else
644             tmp /= 255.0;
645           if (i == 0)
646             rgba.red = tmp;
647           else if (i == 1)
648             rgba.green = tmp;
649           else if (i == 2)
650             rgba.blue = tmp;
651           else
652             g_assert_not_reached ();
653         }
654
655       if (color == COLOR_RGBA)
656         {
657           if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
658             {
659               _gtk_css_parser_error (parser, "Expected ',' in color definition");
660               return NULL;
661             }
662
663           if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
664             {
665               _gtk_css_parser_error (parser, "Invalid number for alpha value");
666               return NULL;
667             }
668         }
669       else
670         rgba.alpha = 1.0;
671       
672       symbolic = gtk_symbolic_color_new_literal (&rgba);
673     }
674   else if (color == COLOR_WIN32)
675     {
676       symbolic = _gtk_win32_theme_color_parse (parser);
677       if (symbolic == NULL)
678         return NULL;
679     }
680   else
681     {
682       child1 = _gtk_css_parser_read_symbolic_color (parser);
683       if (child1 == NULL)
684         return NULL;
685
686       if (color == COLOR_MIX)
687         {
688           if (!_gtk_css_parser_try (parser, ",", TRUE))
689             {
690               _gtk_css_parser_error (parser, "Expected ',' in color definition");
691               gtk_symbolic_color_unref (child1);
692               return NULL;
693             }
694
695           child2 = _gtk_css_parser_read_symbolic_color (parser);
696           if (child2 == NULL)
697             {
698               gtk_symbolic_color_unref (child1);
699               return NULL;
700             }
701         }
702       else
703         child2 = NULL;
704
705       if (color == COLOR_LIGHTER)
706         value = 1.3;
707       else if (color == COLOR_DARKER)
708         value = 0.7;
709       else
710         {
711           if (!_gtk_css_parser_try (parser, ",", TRUE))
712             {
713               _gtk_css_parser_error (parser, "Expected ',' in color definition");
714               gtk_symbolic_color_unref (child1);
715               if (child2)
716                 gtk_symbolic_color_unref (child2);
717               return NULL;
718             }
719
720           if (!_gtk_css_parser_try_double (parser, &value))
721             {
722               _gtk_css_parser_error (parser, "Expected number in color definition");
723               gtk_symbolic_color_unref (child1);
724               if (child2)
725                 gtk_symbolic_color_unref (child2);
726               return NULL;
727             }
728         }
729       
730       switch (color)
731         {
732         case COLOR_LIGHTER:
733         case COLOR_DARKER:
734         case COLOR_SHADE:
735           symbolic = gtk_symbolic_color_new_shade (child1, value);
736           break;
737         case COLOR_ALPHA:
738           symbolic = gtk_symbolic_color_new_alpha (child1, value);
739           break;
740         case COLOR_MIX:
741           symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
742           break;
743         default:
744           g_assert_not_reached ();
745           symbolic = NULL;
746         }
747
748       gtk_symbolic_color_unref (child1);
749       if (child2)
750         gtk_symbolic_color_unref (child2);
751     }
752
753   if (!_gtk_css_parser_try (parser, ")", TRUE))
754     {
755       _gtk_css_parser_error (parser, "Expected ')' in color definition");
756       gtk_symbolic_color_unref (symbolic);
757       return NULL;
758     }
759
760   return symbolic;
761 }
762
763 static GtkSymbolicColor *
764 gtk_css_parser_try_hash_color (GtkCssParser *parser)
765 {
766   if (parser->data[0] == '#' &&
767       g_ascii_isxdigit (parser->data[1]) &&
768       g_ascii_isxdigit (parser->data[2]) &&
769       g_ascii_isxdigit (parser->data[3]))
770     {
771       GdkRGBA rgba;
772       
773       if (g_ascii_isxdigit (parser->data[4]) &&
774           g_ascii_isxdigit (parser->data[5]) &&
775           g_ascii_isxdigit (parser->data[6]))
776         {
777           rgba.red   = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
778           rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
779           rgba.blue  = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
780           rgba.alpha = 1.0;
781           parser->data += 7;
782         }
783       else
784         {
785           rgba.red   = get_xdigit (parser->data[1]) / 15.0;
786           rgba.green = get_xdigit (parser->data[2]) / 15.0;
787           rgba.blue  = get_xdigit (parser->data[3]) / 15.0;
788           rgba.alpha = 1.0;
789           parser->data += 4;
790         }
791
792       _gtk_css_parser_skip_whitespace (parser);
793
794       return gtk_symbolic_color_new_literal (&rgba);
795     }
796
797   return NULL;
798 }
799
800 GtkSymbolicColor *
801 _gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
802 {
803   GtkSymbolicColor *symbolic;
804   guint color;
805   const char *names[] = {"rgba", "rgb",  "lighter", "darker", "shade", "alpha", "mix",
806                          GTK_WIN32_THEME_SYMBOLIC_COLOR_NAME};
807   char *name;
808
809   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
810
811   if (_gtk_css_parser_try (parser, "transparent", TRUE))
812     {
813       GdkRGBA transparent = { 0, 0, 0, 0 };
814       
815       return gtk_symbolic_color_new_literal (&transparent);
816     }
817
818   if (_gtk_css_parser_try (parser, "@", FALSE))
819     {
820       name = _gtk_css_parser_try_name (parser, TRUE);
821
822       if (name)
823         {
824           symbolic = gtk_symbolic_color_new_name (name);
825         }
826       else
827         {
828           _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
829           symbolic = NULL;
830         }
831
832       g_free (name);
833       return symbolic;
834     }
835
836   for (color = 0; color < G_N_ELEMENTS (names); color++)
837     {
838       if (_gtk_css_parser_try (parser, names[color], TRUE))
839         break;
840     }
841
842   if (color < G_N_ELEMENTS (names))
843     return gtk_css_parser_read_symbolic_color_function (parser, color);
844
845   symbolic = gtk_css_parser_try_hash_color (parser);
846   if (symbolic)
847     return symbolic;
848
849   name = _gtk_css_parser_try_name (parser, TRUE);
850   if (name)
851     {
852       GdkRGBA rgba;
853
854       if (gdk_rgba_parse (&rgba, name))
855         {
856           symbolic = gtk_symbolic_color_new_literal (&rgba);
857         }
858       else
859         {
860           _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
861           symbolic = NULL;
862         }
863       g_free (name);
864       return symbolic;
865     }
866
867   _gtk_css_parser_error (parser, "Not a color definition");
868   return NULL;
869 }
870
871 GFile *
872 _gtk_css_parser_read_url (GtkCssParser *parser,
873                           GFile        *base)
874 {
875   gchar *path;
876   char *scheme;
877   GFile *file;
878
879   if (_gtk_css_parser_try (parser, "url", FALSE))
880     {
881       if (!_gtk_css_parser_try (parser, "(", TRUE))
882         {
883           _gtk_css_parser_skip_whitespace (parser);
884           if (_gtk_css_parser_try (parser, "(", TRUE))
885             {
886               GError *error;
887               
888               error = g_error_new_literal (GTK_CSS_PROVIDER_ERROR,
889                                            GTK_CSS_PROVIDER_ERROR_DEPRECATED,
890                                            "Whitespace between 'url' and '(' is deprecated");
891                              
892               _gtk_css_parser_take_error (parser, error);
893             }
894           else
895             {
896               _gtk_css_parser_error (parser, "Expected '(' after 'url'");
897               return NULL;
898             }
899         }
900
901       path = _gtk_css_parser_read_string (parser);
902       if (path == NULL)
903         return NULL;
904
905       if (!_gtk_css_parser_try (parser, ")", TRUE))
906         {
907           _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
908           g_free (path);
909           return NULL;
910         }
911
912       scheme = g_uri_parse_scheme (path);
913       if (scheme != NULL)
914         {
915           file = g_file_new_for_uri (path);
916           g_free (path);
917           g_free (scheme);
918           return file;
919         }
920     }
921   else
922     {
923       path = _gtk_css_parser_try_name (parser, TRUE);
924       if (path == NULL)
925         {
926           _gtk_css_parser_error (parser, "Not a valid url");
927           return NULL;
928         }
929     }
930
931   file = g_file_resolve_relative_path (base, path);
932   g_free (path);
933
934   return file;
935 }
936
937 void
938 _gtk_css_parser_resync_internal (GtkCssParser *parser,
939                                  gboolean      sync_at_semicolon,
940                                  gboolean      read_sync_token,
941                                  char          terminator)
942 {
943   gsize len;
944
945   do {
946     len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
947     parser->data += len;
948
949     if (gtk_css_parser_new_line (parser))
950       continue;
951
952     if (_gtk_css_parser_is_string (parser))
953       {
954         /* Hrm, this emits errors, and i suspect it shouldn't... */
955         char *free_me = _gtk_css_parser_read_string (parser);
956         g_free (free_me);
957         continue;
958       }
959
960     if (gtk_css_parser_skip_comment (parser))
961       continue;
962
963     switch (*parser->data)
964       {
965       case '\\':
966         {
967           GString *ignore = g_string_new (NULL);
968           _gtk_css_parser_unescape (parser, ignore);
969           g_string_free (ignore, TRUE);
970         }
971         break;
972       case ';':
973         if (sync_at_semicolon && !read_sync_token)
974           return;
975         parser->data++;
976         if (sync_at_semicolon)
977           {
978             _gtk_css_parser_skip_whitespace (parser);
979             return;
980           }
981         break;
982       case '(':
983         parser->data++;
984         _gtk_css_parser_resync (parser, FALSE, ')');
985         if (*parser->data)
986           parser->data++;
987         break;
988       case '[':
989         parser->data++;
990         _gtk_css_parser_resync (parser, FALSE, ']');
991         if (*parser->data)
992           parser->data++;
993         break;
994       case '{':
995         parser->data++;
996         _gtk_css_parser_resync (parser, FALSE, '}');
997         if (*parser->data)
998           parser->data++;
999         if (sync_at_semicolon || !terminator)
1000           {
1001             _gtk_css_parser_skip_whitespace (parser);
1002             return;
1003           }
1004         break;
1005       case '}':
1006       case ')':
1007       case ']':
1008         if (terminator == *parser->data)
1009           {
1010             _gtk_css_parser_skip_whitespace (parser);
1011             return;
1012           }
1013         parser->data++;
1014         continue;
1015       case '\0':
1016         break;
1017       case '/':
1018       default:
1019         parser->data++;
1020         break;
1021       }
1022   } while (*parser->data);
1023 }
1024
1025 char *
1026 _gtk_css_parser_read_value (GtkCssParser *parser)
1027 {
1028   const char *start;
1029   char *result;
1030
1031   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
1032
1033   start = parser->data;
1034
1035   /* This needs to be done better */
1036   _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
1037
1038   result = g_strndup (start, parser->data - start);
1039   if (result)
1040     {
1041       g_strchomp (result);
1042       if (result[0] == 0)
1043         {
1044           g_free (result);
1045           result = NULL;
1046         }
1047     }
1048
1049   if (result == NULL)
1050     _gtk_css_parser_error (parser, "Expected a property value");
1051
1052   return result;
1053 }
1054
1055 void
1056 _gtk_css_parser_resync (GtkCssParser *parser,
1057                         gboolean      sync_at_semicolon,
1058                         char          terminator)
1059 {
1060   g_return_if_fail (GTK_IS_CSS_PARSER (parser));
1061
1062   _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
1063 }