1/*
2 * Copyright © 2019 Benjamin Otte
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.1 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 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20
21#include "config.h"
22
23#include "gtkcssparserprivate.h"
24
25#include "gtkcssenums.h"
26#include "gtkcsserror.h"
27#include "gtkcsslocationprivate.h"
28
29typedef struct _GtkCssParserBlock GtkCssParserBlock;
30
31struct _GtkCssParser
32{
33 volatile int ref_count;
34
35 GtkCssTokenizer *tokenizer;
36 GFile *file;
37 GFile *directory;
38 GtkCssParserErrorFunc error_func;
39 gpointer user_data;
40 GDestroyNotify user_destroy;
41
42 GArray *blocks;
43 GtkCssLocation location;
44 GtkCssToken token;
45};
46
47struct _GtkCssParserBlock
48{
49 GtkCssLocation start_location;
50 GtkCssTokenType end_token;
51 GtkCssTokenType inherited_end_token;
52 GtkCssTokenType alternative_token;
53};
54
55static GtkCssParser *
56gtk_css_parser_new (GtkCssTokenizer *tokenizer,
57 GFile *file,
58 GtkCssParserErrorFunc error_func,
59 gpointer user_data,
60 GDestroyNotify user_destroy)
61{
62 GtkCssParser *self;
63
64 self = g_slice_new0 (GtkCssParser);
65
66 self->ref_count = 1;
67 self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
68 if (file)
69 {
70 self->file = g_object_ref (file);
71 self->directory = g_file_get_parent (file);
72 }
73
74 self->error_func = error_func;
75 self->user_data = user_data;
76 self->user_destroy = user_destroy;
77 self->blocks = g_array_new (FALSE, FALSE, element_size: sizeof (GtkCssParserBlock));
78
79 return self;
80}
81
82GtkCssParser *
83gtk_css_parser_new_for_file (GFile *file,
84 GtkCssParserErrorFunc error_func,
85 gpointer user_data,
86 GDestroyNotify user_destroy,
87 GError **error)
88{
89 GBytes *bytes;
90 GtkCssParser *result;
91
92 bytes = g_file_load_bytes (file, NULL, NULL, error);
93 if (bytes == NULL)
94 return NULL;
95
96 result = gtk_css_parser_new_for_bytes (bytes, file, error_func, user_data, user_destroy);
97
98 g_bytes_unref (bytes);
99
100 return result;
101}
102
103GtkCssParser *
104gtk_css_parser_new_for_bytes (GBytes *bytes,
105 GFile *file,
106 GtkCssParserErrorFunc error_func,
107 gpointer user_data,
108 GDestroyNotify user_destroy)
109{
110 GtkCssTokenizer *tokenizer;
111 GtkCssParser *result;
112
113 tokenizer = gtk_css_tokenizer_new (bytes);
114 result = gtk_css_parser_new (tokenizer, file, error_func, user_data, user_destroy);
115 gtk_css_tokenizer_unref (tokenizer);
116
117 return result;
118}
119
120static void
121gtk_css_parser_finalize (GtkCssParser *self)
122{
123 if (self->user_destroy)
124 self->user_destroy (self->user_data);
125
126 g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
127 g_clear_object (&self->file);
128 g_clear_object (&self->directory);
129 if (self->blocks->len)
130 g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
131 g_array_free (array: self->blocks, TRUE);
132
133 g_slice_free (GtkCssParser, self);
134}
135
136GtkCssParser *
137gtk_css_parser_ref (GtkCssParser *self)
138{
139 g_atomic_int_inc (&self->ref_count);
140
141 return self;
142}
143
144void
145gtk_css_parser_unref (GtkCssParser *self)
146{
147 if (g_atomic_int_dec_and_test (&self->ref_count))
148 gtk_css_parser_finalize (self);
149}
150
151/**
152 * gtk_css_parser_get_file:
153 * @self: a `GtkCssParser`
154 *
155 * Gets the file being parsed. If no file is associated with @self -
156 * for example when raw data is parsed - %NULL is returned.
157 *
158 * Returns: (nullable) (transfer none): The file being parsed
159 */
160GFile *
161gtk_css_parser_get_file (GtkCssParser *self)
162{
163 return self->file;
164}
165
166/**
167 * gtk_css_parser_resolve_url:
168 * @self: a `GtkCssParser`
169 * @url: the URL to resolve
170 *
171 * Resolves a given URL against the parser's location.
172 *
173 * Returns: (nullable) (transfer full): a new `GFile` for the
174 * resolved URL
175 */
176GFile *
177gtk_css_parser_resolve_url (GtkCssParser *self,
178 const char *url)
179{
180 char *scheme;
181
182 scheme = g_uri_parse_scheme (uri: url);
183 if (scheme != NULL)
184 {
185 GFile *file = g_file_new_for_uri (uri: url);
186 g_free (mem: scheme);
187 return file;
188 }
189 g_free (mem: scheme);
190
191 if (self->directory == NULL)
192 return NULL;
193
194 return g_file_resolve_relative_path (file: self->directory, relative_path: url);
195}
196
197/**
198 * gtk_css_parser_get_start_location:
199 * @self: a `GtkCssParser`
200 *
201 * Queries the location of the current token.
202 *
203 * This function will return the location of the start of the
204 * current token. In the case a token has been consumed, but no
205 * new token has been queried yet via gtk_css_parser_peek_token()
206 * or gtk_css_parser_get_token(), the previous token's start
207 * location will be returned.
208 *
209 * This function may return the same location as
210 * gtk_css_parser_get_end_location() - in particular at the
211 * beginning and end of the document.
212 *
213 * Returns: the start location
214 **/
215const GtkCssLocation *
216gtk_css_parser_get_start_location (GtkCssParser *self)
217{
218 return &self->location;
219}
220
221/**
222 * gtk_css_parser_get_end_location:
223 * @self: a `GtkCssParser`
224 * @out_location: (caller-allocates) Place to store the location
225 *
226 * Queries the location of the current token.
227 *
228 * This function will return the location of the end of the
229 * current token. In the case a token has been consumed, but no
230 * new token has been queried yet via gtk_css_parser_peek_token()
231 * or gtk_css_parser_get_token(), the previous token's end location
232 * will be returned.
233 *
234 * This function may return the same location as
235 * gtk_css_parser_get_start_location() - in particular at the
236 * beginning and end of the document.
237 *
238 * Returns: the end location
239 **/
240const GtkCssLocation *
241gtk_css_parser_get_end_location (GtkCssParser *self)
242{
243 return gtk_css_tokenizer_get_location (tokenizer: self->tokenizer);
244}
245
246/**
247 * gtk_css_parser_get_block_location:
248 * @self: a `GtkCssParser`
249 *
250 * Queries the start location of the token that started the current
251 * block that is being parsed.
252 *
253 * If no block is currently parsed, the beginning of the document
254 * is returned.
255 *
256 * Returns: The start location of the current block
257 */
258const GtkCssLocation *
259gtk_css_parser_get_block_location (GtkCssParser *self)
260{
261 const GtkCssParserBlock *block;
262
263 if (self->blocks->len == 0)
264 {
265 static const GtkCssLocation start_of_document = { 0, };
266 return &start_of_document;
267 }
268
269 block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
270 return &block->start_location;
271}
272
273static void
274gtk_css_parser_ensure_token (GtkCssParser *self)
275{
276 GError *error = NULL;
277
278 if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
279 return;
280
281 self->location = *gtk_css_tokenizer_get_location (tokenizer: self->tokenizer);
282 if (!gtk_css_tokenizer_read_token (tokenizer: self->tokenizer, token: &self->token, error: &error))
283 {
284 /* We ignore the error here, because the resulting token will
285 * likely already trigger an error in the parsing code and
286 * duplicate errors are rather useless.
287 */
288 g_clear_error (err: &error);
289 }
290}
291
292const GtkCssToken *
293gtk_css_parser_peek_token (GtkCssParser *self)
294{
295 static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
296
297 gtk_css_parser_ensure_token (self);
298
299 if (self->blocks->len)
300 {
301 const GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
302 if (gtk_css_token_is (&self->token, block->end_token) ||
303 gtk_css_token_is (&self->token, block->inherited_end_token) ||
304 gtk_css_token_is (&self->token, block->alternative_token))
305 return &eof_token;
306 }
307
308 return &self->token;
309}
310
311const GtkCssToken *
312gtk_css_parser_get_token (GtkCssParser *self)
313{
314 const GtkCssToken *token;
315
316 for (token = gtk_css_parser_peek_token (self);
317 gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
318 gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
319 token = gtk_css_parser_peek_token (self))
320 {
321 gtk_css_parser_consume_token (self);
322 }
323
324 return token;
325}
326
327void
328gtk_css_parser_consume_token (GtkCssParser *self)
329{
330 gtk_css_parser_ensure_token (self);
331
332 /* unpreserved tokens MUST be consumed via start_block() */
333 g_assert (gtk_css_token_is_preserved (&self->token, NULL));
334
335 /* Don't consume any tokens at the end of a block */
336 if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
337 gtk_css_token_clear (token: &self->token);
338}
339
340void
341gtk_css_parser_start_block (GtkCssParser *self)
342{
343 GtkCssParserBlock block;
344
345 gtk_css_parser_ensure_token (self);
346
347 if (gtk_css_token_is_preserved (token: &self->token, out_closing: &block.end_token))
348 {
349 g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
350 return;
351 }
352
353 block.inherited_end_token = GTK_CSS_TOKEN_EOF;
354 block.alternative_token = GTK_CSS_TOKEN_EOF;
355 block.start_location = self->location;
356 g_array_append_val (self->blocks, block);
357
358 gtk_css_token_clear (token: &self->token);
359}
360
361void
362gtk_css_parser_start_semicolon_block (GtkCssParser *self,
363 GtkCssTokenType alternative_token)
364{
365 GtkCssParserBlock block;
366
367 block.end_token = GTK_CSS_TOKEN_SEMICOLON;
368 if (self->blocks->len)
369 block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1).end_token;
370 else
371 block.inherited_end_token = GTK_CSS_TOKEN_EOF;
372 block.alternative_token = alternative_token;
373 block.start_location = self->location;
374 g_array_append_val (self->blocks, block);
375}
376
377void
378gtk_css_parser_end_block_prelude (GtkCssParser *self)
379{
380 GtkCssParserBlock *block;
381
382 g_return_if_fail (self->blocks->len > 0);
383
384 block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
385
386 if (block->alternative_token == GTK_CSS_TOKEN_EOF)
387 return;
388
389 gtk_css_parser_skip_until (self, token_type: GTK_CSS_TOKEN_EOF);
390
391 if (gtk_css_token_is (&self->token, block->alternative_token))
392 {
393 if (gtk_css_token_is_preserved (token: &self->token, out_closing: &block->end_token))
394 {
395 g_critical ("alternative token is not preserved");
396 return;
397 }
398 block->alternative_token = GTK_CSS_TOKEN_EOF;
399 block->inherited_end_token = GTK_CSS_TOKEN_EOF;
400 gtk_css_token_clear (token: &self->token);
401 }
402}
403
404void
405gtk_css_parser_end_block (GtkCssParser *self)
406{
407 GtkCssParserBlock *block;
408
409 g_return_if_fail (self->blocks->len > 0);
410
411 gtk_css_parser_skip_until (self, token_type: GTK_CSS_TOKEN_EOF);
412
413 block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
414
415 if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
416 {
417 gtk_css_parser_warn (self,
418 code: GTK_CSS_PARSER_WARNING_SYNTAX,
419 start: gtk_css_parser_get_block_location (self),
420 end: gtk_css_parser_get_start_location (self),
421 format: "Unterminated block at end of document");
422 g_array_set_size (array: self->blocks, length: self->blocks->len - 1);
423 }
424 else if (gtk_css_token_is (&self->token, block->inherited_end_token))
425 {
426 g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
427 gtk_css_parser_warn (self,
428 code: GTK_CSS_PARSER_WARNING_SYNTAX,
429 start: gtk_css_parser_get_block_location (self),
430 end: gtk_css_parser_get_start_location (self),
431 format: "Expected ';' at end of block");
432 g_array_set_size (array: self->blocks, length: self->blocks->len - 1);
433 }
434 else
435 {
436 g_array_set_size (array: self->blocks, length: self->blocks->len - 1);
437 if (gtk_css_token_is_preserved (token: &self->token, NULL))
438 {
439 gtk_css_token_clear (token: &self->token);
440 }
441 else
442 {
443 gtk_css_parser_start_block (self);
444 gtk_css_parser_end_block (self);
445 }
446 }
447}
448
449/*
450 * gtk_css_parser_skip:
451 * @self: a `GtkCssParser`
452 *
453 * Skips a component value.
454 *
455 * This means that if the token is a preserved token, only
456 * this token will be skipped. If the token starts a block,
457 * the whole block will be skipped.
458 **/
459void
460gtk_css_parser_skip (GtkCssParser *self)
461{
462 const GtkCssToken *token;
463
464 token = gtk_css_parser_get_token (self);
465 if (gtk_css_token_is_preserved (token, NULL))
466 {
467 gtk_css_parser_consume_token (self);
468 }
469 else
470 {
471 gtk_css_parser_start_block (self);
472 gtk_css_parser_end_block (self);
473 }
474}
475
476/*
477 * gtk_css_parser_skip_until:
478 * @self: a `GtkCssParser`
479 * @token_type: type of token to skip to
480 *
481 * Repeatedly skips a token until a certain type is reached.
482 * After this called, gtk_css_parser_get_token() will either
483 * return a token of this type or the eof token.
484 *
485 * This function is useful for resyncing a parser after encountering
486 * an error.
487 *
488 * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
489 * as the token type.
490 **/
491void
492gtk_css_parser_skip_until (GtkCssParser *self,
493 GtkCssTokenType token_type)
494{
495 const GtkCssToken *token;
496
497 for (token = gtk_css_parser_get_token (self);
498 !gtk_css_token_is (token, token_type) &&
499 !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
500 token = gtk_css_parser_get_token (self))
501 {
502 gtk_css_parser_skip (self);
503 }
504}
505
506void
507gtk_css_parser_emit_error (GtkCssParser *self,
508 const GtkCssLocation *start,
509 const GtkCssLocation *end,
510 const GError *error)
511{
512 if (self->error_func)
513 self->error_func (self, start, end, error, self->user_data);
514}
515
516void
517gtk_css_parser_error (GtkCssParser *self,
518 GtkCssParserError code,
519 const GtkCssLocation *start,
520 const GtkCssLocation *end,
521 const char *format,
522 ...)
523{
524 va_list args;
525 GError *error;
526
527 va_start (args, format);
528 error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
529 code,
530 format, args);
531 gtk_css_parser_emit_error (self, start, end, error);
532 g_error_free (error);
533 va_end (args);
534}
535
536void
537gtk_css_parser_error_syntax (GtkCssParser *self,
538 const char *format,
539 ...)
540{
541 va_list args;
542 GError *error;
543
544 va_start (args, format);
545 error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
546 code: GTK_CSS_PARSER_ERROR_SYNTAX,
547 format, args);
548 gtk_css_parser_emit_error (self,
549 start: gtk_css_parser_get_start_location (self),
550 end: gtk_css_parser_get_end_location (self),
551 error);
552 g_error_free (error);
553 va_end (args);
554}
555
556void
557gtk_css_parser_error_value (GtkCssParser *self,
558 const char *format,
559 ...)
560{
561 va_list args;
562 GError *error;
563
564 va_start (args, format);
565 error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
566 code: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
567 format, args);
568 gtk_css_parser_emit_error (self,
569 start: gtk_css_parser_get_start_location (self),
570 end: gtk_css_parser_get_end_location (self),
571 error);
572 g_error_free (error);
573 va_end (args);
574}
575
576void
577gtk_css_parser_error_import (GtkCssParser *self,
578 const char *format,
579 ...)
580{
581 va_list args;
582 GError *error;
583
584 va_start (args, format);
585 error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
586 code: GTK_CSS_PARSER_ERROR_IMPORT,
587 format, args);
588 gtk_css_parser_emit_error (self,
589 start: gtk_css_parser_get_start_location (self),
590 end: gtk_css_parser_get_end_location (self),
591 error);
592 g_error_free (error);
593 va_end (args);
594}
595
596void
597gtk_css_parser_warn (GtkCssParser *self,
598 GtkCssParserWarning code,
599 const GtkCssLocation *start,
600 const GtkCssLocation *end,
601 const char *format,
602 ...)
603{
604 va_list args;
605 GError *error;
606
607 va_start (args, format);
608 error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
609 code,
610 format, args);
611 gtk_css_parser_emit_error (self, start, end, error);
612 g_error_free (error);
613 va_end (args);
614}
615
616void
617gtk_css_parser_warn_syntax (GtkCssParser *self,
618 const char *format,
619 ...)
620{
621 va_list args;
622 GError *error;
623
624 va_start (args, format);
625 error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
626 code: GTK_CSS_PARSER_WARNING_SYNTAX,
627 format, args);
628 gtk_css_parser_emit_error (self,
629 start: gtk_css_parser_get_start_location (self),
630 end: gtk_css_parser_get_end_location (self),
631 error);
632 g_error_free (error);
633 va_end (args);
634}
635
636gboolean
637gtk_css_parser_consume_function (GtkCssParser *self,
638 guint min_args,
639 guint max_args,
640 guint (* parse_func) (GtkCssParser *, guint, gpointer),
641 gpointer data)
642{
643 const GtkCssToken *token;
644 gboolean result = FALSE;
645 char *function_name;
646 guint arg;
647
648 token = gtk_css_parser_get_token (self);
649 g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
650
651 function_name = g_strdup (str: token->string.string);
652 gtk_css_parser_start_block (self);
653
654 arg = 0;
655 while (TRUE)
656 {
657 guint parse_args = parse_func (self, arg, data);
658 if (parse_args == 0)
659 break;
660 arg += parse_args;
661 token = gtk_css_parser_get_token (self);
662 if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
663 {
664 if (arg < min_args)
665 {
666 gtk_css_parser_error_syntax (self, format: "%s() requires at least %u arguments", function_name, min_args);
667 break;
668 }
669 else
670 {
671 result = TRUE;
672 break;
673 }
674 }
675 else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
676 {
677 if (arg >= max_args)
678 {
679 gtk_css_parser_error_syntax (self, format: "Expected ')' at end of %s()", function_name);
680 break;
681 }
682
683 gtk_css_parser_consume_token (self);
684 continue;
685 }
686 else
687 {
688 gtk_css_parser_error_syntax (self, format: "Unexpected data at end of %s() argument", function_name);
689 break;
690 }
691 }
692
693 gtk_css_parser_end_block (self);
694 g_free (mem: function_name);
695
696 return result;
697}
698
699/**
700 * gtk_css_parser_has_token:
701 * @self: a `GtkCssParser`
702 * @token_type: type of the token to check
703 *
704 * Checks if the next token is of @token_type.
705 *
706 * Returns: %TRUE if the next token is of @token_type
707 **/
708gboolean
709gtk_css_parser_has_token (GtkCssParser *self,
710 GtkCssTokenType token_type)
711{
712 const GtkCssToken *token;
713
714 token = gtk_css_parser_get_token (self);
715
716 return gtk_css_token_is (token, token_type);
717}
718
719/**
720 * gtk_css_parser_has_ident:
721 * @self: a `GtkCssParser`
722 * @ident: name of identifier
723 *
724 * Checks if the next token is an identifier with the given @name.
725 *
726 * Returns: %TRUE if the next token is an identifier with the given @name
727 **/
728gboolean
729gtk_css_parser_has_ident (GtkCssParser *self,
730 const char *ident)
731{
732 const GtkCssToken *token;
733
734 token = gtk_css_parser_get_token (self);
735
736 return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
737 g_ascii_strcasecmp (s1: token->string.string, s2: ident) == 0;
738}
739
740gboolean
741gtk_css_parser_has_integer (GtkCssParser *self)
742{
743 const GtkCssToken *token;
744
745 token = gtk_css_parser_get_token (self);
746
747 return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
748 gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
749}
750
751/**
752 * gtk_css_parser_has_function:
753 * @self: a `GtkCssParser`
754 * @name: name of function
755 *
756 * Checks if the next token is a function with the given @name.
757 *
758 * Returns: %TRUE if the next token is a function with the given @name
759 **/
760gboolean
761gtk_css_parser_has_function (GtkCssParser *self,
762 const char *name)
763{
764 const GtkCssToken *token;
765
766 token = gtk_css_parser_get_token (self);
767
768 return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
769 g_ascii_strcasecmp (s1: token->string.string, s2: name) == 0;
770}
771
772/**
773 * gtk_css_parser_try_delim:
774 * @self: a `GtkCssParser`
775 * @codepoint: unicode character codepoint to check
776 *
777 * Checks if the current token is a delimiter matching the given
778 * @codepoint. If that is the case, the token is consumed and
779 * %TRUE is returned.
780 *
781 * Keep in mind that not every unicode codepoint can be a delim
782 * token.
783 *
784 * Returns: %TRUE if the token matched and was consumed.
785 **/
786gboolean
787gtk_css_parser_try_delim (GtkCssParser *self,
788 gunichar codepoint)
789{
790 const GtkCssToken *token;
791
792 token = gtk_css_parser_get_token (self);
793
794 if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
795 codepoint != token->delim.delim)
796 return FALSE;
797
798 gtk_css_parser_consume_token (self);
799 return TRUE;
800}
801
802/**
803 * gtk_css_parser_try_ident:
804 * @self: a `GtkCssParser`
805 * @ident: identifier to check for
806 *
807 * Checks if the current token is an identifier matching the given
808 * @ident string. If that is the case, the token is consumed
809 * and %TRUE is returned.
810 *
811 * Returns: %TRUE if the token matched and was consumed.
812 **/
813gboolean
814gtk_css_parser_try_ident (GtkCssParser *self,
815 const char *ident)
816{
817 const GtkCssToken *token;
818
819 token = gtk_css_parser_get_token (self);
820
821 if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
822 g_ascii_strcasecmp (s1: token->string.string, s2: ident) != 0)
823 return FALSE;
824
825 gtk_css_parser_consume_token (self);
826 return TRUE;
827}
828
829/**
830 * gtk_css_parser_try_at_keyword:
831 * @self: a `GtkCssParser`
832 * @keyword: name of keyword to check for
833 *
834 * Checks if the current token is an at-keyword token with the
835 * given @keyword. If that is the case, the token is consumed
836 * and %TRUE is returned.
837 *
838 * Returns: %TRUE if the token matched and was consumed.
839 **/
840gboolean
841gtk_css_parser_try_at_keyword (GtkCssParser *self,
842 const char *keyword)
843{
844 const GtkCssToken *token;
845
846 token = gtk_css_parser_get_token (self);
847
848 if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
849 g_ascii_strcasecmp (s1: token->string.string, s2: keyword) != 0)
850 return FALSE;
851
852 gtk_css_parser_consume_token (self);
853 return TRUE;
854}
855
856/**
857 * gtk_css_parser_try_token:
858 * @self: a `GtkCssParser`
859 * @token_type: type of token to try
860 *
861 * Consumes the next token if it matches the given @token_type.
862 *
863 * This function can be used in loops like this:
864 * do {
865 * ... parse one element ...
866 * } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
867 *
868 * Returns: %TRUE if a token was consumed
869 **/
870gboolean
871gtk_css_parser_try_token (GtkCssParser *self,
872 GtkCssTokenType token_type)
873{
874 const GtkCssToken *token;
875
876 token = gtk_css_parser_get_token (self);
877
878 if (!gtk_css_token_is (token, token_type))
879 return FALSE;
880
881 gtk_css_parser_consume_token (self);
882 return TRUE;
883}
884
885/**
886 * gtk_css_parser_consume_ident:
887 * @self: a `GtkCssParser`
888 *
889 * If the current token is an identifier, consumes it and returns
890 * its name.
891 *
892 * If the current token is not an identifier, an error is emitted
893 * and %NULL is returned.
894 *
895 * Returns: (transfer full): the name of the consumed identifier
896 */
897char *
898gtk_css_parser_consume_ident (GtkCssParser *self)
899{
900 const GtkCssToken *token;
901 char *ident;
902
903 token = gtk_css_parser_get_token (self);
904
905 if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
906 {
907 gtk_css_parser_error_syntax (self, format: "Expected an identifier");
908 return NULL;
909 }
910
911 ident = g_strdup (str: token->string.string);
912 gtk_css_parser_consume_token (self);
913
914 return ident;
915}
916
917/**
918 * gtk_css_parser_consume_string:
919 * @self: a `GtkCssParser`
920 *
921 * If the current token is a string, consumes it and return the string.
922 *
923 * If the current token is not a string, an error is emitted
924 * and %NULL is returned.
925 *
926 * Returns: (transfer full): the name of the consumed string
927 **/
928char *
929gtk_css_parser_consume_string (GtkCssParser *self)
930{
931 const GtkCssToken *token;
932 char *ident;
933
934 token = gtk_css_parser_get_token (self);
935
936 if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
937 {
938 gtk_css_parser_error_syntax (self, format: "Expected a string");
939 return NULL;
940 }
941
942 ident = g_strdup (str: token->string.string);
943 gtk_css_parser_consume_token (self);
944
945 return ident;
946}
947
948static guint
949gtk_css_parser_parse_url_arg (GtkCssParser *parser,
950 guint arg,
951 gpointer data)
952{
953 char **out_url = data;
954
955 *out_url = gtk_css_parser_consume_string (self: parser);
956 if (*out_url == NULL)
957 return 0;
958
959 return 1;
960}
961
962/**
963 * gtk_css_parser_consume_url:
964 * @self: a `GtkCssParser`
965 *
966 * If the parser matches the <url> token from the [CSS
967 * specification](https://drafts.csswg.org/css-values-4/#url-value),
968 * consumes it, resolves the URL and returns the resulting `GFile`.
969 * On failure, an error is emitted and %NULL is returned.
970 *
971 * Returns: (nullable) (transfer full): the resulting URL
972 **/
973char *
974gtk_css_parser_consume_url (GtkCssParser *self)
975{
976 const GtkCssToken *token;
977 char *url;
978
979 token = gtk_css_parser_get_token (self);
980
981 if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
982 {
983 url = g_strdup (str: token->string.string);
984 gtk_css_parser_consume_token (self);
985 }
986 else if (gtk_css_token_is_function (token, ident: "url"))
987 {
988 if (!gtk_css_parser_consume_function (self, min_args: 1, max_args: 1, parse_func: gtk_css_parser_parse_url_arg, data: &url))
989 return NULL;
990 }
991 else
992 {
993 gtk_css_parser_error_syntax (self, format: "Expected a URL");
994 return NULL;
995 }
996
997 return url;
998}
999
1000gboolean
1001gtk_css_parser_has_number (GtkCssParser *self)
1002{
1003 return gtk_css_parser_has_token (self, token_type: GTK_CSS_TOKEN_SIGNED_NUMBER)
1004 || gtk_css_parser_has_token (self, token_type: GTK_CSS_TOKEN_SIGNLESS_NUMBER)
1005 || gtk_css_parser_has_token (self, token_type: GTK_CSS_TOKEN_SIGNED_INTEGER)
1006 || gtk_css_parser_has_token (self, token_type: GTK_CSS_TOKEN_SIGNLESS_INTEGER);
1007}
1008
1009gboolean
1010gtk_css_parser_consume_number (GtkCssParser *self,
1011 double *number)
1012{
1013 const GtkCssToken *token;
1014
1015 token = gtk_css_parser_get_token (self);
1016 if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
1017 gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
1018 gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
1019 gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
1020 {
1021 *number = token->number.number;
1022 gtk_css_parser_consume_token (self);
1023 return TRUE;
1024 }
1025
1026 gtk_css_parser_error_syntax (self, format: "Expected a number");
1027 /* FIXME: Implement calc() */
1028 return FALSE;
1029}
1030
1031gboolean
1032gtk_css_parser_consume_integer (GtkCssParser *self,
1033 int *number)
1034{
1035 const GtkCssToken *token;
1036
1037 token = gtk_css_parser_get_token (self);
1038 if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
1039 gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
1040 {
1041 *number = token->number.number;
1042 gtk_css_parser_consume_token (self);
1043 return TRUE;
1044 }
1045
1046 gtk_css_parser_error_syntax (self, format: "Expected an integer");
1047 /* FIXME: Implement calc() */
1048 return FALSE;
1049}
1050
1051gboolean
1052gtk_css_parser_consume_percentage (GtkCssParser *self,
1053 double *number)
1054{
1055 const GtkCssToken *token;
1056
1057 token = gtk_css_parser_get_token (self);
1058 if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
1059 {
1060 *number = token->number.number;
1061 gtk_css_parser_consume_token (self);
1062 return TRUE;
1063 }
1064
1065 gtk_css_parser_error_syntax (self, format: "Expected a percentage");
1066 /* FIXME: Implement calc() */
1067 return FALSE;
1068}
1069
1070gsize
1071gtk_css_parser_consume_any (GtkCssParser *parser,
1072 const GtkCssParseOption *options,
1073 gsize n_options,
1074 gpointer user_data)
1075{
1076 gsize result;
1077 gsize i;
1078
1079 g_return_val_if_fail (parser != NULL, 0);
1080 g_return_val_if_fail (options != NULL, 0);
1081 g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
1082
1083 result = 0;
1084 while (result != (1u << n_options) - 1u)
1085 {
1086 for (i = 0; i < n_options; i++)
1087 {
1088 if (result & (1 << i))
1089 continue;
1090 if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
1091 continue;
1092 if (!options[i].parse (parser, options[i].data, user_data))
1093 return 0;
1094 result |= 1 << i;
1095 break;
1096 }
1097 if (i == n_options)
1098 break;
1099 }
1100
1101 if (result == 0)
1102 {
1103 gtk_css_parser_error_syntax (self: parser, format: "No valid value given");
1104 return result;
1105 }
1106
1107 return result;
1108}
1109

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