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 | |
29 | struct _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 | |
41 | void |
42 | gtk_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 | |
99 | static void |
100 | gtk_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 | |
135 | static void |
136 | append_ident (GString *string, |
137 | const char *ident) |
138 | { |
139 | /* XXX */ |
140 | g_string_append (string, val: ident); |
141 | } |
142 | |
143 | static void |
144 | append_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 | **/ |
164 | gboolean |
165 | gtk_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 | */ |
229 | gboolean |
230 | gtk_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 | |
294 | gboolean |
295 | gtk_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 | |
302 | gboolean |
303 | gtk_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 | |
310 | gboolean |
311 | gtk_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 | |
318 | void |
319 | gtk_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 | |
482 | char * |
483 | gtk_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 | |
492 | static void |
493 | gtk_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 | |
515 | static void |
516 | gtk_css_token_init_delim (GtkCssToken *token, |
517 | gunichar delim) |
518 | { |
519 | token->type = GTK_CSS_TOKEN_DELIM; |
520 | token->delim.delim = delim; |
521 | } |
522 | |
523 | static void |
524 | gtk_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 | |
544 | static void |
545 | gtk_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 | |
566 | GtkCssTokenizer * |
567 | gtk_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 | |
584 | GtkCssTokenizer * |
585 | gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer) |
586 | { |
587 | tokenizer->ref_count++; |
588 | |
589 | return tokenizer; |
590 | } |
591 | |
592 | void |
593 | gtk_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 | |
604 | const GtkCssLocation * |
605 | gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer) |
606 | { |
607 | return &tokenizer->position; |
608 | } |
609 | |
610 | static void G_GNUC_PRINTF(2, 3) |
611 | gtk_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 | |
633 | static gboolean |
634 | is_newline (char c) |
635 | { |
636 | return c == '\n' |
637 | || c == '\r' |
638 | || c == '\f'; |
639 | } |
640 | |
641 | static gboolean |
642 | is_whitespace (char c) |
643 | { |
644 | return is_newline (c) |
645 | || c == '\t' |
646 | || c == ' '; |
647 | } |
648 | |
649 | static gboolean |
650 | is_multibyte (char c) |
651 | { |
652 | return c & 0x80; |
653 | } |
654 | |
655 | static gboolean |
656 | is_name_start (char c) |
657 | { |
658 | return is_multibyte (c) |
659 | || g_ascii_isalpha (c) |
660 | || c == '_'; |
661 | } |
662 | |
663 | static gboolean |
664 | is_name (char c) |
665 | { |
666 | return is_name_start (c) |
667 | || g_ascii_isdigit (c) |
668 | || c == '-'; |
669 | } |
670 | |
671 | static gboolean |
672 | is_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 | |
681 | static gboolean |
682 | is_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 | |
700 | static inline gsize |
701 | gtk_css_tokenizer_remaining (GtkCssTokenizer *tokenizer) |
702 | { |
703 | return tokenizer->end - tokenizer->data; |
704 | } |
705 | |
706 | static gboolean |
707 | gtk_css_tokenizer_has_valid_escape (GtkCssTokenizer *tokenizer) |
708 | { |
709 | return is_valid_escape (data: tokenizer->data, end: tokenizer->end); |
710 | } |
711 | |
712 | static gboolean |
713 | gtk_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 | |
745 | static gboolean |
746 | gtk_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 | |
770 | static void |
771 | gtk_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 | |
785 | static inline void |
786 | gtk_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 | |
796 | static inline void |
797 | gtk_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 | |
803 | static inline void |
804 | gtk_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 | |
812 | static inline void |
813 | gtk_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 | |
828 | static void |
829 | gtk_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 | |
840 | static gunichar |
841 | gtk_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 | |
878 | static char * |
879 | gtk_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 | |
918 | static void |
919 | gtk_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 | |
936 | static gboolean |
937 | gtk_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 | |
1013 | static gboolean |
1014 | gtk_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 | |
1047 | static void |
1048 | gtk_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 | |
1152 | static void |
1153 | gtk_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 | |
1160 | static gboolean |
1161 | gtk_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 | |
1194 | static gboolean |
1195 | gtk_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 | |
1246 | static gboolean |
1247 | (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 | |
1270 | static void |
1271 | gtk_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 | |
1286 | gboolean |
1287 | gtk_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 | |