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