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