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