1/* GSK - 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 "gtkcsstokenizerprivate.h"
21
22#include "gtkcssenums.h"
23#include "gtkcsserror.h"
24#include "gtkcsslocationprivate.h"
25
26#include <math.h>
27#include <string.h>
28
29struct _GtkCssTokenizer
30{
31 int ref_count;
32 GBytes *bytes;
33 GString *name_buffer;
34
35 const char *data;
36 const char *end;
37
38 GtkCssLocation position;
39};
40
41void
42gtk_css_token_clear (GtkCssToken *token)
43{
44 switch (token->type)
45 {
46 case GTK_CSS_TOKEN_STRING:
47 case GTK_CSS_TOKEN_IDENT:
48 case GTK_CSS_TOKEN_FUNCTION:
49 case GTK_CSS_TOKEN_AT_KEYWORD:
50 case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
51 case GTK_CSS_TOKEN_HASH_ID:
52 case GTK_CSS_TOKEN_URL:
53 g_free (mem: token->string.string);
54 break;
55
56 case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
57 case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
58 case GTK_CSS_TOKEN_SIGNED_DIMENSION:
59 case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
60 g_free (mem: token->dimension.dimension);
61 break;
62
63 default:
64 g_assert_not_reached ();
65 case GTK_CSS_TOKEN_EOF:
66 case GTK_CSS_TOKEN_WHITESPACE:
67 case GTK_CSS_TOKEN_OPEN_PARENS:
68 case GTK_CSS_TOKEN_CLOSE_PARENS:
69 case GTK_CSS_TOKEN_OPEN_SQUARE:
70 case GTK_CSS_TOKEN_CLOSE_SQUARE:
71 case GTK_CSS_TOKEN_OPEN_CURLY:
72 case GTK_CSS_TOKEN_CLOSE_CURLY:
73 case GTK_CSS_TOKEN_COMMA:
74 case GTK_CSS_TOKEN_COLON:
75 case GTK_CSS_TOKEN_SEMICOLON:
76 case GTK_CSS_TOKEN_CDC:
77 case GTK_CSS_TOKEN_CDO:
78 case GTK_CSS_TOKEN_DELIM:
79 case GTK_CSS_TOKEN_SIGNED_INTEGER:
80 case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
81 case GTK_CSS_TOKEN_SIGNED_NUMBER:
82 case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
83 case GTK_CSS_TOKEN_PERCENTAGE:
84 case GTK_CSS_TOKEN_INCLUDE_MATCH:
85 case GTK_CSS_TOKEN_DASH_MATCH:
86 case GTK_CSS_TOKEN_PREFIX_MATCH:
87 case GTK_CSS_TOKEN_SUFFIX_MATCH:
88 case GTK_CSS_TOKEN_SUBSTRING_MATCH:
89 case GTK_CSS_TOKEN_COLUMN:
90 case GTK_CSS_TOKEN_BAD_STRING:
91 case GTK_CSS_TOKEN_BAD_URL:
92 case GTK_CSS_TOKEN_COMMENT:
93 break;
94 }
95
96 token->type = GTK_CSS_TOKEN_EOF;
97}
98
99static void
100gtk_css_token_init (GtkCssToken *token,
101 GtkCssTokenType type)
102{
103 token->type = type;
104
105 switch ((guint)type)
106 {
107 case GTK_CSS_TOKEN_EOF:
108 case GTK_CSS_TOKEN_WHITESPACE:
109 case GTK_CSS_TOKEN_OPEN_PARENS:
110 case GTK_CSS_TOKEN_CLOSE_PARENS:
111 case GTK_CSS_TOKEN_OPEN_SQUARE:
112 case GTK_CSS_TOKEN_CLOSE_SQUARE:
113 case GTK_CSS_TOKEN_OPEN_CURLY:
114 case GTK_CSS_TOKEN_CLOSE_CURLY:
115 case GTK_CSS_TOKEN_COMMA:
116 case GTK_CSS_TOKEN_COLON:
117 case GTK_CSS_TOKEN_SEMICOLON:
118 case GTK_CSS_TOKEN_CDC:
119 case GTK_CSS_TOKEN_CDO:
120 case GTK_CSS_TOKEN_INCLUDE_MATCH:
121 case GTK_CSS_TOKEN_DASH_MATCH:
122 case GTK_CSS_TOKEN_PREFIX_MATCH:
123 case GTK_CSS_TOKEN_SUFFIX_MATCH:
124 case GTK_CSS_TOKEN_SUBSTRING_MATCH:
125 case GTK_CSS_TOKEN_COLUMN:
126 case GTK_CSS_TOKEN_BAD_STRING:
127 case GTK_CSS_TOKEN_BAD_URL:
128 case GTK_CSS_TOKEN_COMMENT:
129 break;
130 default:
131 g_assert_not_reached ();
132 }
133}
134
135static void
136append_ident (GString *string,
137 const char *ident)
138{
139 /* XXX */
140 g_string_append (string, val: ident);
141}
142
143static void
144append_string (GString *string,
145 const char *s)
146{
147 g_string_append_c (string, '"');
148 /* XXX */
149 g_string_append (string, val: s);
150 g_string_append_c (string, '"');
151}
152
153/*
154 * gtk_css_token_is_finite:
155 * @token: a `GtkCssToken`
156 *
157 * A token is considered finite when it would stay the same no matter
158 * what bytes follow it in the data stream.
159 *
160 * An obvious example for this is the ';' token.
161 *
162 * Returns: %TRUE if the token is considered finite.
163 **/
164gboolean
165gtk_css_token_is_finite (const GtkCssToken *token)
166{
167 switch (token->type)
168 {
169 case GTK_CSS_TOKEN_EOF:
170 case GTK_CSS_TOKEN_STRING:
171 case GTK_CSS_TOKEN_FUNCTION:
172 case GTK_CSS_TOKEN_URL:
173 case GTK_CSS_TOKEN_PERCENTAGE:
174 case GTK_CSS_TOKEN_OPEN_PARENS:
175 case GTK_CSS_TOKEN_CLOSE_PARENS:
176 case GTK_CSS_TOKEN_OPEN_SQUARE:
177 case GTK_CSS_TOKEN_CLOSE_SQUARE:
178 case GTK_CSS_TOKEN_OPEN_CURLY:
179 case GTK_CSS_TOKEN_CLOSE_CURLY:
180 case GTK_CSS_TOKEN_COMMA:
181 case GTK_CSS_TOKEN_COLON:
182 case GTK_CSS_TOKEN_SEMICOLON:
183 case GTK_CSS_TOKEN_CDC:
184 case GTK_CSS_TOKEN_CDO:
185 case GTK_CSS_TOKEN_INCLUDE_MATCH:
186 case GTK_CSS_TOKEN_DASH_MATCH:
187 case GTK_CSS_TOKEN_PREFIX_MATCH:
188 case GTK_CSS_TOKEN_SUFFIX_MATCH:
189 case GTK_CSS_TOKEN_SUBSTRING_MATCH:
190 case GTK_CSS_TOKEN_COLUMN:
191 case GTK_CSS_TOKEN_COMMENT:
192 return TRUE;
193
194 default:
195 g_assert_not_reached ();
196 case GTK_CSS_TOKEN_WHITESPACE:
197 case GTK_CSS_TOKEN_IDENT:
198 case GTK_CSS_TOKEN_AT_KEYWORD:
199 case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
200 case GTK_CSS_TOKEN_HASH_ID:
201 case GTK_CSS_TOKEN_DELIM:
202 case GTK_CSS_TOKEN_SIGNED_INTEGER:
203 case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
204 case GTK_CSS_TOKEN_SIGNED_NUMBER:
205 case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
206 case GTK_CSS_TOKEN_BAD_STRING:
207 case GTK_CSS_TOKEN_BAD_URL:
208 case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
209 case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
210 case GTK_CSS_TOKEN_SIGNED_DIMENSION:
211 case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
212 return FALSE;
213 }
214}
215
216/*
217 * gtk_css_token_is_preserved:
218 * @token: a `GtkCssToken`
219 * @out_closing: (nullable): Type of the token that closes a block
220 * started with this token
221 *
222 * A token is considered preserved when it does not start a block.
223 *
224 * Tokens that start a block require different error recovery when parsing,
225 * so CSS parsers want to look at this function
226 *
227 * Returns: %TRUE if the token is considered preserved.
228 */
229gboolean
230gtk_css_token_is_preserved (const GtkCssToken *token,
231 GtkCssTokenType *out_closing)
232{
233 switch (token->type)
234 {
235 case GTK_CSS_TOKEN_FUNCTION:
236 case GTK_CSS_TOKEN_OPEN_PARENS:
237 if (out_closing)
238 *out_closing = GTK_CSS_TOKEN_CLOSE_PARENS;
239 return FALSE;
240
241 case GTK_CSS_TOKEN_OPEN_SQUARE:
242 if (out_closing)
243 *out_closing = GTK_CSS_TOKEN_CLOSE_SQUARE;
244 return FALSE;
245
246 case GTK_CSS_TOKEN_OPEN_CURLY:
247 if (out_closing)
248 *out_closing = GTK_CSS_TOKEN_CLOSE_CURLY;
249 return FALSE;
250
251 default:
252 g_assert_not_reached ();
253 case GTK_CSS_TOKEN_EOF:
254 case GTK_CSS_TOKEN_WHITESPACE:
255 case GTK_CSS_TOKEN_STRING:
256 case GTK_CSS_TOKEN_URL:
257 case GTK_CSS_TOKEN_PERCENTAGE:
258 case GTK_CSS_TOKEN_CLOSE_PARENS:
259 case GTK_CSS_TOKEN_CLOSE_SQUARE:
260 case GTK_CSS_TOKEN_CLOSE_CURLY:
261 case GTK_CSS_TOKEN_COMMA:
262 case GTK_CSS_TOKEN_COLON:
263 case GTK_CSS_TOKEN_SEMICOLON:
264 case GTK_CSS_TOKEN_CDC:
265 case GTK_CSS_TOKEN_CDO:
266 case GTK_CSS_TOKEN_INCLUDE_MATCH:
267 case GTK_CSS_TOKEN_DASH_MATCH:
268 case GTK_CSS_TOKEN_PREFIX_MATCH:
269 case GTK_CSS_TOKEN_SUFFIX_MATCH:
270 case GTK_CSS_TOKEN_SUBSTRING_MATCH:
271 case GTK_CSS_TOKEN_COLUMN:
272 case GTK_CSS_TOKEN_COMMENT:
273 case GTK_CSS_TOKEN_IDENT:
274 case GTK_CSS_TOKEN_AT_KEYWORD:
275 case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
276 case GTK_CSS_TOKEN_HASH_ID:
277 case GTK_CSS_TOKEN_DELIM:
278 case GTK_CSS_TOKEN_SIGNED_INTEGER:
279 case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
280 case GTK_CSS_TOKEN_SIGNED_NUMBER:
281 case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
282 case GTK_CSS_TOKEN_BAD_STRING:
283 case GTK_CSS_TOKEN_BAD_URL:
284 case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
285 case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
286 case GTK_CSS_TOKEN_SIGNED_DIMENSION:
287 case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
288 if (out_closing)
289 *out_closing = GTK_CSS_TOKEN_EOF;
290 return TRUE;
291 }
292}
293
294gboolean
295gtk_css_token_is_ident (const GtkCssToken *token,
296 const char *ident)
297{
298 return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)
299 && (g_ascii_strcasecmp (s1: token->string.string, s2: ident) == 0);
300}
301
302gboolean
303gtk_css_token_is_function (const GtkCssToken *token,
304 const char *ident)
305{
306 return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION)
307 && (g_ascii_strcasecmp (s1: token->string.string, s2: ident) == 0);
308}
309
310gboolean
311gtk_css_token_is_delim (const GtkCssToken *token,
312 gunichar delim)
313{
314 return gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM)
315 && token->delim.delim == delim;
316}
317
318void
319gtk_css_token_print (const GtkCssToken *token,
320 GString *string)
321{
322 char buf[G_ASCII_DTOSTR_BUF_SIZE];
323
324 switch (token->type)
325 {
326 case GTK_CSS_TOKEN_STRING:
327 append_string (string, s: token->string.string);
328 break;
329
330 case GTK_CSS_TOKEN_IDENT:
331 append_ident (string, ident: token->string.string);
332 break;
333
334 case GTK_CSS_TOKEN_URL:
335 g_string_append (string, val: "url(");
336 append_ident (string, ident: token->string.string);
337 g_string_append (string, val: ")");
338 break;
339
340 case GTK_CSS_TOKEN_FUNCTION:
341 append_ident (string, ident: token->string.string);
342 g_string_append_c (string, '(');
343 break;
344
345 case GTK_CSS_TOKEN_AT_KEYWORD:
346 g_string_append_c (string, '@');
347 append_ident (string, ident: token->string.string);
348 break;
349
350 case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
351 case GTK_CSS_TOKEN_HASH_ID:
352 g_string_append_c (string, '#');
353 append_ident (string, ident: token->string.string);
354 break;
355
356 case GTK_CSS_TOKEN_DELIM:
357 g_string_append_unichar (string, wc: token->delim.delim);
358 break;
359
360 case GTK_CSS_TOKEN_SIGNED_INTEGER:
361 case GTK_CSS_TOKEN_SIGNED_NUMBER:
362 if (token->number.number >= 0)
363 g_string_append_c (string, '+');
364 G_GNUC_FALLTHROUGH;
365 case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
366 case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
367 g_ascii_dtostr (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, d: token->number.number);
368 g_string_append (string, val: buf);
369 break;
370
371 case GTK_CSS_TOKEN_PERCENTAGE:
372 g_ascii_dtostr (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, d: token->number.number);
373 g_string_append (string, val: buf);
374 g_string_append_c (string, '%');
375 break;
376
377 case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
378 case GTK_CSS_TOKEN_SIGNED_DIMENSION:
379 if (token->dimension.value >= 0)
380 g_string_append_c (string, '+');
381 G_GNUC_FALLTHROUGH;
382 case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
383 case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
384 g_ascii_dtostr (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, d: token->dimension.value);
385 g_string_append (string, val: buf);
386 append_ident (string, ident: token->dimension.dimension);
387 break;
388
389 case GTK_CSS_TOKEN_EOF:
390 break;
391
392 case GTK_CSS_TOKEN_WHITESPACE:
393 g_string_append (string, val: " ");
394 break;
395
396 case GTK_CSS_TOKEN_OPEN_PARENS:
397 g_string_append (string, val: "(");
398 break;
399
400 case GTK_CSS_TOKEN_CLOSE_PARENS:
401 g_string_append (string, val: ")");
402 break;
403
404 case GTK_CSS_TOKEN_OPEN_SQUARE:
405 g_string_append (string, val: "[");
406 break;
407
408 case GTK_CSS_TOKEN_CLOSE_SQUARE:
409 g_string_append (string, val: "]");
410 break;
411
412 case GTK_CSS_TOKEN_OPEN_CURLY:
413 g_string_append (string, val: "{");
414 break;
415
416 case GTK_CSS_TOKEN_CLOSE_CURLY:
417 g_string_append (string, val: "}");
418 break;
419
420 case GTK_CSS_TOKEN_COMMA:
421 g_string_append (string, val: ",");
422 break;
423
424 case GTK_CSS_TOKEN_COLON:
425 g_string_append (string, val: ":");
426 break;
427
428 case GTK_CSS_TOKEN_SEMICOLON:
429 g_string_append (string, val: ";");
430 break;
431
432 case GTK_CSS_TOKEN_CDO:
433 g_string_append (string, val: "<!--");
434 break;
435
436 case GTK_CSS_TOKEN_CDC:
437 g_string_append (string, val: "-->");
438 break;
439
440 case GTK_CSS_TOKEN_INCLUDE_MATCH:
441 g_string_append (string, val: "~=");
442 break;
443
444 case GTK_CSS_TOKEN_DASH_MATCH:
445 g_string_append (string, val: "|=");
446 break;
447
448 case GTK_CSS_TOKEN_PREFIX_MATCH:
449 g_string_append (string, val: "^=");
450 break;
451
452 case GTK_CSS_TOKEN_SUFFIX_MATCH:
453 g_string_append (string, val: "$=");
454 break;
455
456 case GTK_CSS_TOKEN_SUBSTRING_MATCH:
457 g_string_append (string, val: "*=");
458 break;
459
460 case GTK_CSS_TOKEN_COLUMN:
461 g_string_append (string, val: "||");
462 break;
463
464 case GTK_CSS_TOKEN_BAD_STRING:
465 g_string_append (string, val: "\"\n");
466 break;
467
468 case GTK_CSS_TOKEN_BAD_URL:
469 g_string_append (string, val: "url(bad url)");
470 break;
471
472 case GTK_CSS_TOKEN_COMMENT:
473 g_string_append (string, val: "/* comment */");
474 break;
475
476 default:
477 g_assert_not_reached ();
478 break;
479 }
480}
481
482char *
483gtk_css_token_to_string (const GtkCssToken *token)
484{
485 GString *string;
486
487 string = g_string_new (NULL);
488 gtk_css_token_print (token, string);
489 return g_string_free (string, FALSE);
490}
491
492static void
493gtk_css_token_init_string (GtkCssToken *token,
494 GtkCssTokenType type,
495 char *string)
496{
497 token->type = type;
498
499 switch ((guint)type)
500 {
501 case GTK_CSS_TOKEN_STRING:
502 case GTK_CSS_TOKEN_IDENT:
503 case GTK_CSS_TOKEN_FUNCTION:
504 case GTK_CSS_TOKEN_AT_KEYWORD:
505 case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
506 case GTK_CSS_TOKEN_HASH_ID:
507 case GTK_CSS_TOKEN_URL:
508 token->string.string = string;
509 break;
510 default:
511 g_assert_not_reached ();
512 }
513}
514
515static void
516gtk_css_token_init_delim (GtkCssToken *token,
517 gunichar delim)
518{
519 token->type = GTK_CSS_TOKEN_DELIM;
520 token->delim.delim = delim;
521}
522
523static void
524gtk_css_token_init_number (GtkCssToken *token,
525 GtkCssTokenType type,
526 double value)
527{
528 token->type = type;
529
530 switch ((guint)type)
531 {
532 case GTK_CSS_TOKEN_SIGNED_INTEGER:
533 case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
534 case GTK_CSS_TOKEN_SIGNED_NUMBER:
535 case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
536 case GTK_CSS_TOKEN_PERCENTAGE:
537 token->number.number = value;
538 break;
539 default:
540 g_assert_not_reached ();
541 }
542}
543
544static void
545gtk_css_token_init_dimension (GtkCssToken *token,
546 GtkCssTokenType type,
547 double value,
548 char *dimension)
549{
550 token->type = type;
551
552 switch ((guint)type)
553 {
554 case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
555 case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
556 case GTK_CSS_TOKEN_SIGNED_DIMENSION:
557 case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
558 token->dimension.value = value;
559 token->dimension.dimension = dimension;
560 break;
561 default:
562 g_assert_not_reached ();
563 }
564}
565
566GtkCssTokenizer *
567gtk_css_tokenizer_new (GBytes *bytes)
568{
569 GtkCssTokenizer *tokenizer;
570
571 tokenizer = g_slice_new0 (GtkCssTokenizer);
572 tokenizer->ref_count = 1;
573 tokenizer->bytes = g_bytes_ref (bytes);
574 tokenizer->name_buffer = g_string_new (NULL);
575
576 tokenizer->data = g_bytes_get_data (bytes, NULL);
577 tokenizer->end = tokenizer->data + g_bytes_get_size (bytes);
578
579 gtk_css_location_init (location: &tokenizer->position);
580
581 return tokenizer;
582}
583
584GtkCssTokenizer *
585gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer)
586{
587 tokenizer->ref_count++;
588
589 return tokenizer;
590}
591
592void
593gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer)
594{
595 tokenizer->ref_count--;
596 if (tokenizer->ref_count > 0)
597 return;
598
599 g_string_free (string: tokenizer->name_buffer, TRUE);
600 g_bytes_unref (bytes: tokenizer->bytes);
601 g_slice_free (GtkCssTokenizer, tokenizer);
602}
603
604const GtkCssLocation *
605gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer)
606{
607 return &tokenizer->position;
608}
609
610static void G_GNUC_PRINTF(2, 3)
611gtk_css_tokenizer_parse_error (GError **error,
612 const char *format,
613 ...)
614{
615 va_list args;
616
617 va_start (args, format);
618 if (error)
619 {
620 *error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
621 code: GTK_CSS_PARSER_ERROR_SYNTAX,
622 format, args);
623 }
624 else
625 {
626 char *s = g_strdup_vprintf (format, args);
627 g_print (format: "error: %s\n", s);
628 g_free (mem: s);
629 }
630 va_end (args);
631}
632
633static gboolean
634is_newline (char c)
635{
636 return c == '\n'
637 || c == '\r'
638 || c == '\f';
639}
640
641static gboolean
642is_whitespace (char c)
643{
644 return is_newline (c)
645 || c == '\t'
646 || c == ' ';
647}
648
649static gboolean
650is_multibyte (char c)
651{
652 return c & 0x80;
653}
654
655static gboolean
656is_name_start (char c)
657{
658 return is_multibyte (c)
659 || g_ascii_isalpha (c)
660 || c == '_';
661}
662
663static gboolean
664is_name (char c)
665{
666 return is_name_start (c)
667 || g_ascii_isdigit (c)
668 || c == '-';
669}
670
671static gboolean
672is_non_printable (char c)
673{
674 return (c >= 0 && c <= 0x08)
675 || c == 0x0B
676 || c == 0x0E
677 || c == 0x1F
678 || c == 0x7F;
679}
680
681static gboolean
682is_valid_escape (const char *data,
683 const char *end)
684{
685 switch (end - data)
686 {
687 default:
688 if (is_newline (c: data[1]))
689 return FALSE;
690 G_GNUC_FALLTHROUGH;
691
692 case 1:
693 return data[0] == '\\';
694
695 case 0:
696 return FALSE;
697 }
698}
699
700static inline gsize
701gtk_css_tokenizer_remaining (GtkCssTokenizer *tokenizer)
702{
703 return tokenizer->end - tokenizer->data;
704}
705
706static gboolean
707gtk_css_tokenizer_has_valid_escape (GtkCssTokenizer *tokenizer)
708{
709 return is_valid_escape (data: tokenizer->data, end: tokenizer->end);
710}
711
712static gboolean
713gtk_css_tokenizer_has_identifier (GtkCssTokenizer *tokenizer)
714{
715 const char *data = tokenizer->data;
716
717 if (data == tokenizer->end)
718 return FALSE;
719
720 if (*data == '-')
721 {
722 data++;
723 if (data == tokenizer->end)
724 return FALSE;
725 if (*data == '-')
726 return TRUE;
727 }
728
729 if (is_name_start (c: *data))
730 return TRUE;
731
732 if (*data == '\\')
733 {
734 data++;
735 if (data == tokenizer->end)
736 return TRUE; /* really? */
737 if (is_newline (c: *data))
738 return FALSE;
739 return TRUE;
740 }
741
742 return FALSE;
743}
744
745static gboolean
746gtk_css_tokenizer_has_number (GtkCssTokenizer *tokenizer)
747{
748 const char *data = tokenizer->data;
749
750 if (data == tokenizer->end)
751 return FALSE;
752
753 if (*data == '-' || *data == '+')
754 {
755 data++;
756 if (data == tokenizer->end)
757 return FALSE;
758 }
759
760 if (*data == '.')
761 {
762 data++;
763 if (data == tokenizer->end)
764 return FALSE;
765 }
766
767 return g_ascii_isdigit (*data);
768}
769
770static void
771gtk_css_tokenizer_consume_newline (GtkCssTokenizer *tokenizer)
772{
773 gsize n;
774
775 if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
776 tokenizer->data[0] == '\r' && tokenizer->data[1] == '\n')
777 n = 2;
778 else
779 n = 1;
780
781 tokenizer->data += n;
782 gtk_css_location_advance_newline (location: &tokenizer->position, is_windows: n == 2 ? TRUE : FALSE);
783}
784
785static inline void
786gtk_css_tokenizer_consume (GtkCssTokenizer *tokenizer,
787 gsize n_bytes,
788 gsize n_characters)
789{
790 /* NB: must not contain newlines! */
791 tokenizer->data += n_bytes;
792
793 gtk_css_location_advance (location: &tokenizer->position, bytes: n_bytes, chars: n_characters);
794}
795
796static inline void
797gtk_css_tokenizer_consume_ascii (GtkCssTokenizer *tokenizer)
798{
799 /* NB: must not contain newlines! */
800 gtk_css_tokenizer_consume (tokenizer, n_bytes: 1, n_characters: 1);
801}
802
803static inline void
804gtk_css_tokenizer_consume_whitespace (GtkCssTokenizer *tokenizer)
805{
806 if (is_newline (c: *tokenizer->data))
807 gtk_css_tokenizer_consume_newline (tokenizer);
808 else
809 gtk_css_tokenizer_consume_ascii (tokenizer);
810}
811
812static inline void
813gtk_css_tokenizer_consume_char (GtkCssTokenizer *tokenizer,
814 GString *string)
815{
816 if (is_newline (c: *tokenizer->data))
817 gtk_css_tokenizer_consume_newline (tokenizer);
818 else
819 {
820 gsize char_size = g_utf8_next_char (tokenizer->data) - tokenizer->data;
821
822 if (string)
823 g_string_append_len (string, val: tokenizer->data, len: char_size);
824 gtk_css_tokenizer_consume (tokenizer, n_bytes: char_size, n_characters: 1);
825 }
826}
827
828static void
829gtk_css_tokenizer_read_whitespace (GtkCssTokenizer *tokenizer,
830 GtkCssToken *token)
831{
832 do {
833 gtk_css_tokenizer_consume_whitespace (tokenizer);
834 } while (tokenizer->data != tokenizer->end &&
835 is_whitespace (c: *tokenizer->data));
836
837 gtk_css_token_init (token, type: GTK_CSS_TOKEN_WHITESPACE);
838}
839
840static gunichar
841gtk_css_tokenizer_read_escape (GtkCssTokenizer *tokenizer)
842{
843 gunichar value = 0;
844 guint i;
845
846 gtk_css_tokenizer_consume (tokenizer, n_bytes: 1, n_characters: 1);
847
848 for (i = 0; i < 6 && tokenizer->data < tokenizer->end && g_ascii_isxdigit (*tokenizer->data); i++)
849 {
850 value = value * 16 + g_ascii_xdigit_value (c: *tokenizer->data);
851 gtk_css_tokenizer_consume (tokenizer, n_bytes: 1, n_characters: 1);
852 }
853
854 if (i == 0)
855 {
856 gsize remaining = gtk_css_tokenizer_remaining (tokenizer);
857 if (remaining == 0)
858 return 0xFFFD;
859
860 value = g_utf8_get_char_validated (p: tokenizer->data, max_len: remaining);
861 if (value == (gunichar) -1 || value == (gunichar) -2)
862 value = 0;
863
864 gtk_css_tokenizer_consume_char (tokenizer, NULL);
865 }
866 else
867 {
868 if (is_whitespace (c: *tokenizer->data))
869 gtk_css_tokenizer_consume_ascii (tokenizer);
870 }
871
872 if (!g_unichar_validate (ch: value) || g_unichar_type (c: value) == G_UNICODE_SURROGATE)
873 return 0xFFFD;
874
875 return value;
876}
877
878static char *
879gtk_css_tokenizer_read_name (GtkCssTokenizer *tokenizer)
880{
881 g_string_set_size (string: tokenizer->name_buffer, len: 0);
882
883 do {
884 if (*tokenizer->data == '\\')
885 {
886 if (gtk_css_tokenizer_has_valid_escape (tokenizer))
887 {
888 gunichar value = gtk_css_tokenizer_read_escape (tokenizer);
889 g_string_append_unichar (string: tokenizer->name_buffer, wc: value);
890 }
891 else
892 {
893 gtk_css_tokenizer_consume_ascii (tokenizer);
894
895 if (tokenizer->data == tokenizer->end)
896 {
897 g_string_append_unichar (string: tokenizer->name_buffer, wc: 0xFFFD);
898 break;
899 }
900
901 gtk_css_tokenizer_consume_char (tokenizer, string: tokenizer->name_buffer);
902 }
903 }
904 else if (is_name (c: *tokenizer->data))
905 {
906 gtk_css_tokenizer_consume_char (tokenizer, string: tokenizer->name_buffer);
907 }
908 else
909 {
910 break;
911 }
912 }
913 while (tokenizer->data != tokenizer->end);
914
915 return g_strndup (str: tokenizer->name_buffer->str, n: tokenizer->name_buffer->len);
916}
917
918static void
919gtk_css_tokenizer_read_bad_url (GtkCssTokenizer *tokenizer,
920 GtkCssToken *token)
921{
922 while (tokenizer->data < tokenizer->end && *tokenizer->data != ')')
923 {
924 if (gtk_css_tokenizer_has_valid_escape (tokenizer))
925 gtk_css_tokenizer_read_escape (tokenizer);
926 else
927 gtk_css_tokenizer_consume_char (tokenizer, NULL);
928 }
929
930 if (tokenizer->data < tokenizer->end)
931 gtk_css_tokenizer_consume_ascii (tokenizer);
932
933 gtk_css_token_init (token, type: GTK_CSS_TOKEN_BAD_URL);
934}
935
936static gboolean
937gtk_css_tokenizer_read_url (GtkCssTokenizer *tokenizer,
938 GtkCssToken *token,
939 GError **error)
940{
941 GString *url = g_string_new (NULL);
942
943 while (tokenizer->data < tokenizer->end && is_whitespace (c: *tokenizer->data))
944 gtk_css_tokenizer_consume_whitespace (tokenizer);
945
946 while (tokenizer->data < tokenizer->end)
947 {
948 if (*tokenizer->data == ')')
949 {
950 gtk_css_tokenizer_consume_ascii (tokenizer);
951 break;
952 }
953 else if (is_whitespace (c: *tokenizer->data))
954 {
955 do
956 gtk_css_tokenizer_consume_whitespace (tokenizer);
957 while (tokenizer->data < tokenizer->end && is_whitespace (c: *tokenizer->data));
958
959 if (*tokenizer->data == ')')
960 {
961 gtk_css_tokenizer_consume_ascii (tokenizer);
962 break;
963 }
964 else if (tokenizer->data >= tokenizer->end)
965 {
966 break;
967 }
968 else
969 {
970 gtk_css_tokenizer_read_bad_url (tokenizer, token);
971 gtk_css_tokenizer_parse_error (error, format: "Whitespace only allowed at start and end of url");
972 return FALSE;
973 }
974 }
975 else if (is_non_printable (c: *tokenizer->data))
976 {
977 gtk_css_tokenizer_read_bad_url (tokenizer, token);
978 g_string_free (string: url, TRUE);
979 gtk_css_tokenizer_parse_error (error, format: "Nonprintable character 0x%02X in url", *tokenizer->data);
980 return FALSE;
981 }
982 else if (*tokenizer->data == '"' ||
983 *tokenizer->data == '\'' ||
984 *tokenizer->data == '(')
985 {
986 gtk_css_tokenizer_read_bad_url (tokenizer, token);
987 gtk_css_tokenizer_parse_error (error, format: "Invalid character %c in url", *tokenizer->data);
988 g_string_free (string: url, TRUE);
989 return FALSE;
990 }
991 else if (gtk_css_tokenizer_has_valid_escape (tokenizer))
992 {
993 g_string_append_unichar (string: url, wc: gtk_css_tokenizer_read_escape (tokenizer));
994 }
995 else if (*tokenizer->data == '\\')
996 {
997 gtk_css_tokenizer_read_bad_url (tokenizer, token);
998 gtk_css_tokenizer_parse_error (error, format: "Newline may not follow '\' escape character");
999 g_string_free (string: url, TRUE);
1000 return FALSE;
1001 }
1002 else
1003 {
1004 gtk_css_tokenizer_consume_char (tokenizer, string: url);
1005 }
1006 }
1007
1008 gtk_css_token_init_string (token, type: GTK_CSS_TOKEN_URL, string: g_string_free (string: url, FALSE));
1009
1010 return TRUE;
1011}
1012
1013static gboolean
1014gtk_css_tokenizer_read_ident_like (GtkCssTokenizer *tokenizer,
1015 GtkCssToken *token,
1016 GError **error)
1017{
1018 char *name = gtk_css_tokenizer_read_name (tokenizer);
1019
1020 if (*tokenizer->data == '(')
1021 {
1022 gtk_css_tokenizer_consume_ascii (tokenizer);
1023 if (g_ascii_strcasecmp (s1: name, s2: "url") == 0)
1024 {
1025 const char *data = tokenizer->data;
1026
1027 while (is_whitespace (c: *data))
1028 data++;
1029
1030 if (*data != '"' && *data != '\'')
1031 {
1032 g_free (mem: name);
1033 return gtk_css_tokenizer_read_url (tokenizer, token, error);
1034 }
1035 }
1036
1037 gtk_css_token_init_string (token, type: GTK_CSS_TOKEN_FUNCTION, string: name);
1038 return TRUE;
1039 }
1040 else
1041 {
1042 gtk_css_token_init_string (token, type: GTK_CSS_TOKEN_IDENT, string: name);
1043 return TRUE;
1044 }
1045}
1046
1047static void
1048gtk_css_tokenizer_read_numeric (GtkCssTokenizer *tokenizer,
1049 GtkCssToken *token)
1050{
1051 int sign = 1, exponent_sign = 1;
1052 gint64 integer, fractional = 0, fractional_length = 1, exponent = 0;
1053 gboolean is_int = TRUE, has_sign = FALSE;
1054 const char *data = tokenizer->data;
1055 double value;
1056
1057 if (*data == '-')
1058 {
1059 has_sign = TRUE;
1060 sign = -1;
1061 data++;
1062 }
1063 else if (*data == '+')
1064 {
1065 has_sign = TRUE;
1066 data++;
1067 }
1068
1069 for (integer = 0; data < tokenizer->end && g_ascii_isdigit (*data); data++)
1070 {
1071 /* check for overflow here? */
1072 integer = 10 * integer + g_ascii_digit_value (c: *data);
1073 }
1074
1075 if (data + 1 < tokenizer->end && *data == '.' && g_ascii_isdigit (data[1]))
1076 {
1077 is_int = FALSE;
1078 data++;
1079
1080 fractional = g_ascii_digit_value (c: *data);
1081 fractional_length = 10;
1082 data++;
1083
1084 while (data < tokenizer->end && g_ascii_isdigit (*data))
1085 {
1086 if (fractional_length < G_MAXINT64 / 10)
1087 {
1088 fractional = 10 * fractional + g_ascii_digit_value (c: *data);
1089 fractional_length *= 10;
1090 }
1091 data++;
1092 }
1093 }
1094
1095 if (data + 1 < tokenizer->end && (*data == 'e' || *data == 'E') &&
1096 (g_ascii_isdigit (data[1]) ||
1097 (data + 2 < tokenizer->end && (data[1] == '+' || data[1] == '-') && g_ascii_isdigit (data[2]))))
1098 {
1099 is_int = FALSE;
1100 data++;
1101
1102 if (*data == '-')
1103 {
1104 exponent_sign = -1;
1105 data++;
1106 }
1107 else if (*data == '+')
1108 {
1109 data++;
1110 }
1111
1112 while (data < tokenizer->end && g_ascii_isdigit (*data))
1113 {
1114 exponent = 10 * exponent + g_ascii_digit_value (c: *data);
1115 data++;
1116 }
1117 }
1118
1119 gtk_css_tokenizer_consume (tokenizer, n_bytes: data - tokenizer->data, n_characters: data - tokenizer->data);
1120
1121 value = sign * (integer + ((double) fractional / fractional_length)) * pow (x: 10, y: exponent_sign * exponent);
1122
1123 if (gtk_css_tokenizer_has_identifier (tokenizer))
1124 {
1125 GtkCssTokenType type;
1126
1127 if (is_int)
1128 type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION;
1129 else
1130 type = has_sign ? GTK_CSS_TOKEN_SIGNED_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_DIMENSION;
1131
1132 gtk_css_token_init_dimension (token, type, value, dimension: gtk_css_tokenizer_read_name (tokenizer));
1133 }
1134 else if (gtk_css_tokenizer_remaining (tokenizer) > 0 && *tokenizer->data == '%')
1135 {
1136 gtk_css_token_init_number (token, type: GTK_CSS_TOKEN_PERCENTAGE, value);
1137 gtk_css_tokenizer_consume_ascii (tokenizer);
1138 }
1139 else
1140 {
1141 GtkCssTokenType type;
1142
1143 if (is_int)
1144 type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER : GTK_CSS_TOKEN_SIGNLESS_INTEGER;
1145 else
1146 type = has_sign ? GTK_CSS_TOKEN_SIGNED_NUMBER : GTK_CSS_TOKEN_SIGNLESS_NUMBER;
1147
1148 gtk_css_token_init_number (token, type,value);
1149 }
1150}
1151
1152static void
1153gtk_css_tokenizer_read_delim (GtkCssTokenizer *tokenizer,
1154 GtkCssToken *token)
1155{
1156 gtk_css_token_init_delim (token, delim: g_utf8_get_char (p: tokenizer->data));
1157 gtk_css_tokenizer_consume_char (tokenizer, NULL);
1158}
1159
1160static gboolean
1161gtk_css_tokenizer_read_dash (GtkCssTokenizer *tokenizer,
1162 GtkCssToken *token,
1163 GError **error)
1164{
1165 if (gtk_css_tokenizer_remaining (tokenizer) == 1)
1166 {
1167 gtk_css_tokenizer_read_delim (tokenizer, token);
1168 return TRUE;
1169 }
1170 else if (gtk_css_tokenizer_has_number (tokenizer))
1171 {
1172 gtk_css_tokenizer_read_numeric (tokenizer, token);
1173 return TRUE;
1174 }
1175 else if (gtk_css_tokenizer_remaining (tokenizer) >= 3 &&
1176 tokenizer->data[1] == '-' &&
1177 tokenizer->data[2] == '>')
1178 {
1179 gtk_css_token_init (token, type: GTK_CSS_TOKEN_CDC);
1180 gtk_css_tokenizer_consume (tokenizer, n_bytes: 3, n_characters: 3);
1181 return TRUE;
1182 }
1183 else if (gtk_css_tokenizer_has_identifier (tokenizer))
1184 {
1185 return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
1186 }
1187 else
1188 {
1189 gtk_css_tokenizer_read_delim (tokenizer, token);
1190 return TRUE;
1191 }
1192}
1193
1194static gboolean
1195gtk_css_tokenizer_read_string (GtkCssTokenizer *tokenizer,
1196 GtkCssToken *token,
1197 GError **error)
1198{
1199 GString *string = g_string_new (NULL);
1200 char end = *tokenizer->data;
1201
1202 gtk_css_tokenizer_consume_ascii (tokenizer);
1203
1204 while (tokenizer->data < tokenizer->end)
1205 {
1206 if (*tokenizer->data == end)
1207 {
1208 gtk_css_tokenizer_consume_ascii (tokenizer);
1209 break;
1210 }
1211 else if (*tokenizer->data == '\\')
1212 {
1213 if (gtk_css_tokenizer_remaining (tokenizer) == 1)
1214 {
1215 gtk_css_tokenizer_consume_ascii (tokenizer);
1216 break;
1217 }
1218 else if (is_newline (c: tokenizer->data[1]))
1219 {
1220 gtk_css_tokenizer_consume_ascii (tokenizer);
1221 gtk_css_tokenizer_consume_newline (tokenizer);
1222 }
1223 else
1224 {
1225 g_string_append_unichar (string, wc: gtk_css_tokenizer_read_escape (tokenizer));
1226 }
1227 }
1228 else if (is_newline (c: *tokenizer->data))
1229 {
1230 g_string_free (string, TRUE);
1231 gtk_css_token_init (token, type: GTK_CSS_TOKEN_BAD_STRING);
1232 gtk_css_tokenizer_parse_error (error, format: "Newlines inside strings must be escaped");
1233 return FALSE;
1234 }
1235 else
1236 {
1237 gtk_css_tokenizer_consume_char (tokenizer, string);
1238 }
1239 }
1240
1241 gtk_css_token_init_string (token, type: GTK_CSS_TOKEN_STRING, string: g_string_free (string, FALSE));
1242
1243 return TRUE;
1244}
1245
1246static gboolean
1247gtk_css_tokenizer_read_comment (GtkCssTokenizer *tokenizer,
1248 GtkCssToken *token,
1249 GError **error)
1250{
1251 gtk_css_tokenizer_consume (tokenizer, n_bytes: 2, n_characters: 2);
1252
1253 while (tokenizer->data < tokenizer->end)
1254 {
1255 if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
1256 tokenizer->data[0] == '*' && tokenizer->data[1] == '/')
1257 {
1258 gtk_css_tokenizer_consume (tokenizer, n_bytes: 2, n_characters: 2);
1259 gtk_css_token_init (token, type: GTK_CSS_TOKEN_COMMENT);
1260 return TRUE;
1261 }
1262 gtk_css_tokenizer_consume_char (tokenizer, NULL);
1263 }
1264
1265 gtk_css_token_init (token, type: GTK_CSS_TOKEN_COMMENT);
1266 gtk_css_tokenizer_parse_error (error, format: "Comment not terminated at end of document.");
1267 return FALSE;
1268}
1269
1270static void
1271gtk_css_tokenizer_read_match (GtkCssTokenizer *tokenizer,
1272 GtkCssToken *token,
1273 GtkCssTokenType type)
1274{
1275 if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '=')
1276 {
1277 gtk_css_token_init (token, type);
1278 gtk_css_tokenizer_consume (tokenizer, n_bytes: 2, n_characters: 2);
1279 }
1280 else
1281 {
1282 gtk_css_tokenizer_read_delim (tokenizer, token);
1283 }
1284}
1285
1286gboolean
1287gtk_css_tokenizer_read_token (GtkCssTokenizer *tokenizer,
1288 GtkCssToken *token,
1289 GError **error)
1290{
1291 if (tokenizer->data == tokenizer->end)
1292 {
1293 gtk_css_token_init (token, type: GTK_CSS_TOKEN_EOF);
1294 return TRUE;
1295 }
1296
1297 if (tokenizer->data[0] == '/' && gtk_css_tokenizer_remaining (tokenizer) > 1 &&
1298 tokenizer->data[1] == '*')
1299 return gtk_css_tokenizer_read_comment (tokenizer, token, error);
1300
1301 switch (*tokenizer->data)
1302 {
1303 case '\n':
1304 case '\r':
1305 case '\t':
1306 case '\f':
1307 case ' ':
1308 gtk_css_tokenizer_read_whitespace (tokenizer, token);
1309 return TRUE;
1310
1311 case '"':
1312 return gtk_css_tokenizer_read_string (tokenizer, token, error);
1313
1314 case '#':
1315 gtk_css_tokenizer_consume_ascii (tokenizer);
1316 if (is_name (c: *tokenizer->data) || gtk_css_tokenizer_has_valid_escape (tokenizer))
1317 {
1318 GtkCssTokenType type;
1319
1320 if (gtk_css_tokenizer_has_identifier (tokenizer))
1321 type = GTK_CSS_TOKEN_HASH_ID;
1322 else
1323 type = GTK_CSS_TOKEN_HASH_UNRESTRICTED;
1324
1325 gtk_css_token_init_string (token,
1326 type,
1327 string: gtk_css_tokenizer_read_name (tokenizer));
1328 }
1329 else
1330 {
1331 gtk_css_token_init_delim (token, delim: '#');
1332 }
1333 return TRUE;
1334
1335 case '$':
1336 gtk_css_tokenizer_read_match (tokenizer, token, type: GTK_CSS_TOKEN_SUFFIX_MATCH);
1337 return TRUE;
1338
1339 case '\'':
1340 return gtk_css_tokenizer_read_string (tokenizer, token, error);
1341
1342 case '(':
1343 gtk_css_token_init (token, type: GTK_CSS_TOKEN_OPEN_PARENS);
1344 gtk_css_tokenizer_consume_ascii (tokenizer);
1345 return TRUE;
1346
1347 case ')':
1348 gtk_css_token_init (token, type: GTK_CSS_TOKEN_CLOSE_PARENS);
1349 gtk_css_tokenizer_consume_ascii (tokenizer);
1350 return TRUE;
1351
1352 case '*':
1353 gtk_css_tokenizer_read_match (tokenizer, token, type: GTK_CSS_TOKEN_SUBSTRING_MATCH);
1354 return TRUE;
1355
1356 case '+':
1357 if (gtk_css_tokenizer_has_number (tokenizer))
1358 gtk_css_tokenizer_read_numeric (tokenizer, token);
1359 else
1360 gtk_css_tokenizer_read_delim (tokenizer, token);
1361 return TRUE;
1362
1363 case ',':
1364 gtk_css_token_init (token, type: GTK_CSS_TOKEN_COMMA);
1365 gtk_css_tokenizer_consume_ascii (tokenizer);
1366 return TRUE;
1367
1368 case '-':
1369 return gtk_css_tokenizer_read_dash (tokenizer, token, error);
1370
1371 case '.':
1372 if (gtk_css_tokenizer_has_number (tokenizer))
1373 gtk_css_tokenizer_read_numeric (tokenizer, token);
1374 else
1375 gtk_css_tokenizer_read_delim (tokenizer, token);
1376 return TRUE;
1377
1378 case ':':
1379 gtk_css_token_init (token, type: GTK_CSS_TOKEN_COLON);
1380 gtk_css_tokenizer_consume_ascii (tokenizer);
1381 return TRUE;
1382
1383 case ';':
1384 gtk_css_token_init (token, type: GTK_CSS_TOKEN_SEMICOLON);
1385 gtk_css_tokenizer_consume_ascii (tokenizer);
1386 return TRUE;
1387
1388 case '<':
1389 if (gtk_css_tokenizer_remaining (tokenizer) >= 4 &&
1390 tokenizer->data[1] == '!' &&
1391 tokenizer->data[2] == '-' &&
1392 tokenizer->data[3] == '-')
1393 {
1394 gtk_css_token_init (token, type: GTK_CSS_TOKEN_CDO);
1395 gtk_css_tokenizer_consume (tokenizer, n_bytes: 4, n_characters: 4);
1396 }
1397 else
1398 {
1399 gtk_css_tokenizer_read_delim (tokenizer, token);
1400 }
1401 return TRUE;
1402
1403 case '@':
1404 gtk_css_tokenizer_consume_ascii (tokenizer);
1405 if (gtk_css_tokenizer_has_identifier (tokenizer))
1406 {
1407 gtk_css_token_init_string (token,
1408 type: GTK_CSS_TOKEN_AT_KEYWORD,
1409 string: gtk_css_tokenizer_read_name (tokenizer));
1410 }
1411 else
1412 {
1413 gtk_css_token_init_delim (token, delim: '@');
1414 }
1415 return TRUE;
1416
1417 case '[':
1418 gtk_css_token_init (token, type: GTK_CSS_TOKEN_OPEN_SQUARE);
1419 gtk_css_tokenizer_consume_ascii (tokenizer);
1420 return TRUE;
1421
1422 case '\\':
1423 if (gtk_css_tokenizer_has_valid_escape (tokenizer))
1424 {
1425 return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
1426 }
1427 else
1428 {
1429 gtk_css_token_init_delim (token, delim: '\\');
1430 gtk_css_tokenizer_consume_ascii (tokenizer);
1431 gtk_css_tokenizer_parse_error (error, format: "Newline may not follow '\' escape character");
1432 return FALSE;
1433 }
1434
1435 case ']':
1436 gtk_css_token_init (token, type: GTK_CSS_TOKEN_CLOSE_SQUARE);
1437 gtk_css_tokenizer_consume_ascii (tokenizer);
1438 return TRUE;
1439
1440 case '^':
1441 gtk_css_tokenizer_read_match (tokenizer, token, type: GTK_CSS_TOKEN_PREFIX_MATCH);
1442 return TRUE;
1443
1444 case '{':
1445 gtk_css_token_init (token, type: GTK_CSS_TOKEN_OPEN_CURLY);
1446 gtk_css_tokenizer_consume_ascii (tokenizer);
1447 return TRUE;
1448
1449 case '}':
1450 gtk_css_token_init (token, type: GTK_CSS_TOKEN_CLOSE_CURLY);
1451 gtk_css_tokenizer_consume_ascii (tokenizer);
1452 return TRUE;
1453
1454 case '|':
1455 if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '|')
1456 {
1457 gtk_css_token_init (token, type: GTK_CSS_TOKEN_COLUMN);
1458 gtk_css_tokenizer_consume (tokenizer, n_bytes: 2, n_characters: 2);
1459 }
1460 else
1461 {
1462 gtk_css_tokenizer_read_match (tokenizer, token, type: GTK_CSS_TOKEN_DASH_MATCH);
1463 }
1464 return TRUE;
1465
1466 case '~':
1467 gtk_css_tokenizer_read_match (tokenizer, token, type: GTK_CSS_TOKEN_INCLUDE_MATCH);
1468 return TRUE;
1469
1470 default:
1471 if (g_ascii_isdigit (*tokenizer->data))
1472 {
1473 gtk_css_tokenizer_read_numeric (tokenizer, token);
1474 return TRUE;
1475 }
1476 else if (is_name_start (c: *tokenizer->data))
1477 {
1478 return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
1479 }
1480 else
1481 {
1482 gtk_css_tokenizer_read_delim (tokenizer, token);
1483 return TRUE;
1484 }
1485 }
1486}
1487
1488

source code of gtk/gtk/css/gtkcsstokenizer.c