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