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